canvas(id="scene")
a(target="_blank", href="https://twitter.com/ReisnerShawn")
  img(class="social", src="https://cdn1.iconfinder.com/data/icons/logotypes/32/twitter-128.png")
View Compiled
$background: #50514F;

* {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

body {
  background: $background
}

#scene {
  position: absolute;
  background: $background;
}

a {
  position: absolute;
  z-index: 1;
  width: 48px;
  height: 48px;
  left: 25px;
  top: 25px;
  animation: 3s pop infinite;
  
  img {
    width: 100%;
    height: 100%;
  }
}

@keyframes pop {
  0% {
    transform: scale(1);
  }
  
  10% {
    transform: scale(1.5);
  }
  
  20% {
    transform: scale(1);
  }
}
View Compiled
let scene = document.getElementById("scene");
let context = scene.getContext('2d');

scene.width = window.innerWidth;
scene.height = window.innerHeight;

let dots = [];
let dotMaxSpeed = 25;
let dotMaxRadius = 5;
let gravity = 0.005;
let spigotSize = 3;
let waveHeight = window.innerHeight / 5;
let colors = [
  '#FFF',
  '#000',
  '#999'
]
let origin = {
  x: 25,
  y: window.innerHeight / 2 - waveHeight
};
let originSpeed = 10;

let mouseDown = false;
let mouseX, mouseY;

let addDots = () => {
  for(let i = 0; i < spigotSize; i++) {
    dots.push(generateDot(origin.x, origin.y));
  }
};

let generateDot = (x, y) => {
  return {
    x: x,
    y: y,
    vx: getRandomSpeed(),
    vy: getRandomSpeed(),
    ax: 0,
    ay: 0,
    radius: getRandomRadius(),
    color: getRandomColor()
  };
};

let getRandomSpeed = () => {
  return Math.random() * dotMaxSpeed * 2 - dotMaxSpeed;
};

let getRandomRadius = () => {
  return Math.random() * dotMaxRadius;
};

let getRandomColor = () => {
  return colors[Math.floor(Math.random() * colors.length)];
};

let clear = () => {
  context.clearRect(0, 0, window.innerWidth, window.innerHeight);
}

let draw = () => {
  let outOfBoundsDotsIndices = [];
  for(let i = 0; i < dots.length; i++) {
    let dot = dots[i];
    if(isOutOfBounds(dot)) {
      outOfBoundsDotsIndices.push(i);
    }
    drawDot(dot);
  }
  
  removeDots(outOfBoundsDotsIndices);
};

let drawDot = (dot) => {
  context.fillStyle = `${dot.color}`;
  context.beginPath();
  context.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2);
  context.fill();
};

let update = () => {
  dots.forEach(updateDotPosition);
  updateOrigin();
};

let updateDotPosition = (dot) => {
  var sin = Math.sin(dot.ay);
  dot.ay += gravity;
  dot.vy += dot.ay;
  dot.x += dot.vx * sin * sin * sin * sin;
  dot.y += dot.vy * sin * sin * sin * sin;
  
};

let updateOrigin = () => {
  origin.x += originSpeed;
  if(origin.x > window.innerWidth) {
    origin.x = -50;
  }
  origin.y = window.innerHeight / 2 + originSpeed * Math.sin(origin.x / waveHeight) * 10;
};

let isOutOfBounds = (dot) => {
  return dot.x < 0 || dot.x > window.innerWidth ||
         dot.y < 0 || dot.y > window.innerHeight;
};

let removeDots = (indices) => {
  indices.reverse().forEach((index) => {
    dots.splice(index, 1);
  });
};

let tick = () => {
  clear();
  addDots();
  draw();
  update();
  requestAnimationFrame(tick);
};

let reset = () => {
  dots = [];
  clear();
};

scene.addEventListener('mousedown', (event) => {
  mouseDown = true;
  mouseX = event.x;
  mouseY = event.y;
});

scene.addEventListener('mousemove', (event) => {
  if(mouseDown) {
    mouseX = event.x;
    mouseY = event.y;
  }
});

scene.addEventListener('mouseup', () => {
  mouseDown = false;
});

tick();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.