<!--
Project forked from:
https://codepen.io/jackrugile/pen/ZYMJpd
-->
<link href="https://fonts.googleapis.com/css?family=Oxygen:300" rel="stylesheet">
<canvas id="c"></canvas>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha256-/SIrNqv8h6QGKDuNoLGA4iret+kyesCkHGzVUUV0shc=" crossorigin="anonymous"></script>
<script src="js/index.js"></script>
<div class="modal">
<div>
<p>St. Patrick is famous for banishing snakes from Ireland <a href="https://en.wikipedia.org/wiki/Saint_Patrick#Patrick_banishes_all_snakes_from_Ireland" target="_blank">(source)</a>. In St. Patrick's honor:</p>
<button>Chase Snakes With Shamrocks</button>
</div>
</div>
<div class="modal-overlay" id="modal-overlay"></div>
<script>
$(document).ready(function() {
$('.modal button').click(function() {
$('div.modal').remove();
$('div.modal-overlay').remove();
});
});
</script>
html,
body {
height: 100%;
}
body {
background-color: #37444F;
font-family: 'Oxygen', sans-serif;
line-height: 2rem;
}
a {
color: #F65A5B;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
canvas {
display: block;
}
body {
word-wrap: break-word;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80vw;
max-width: 100%;
height: 30vh;
max-height: 100%;
background-color: rgba(255,255,255,1);
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
z-index: 1010;
font-size: calc(1rem + 1vw);
padding: 3rem 4rem;
}
.modal-overlay {
z-index: 1000;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.modal button {
background-color: #34B3A0;
border: none;
display: block;
width: 100%;
margin-top: 2rem;
padding: 1.5rem 2.5rem;
color: white;
font-size: calc(0.9rem + 1vw);
font-weight: 300;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 0.05rem;
animation-name: lantern;
animation-duration: 3s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-play-state: running;
}
.text {
font: 16px/300px 'Lato', sans-serif;
width: 300px;
height: 300px;
position: absolute;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
text-align: center;
user-select: none;
}
.click-explosion {
width: 50px;
height: 50px;
background-image: url('https://cloud.githubusercontent.com/assets/6455018/24014398/2d53347a-0a5b-11e7-831e-0fb82b1d1008.png');
animation: click-explode 1s ease-out forwards;
position: absolute;
z-index: 400;
}
.click-explosion-right {
width: 50px;
height: 50px;
border-radius: 50px;
background: #f00;
animation: click-explode 1s ease-out forwards;
position: absolute;
z-index: 400;
}
@keyframes click-explode {
100% {
transform: scale(1.5);
opacity: 0;
}
}
@keyframes lantern {
from {
background-color: #34B3A0;
}
50% {
background-color: HSL(171, 55%, 30%);
}
to {
background-color: #34B3A0;
}
}
const a = document.getElementById('c');
var c = a.getContext('2d');
const chains = [];
const chainCount = 30;
const entityCount = 20;
let w = a.width;
let h = a.height;
let cx = w / 2;
let cy = h / 2;
let mx = cx;
let my = cy;
let md = 0;
const maxa = 2;
const maxv = 1;
const avoidTick = 20;
const avoidThresh = 50;
function rand(min, max) {
return (Math.random() * (max - min)) + min;
}
function Impulse() {
this.x = cx;
this.y = cy;
this.ax = 0;
this.ay = 0;
this.vx = 0;
this.vy = 0;
this.r = 1;
}
Impulse.prototype.step = function step() {
this.x += this.vx;
this.y += this.vy;
if (this.x + this.r >= w || this.x <= this.r) {
this.vx = 0;
this.ax = 0;
}
if (this.y + this.r >= h || this.y <= this.r) {
this.vy = 0;
this.ay = 0;
}
if (this.x + this.r >= w) {
this.x = w - this.r;
}
if (this.x <= this.r) {
this.x = this.r;
}
if (this.y + this.r >= h) {
this.y = h - this.r;
}
if (this.y <= this.r) {
this.y = this.r;
}
if (md) {
this.vx += (this.x - mx) * 0.03;
this.vy += (this.y - my) * 0.03;
}
this.ax += rand(-0.4, 0.4);
this.ay += rand(-0.4, 0.4);
this.vx += this.ax;
this.vy += this.ay;
this.ax *= Math.abs(this.ax) > maxa
? 0.75
: 1;
this.ay *= Math.abs(this.ay) > maxa
? 0.75
: 1;
this.vx *= Math.abs(this.vx) > maxv
? 0.75
: 1;
this.vy *= Math.abs(this.vy) > maxv
? 0.75
: 1;
};
Chain.prototype.step = function () {
this.impulse.step();
this.branches.forEach((branch) => {
branch.step();
});
this.branches.forEach((branch) => {
branch.draw();
});
};
function Branch(opt) {
this.entities = [];
this.chain = opt.chain;
this.avoiding = 0;
let entity;
for (let i = 0; i < entityCount; i++) {
entity = new Entity({
branch: this,
i,
x: cx,
y: cy,
radius: 1 + (((entityCount - i) / entityCount) * 5),
damp: 0.2,
attractor: i === 0
? opt.attractor
: this.entities[i - 1]
});
this.entities.push(entity);
}
}
function Chain() {
this.branches = [];
this.impulse = new Impulse();
this.branches.push(new Branch({chain: this, attractor: this.impulse}));
}
Branch.prototype.step = function () {
let i = chains.length;
while (i--) {
const impulse = this.chain.impulse;
const oImpulse = chains[i].impulse;
const dx = oImpulse.x - impulse.x;
const dy = oImpulse.y - impulse.y;
const dist = Math.sqrt((dx * dx) + (dy * dy));
if (!md && impulse !== oImpulse && dist < avoidThresh) {
impulse.ax = 0;
impulse.ay = 0;
impulse.vx -= dx * 0.1;
impulse.vy -= dy * 0.1;
this.avoiding = avoidTick;
}
}
this.entities.forEach((entity) => {
entity.step();
});
if (this.avoiding > 0) {
this.avoiding--;
}
};
Branch.prototype.draw = function () {
c.beginPath();
c.moveTo(this.entities[0].x, this.entities[0].y);
this.entities.forEach((entity, i) => {
if (i > 0) {
c.lineTo(entity.x, entity.y);
}
});
this.entities.forEach((entity) => {
c.save();
c.translate(entity.x, entity.y);
c.beginPath();
c.rotate(entity.rot);
c.fillRect(-entity.radius, -entity.radius, entity.radius * 3, entity.radius * 3);
c.restore();
});
};
function Entity(opt) {
this.branch = opt.branch;
this.i = opt.i;
this.x = opt.x;
this.y = opt.y;
this.vx = 0;
this.vy = 0;
this.radius = opt.radius;
this.attractor = opt.attractor;
this.damp = opt.damp;
}
Entity.prototype.step = function () {
this.vx = (this.attractor.x - this.x) * this.damp;
this.vy = (this.attractor.y - this.y) * this.damp;
this.x += this.vx;
this.y += this.vy;
this.av = (Math.abs(this.vx) + Math.abs(this.vy)) / 2;
const dx = this.attractor.x - this.x;
const dy = this.attractor.y - this.y;
this.rot = Math.atan2(dy, dx);
};
function loop() {
requestAnimationFrame(loop);
c.globalCompositeOperation = 'destination-out';
c.fillStyle = md
? '#F65A5B'
: '#34B3A0';
c.fillRect(0, 0, a.width, a.height);
c.globalCompositeOperation = 'lighter';
chains.forEach((chain) => {
chain.step();
});
tick++;
}
function resize() {
a.width = window.innerWidth;
a.height = window.innerHeight;
w = a.width;
h = a.height;
cx = w / 2;
cy = h / 2;
}
function createShamrock(e) {
const elem = $('<div class="click-explosion"></div>').appendTo('body');
const mouseX = e.pageX - 20;
const mouseY = e.pageY - 20;
elem.css('top', mouseY);
elem.css('left', mouseX);
setTimeout(() => {
elem.remove();
}, 1000);
}
window.addEventListener('resize', resize);
window.addEventListener('mousedown', (e) => {
md = 1;
createShamrock(e);
});
window.addEventListener('touchstart', (e) => {
md = 1;
createShamrock(e);
});
window.addEventListener('mouseup', () => {
md = 0;
});
window.addEventListener('touchend', (e) => {
md = 1;
});
window.addEventListener('mousemove', (e) => {
mx = e.clientX;
my = e.clientY;
});
resize();
for (let i = 0; i < chainCount; i++) {
chains.push(new Chain());
}
loop();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.