live - 60fps 1920 x 1080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Click Response</title>
<!-- Anime.js library for animations -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/1.0.0/anime.min.js"></script>
<style>
canvas {
display: block;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
// Recreate the click response animation from CodePen
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cH;
var cW;
var bgColor = "#FF6138";
var animations = [];
// Color cycling helper
var colorPicker = (function() {
var colors = ["#FF6138", "#FFBE53", "#2980B9", "#282741"];
var index = 0;
function next() {
index = index++ < colors.length - 1 ? index : 0;
return colors[index];
}
function current() {
return colors[index];
}
return { next: next, current: current };
})();
function removeAnimation(animation) {
var index = animations.indexOf(animation);
if (index > -1) animations.splice(index, 1);
}
function calcPageFillRadius(x, y) {
var l = Math.max(x - 0, cW - x);
var h = Math.max(y - 0, cH - y);
return Math.sqrt(Math.pow(l, 2) + Math.pow(h, 2));
}
function addClickListeners() {
document.addEventListener("touchstart", handleEvent);
document.addEventListener("mousedown", handleEvent);
}
function handleEvent(e) {
if (e.touches) {
e.preventDefault();
e = e.touches[0];
}
var currentColor = colorPicker.current();
var nextColor = colorPicker.next();
var targetR = calcPageFillRadius(e.pageX, e.pageY);
var rippleSize = Math.min(200, cW * 0.4);
var minCoverDuration = 750;
var pageFill = new Circle({ x: e.pageX, y: e.pageY, r: 0, fill: nextColor });
var fillAnimation = anime({
targets: pageFill,
r: targetR,
duration: Math.max(targetR / 2, minCoverDuration),
easing: "easeOutQuart",
complete: function() {
bgColor = pageFill.fill;
removeAnimation(fillAnimation);
},
});
var ripple = new Circle({
x: e.pageX,
y: e.pageY,
r: 0,
fill: currentColor,
stroke: { width: 3, color: currentColor },
opacity: 1,
});
var rippleAnimation = anime({
targets: ripple,
r: rippleSize,
opacity: 0,
easing: "easeOutExpo",
duration: 900,
complete: removeAnimation,
});
var particles = [];
for (var i = 0; i < 32; i++) {
var particle = new Circle({
x: e.pageX,
y: e.pageY,
fill: currentColor,
r: anime.random(24, 48),
});
particles.push(particle);
}
var particlesAnimation = anime({
targets: particles,
x: function(particle) {
return particle.x + anime.random(rippleSize, -rippleSize);
},
y: function(particle) {
return particle.y + anime.random(rippleSize * 1.15, -rippleSize * 1.15);
},
r: 0,
easing: "easeOutExpo",
duration: anime.random(1000, 1300),
complete: removeAnimation,
});
animations.push(fillAnimation, rippleAnimation, particlesAnimation);
}
function extend(a, b) {
for (var key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;
}
var Circle = function(opts) {
extend(this, opts);
};
Circle.prototype.draw = function() {
ctx.globalAlpha = this.opacity || 1;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
if (this.stroke) {
ctx.strokeStyle = this.stroke.color;
ctx.lineWidth = this.stroke.width;
ctx.stroke();
}
if (this.fill) {
ctx.fillStyle = this.fill;
ctx.fill();
}
ctx.closePath();
ctx.globalAlpha = 1;
};
var animationLoop = anime({
duration: Infinity,
update: function() {
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, cW, cH);
animations.forEach(function(anim) {
anim.animatables.forEach(function(animatable) {
animatable.target.draw();
});
});
},
});
var resizeCanvas = function() {
cW = window.innerWidth;
cH = window.innerHeight;
var pixelRatio = window.devicePixelRatio || 1;
c.width = cW * pixelRatio;
c.height = cH * pixelRatio;
ctx.scale(pixelRatio, pixelRatio);
};
(function init() {
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
addClickListeners();
handleInactiveUser();
})();
function handleInactiveUser() {
var inactive = setTimeout(function() {
fauxClick(cW / 2, cH / 2);
}, 2000);
function clearInactiveTimeout() {
clearTimeout(inactive);
document.removeEventListener("mousedown", clearInactiveTimeout);
document.removeEventListener("touchstart", clearInactiveTimeout);
}
document.addEventListener("mousedown", clearInactiveTimeout);
document.addEventListener("touchstart", clearInactiveTimeout);
}
function fauxClick(x, y) {
var faux = new Event("mousedown");
faux.pageX = x;
faux.pageY = y;
document.dispatchEvent(faux);
}
</script>
</body>
</html> About this animation
Ripple effect on mouse click.
Under the hood
Event-Listener Ripple Math
This script acts on the
window.addEventListener('click') event. Upon firing, it captures the user's clientX and clientY mouse coordinates and spawns an ephemeral ripple element (or canvas ring) that expands via CSS transform: scale() before unmounting itself for memory cleanup. Related