(function() { let possibleColors = [ "#D61C59", "#E7D84B", "#1B8798", ]; let element = document.body; let width = window.innerWidth; let height = window.innerHeight; const cursor = { x: width / 2, y: width / 2 }; const lastPos = { x: width / 2, y: width / 2 }; const particles = []; const canvImages = []; let canvas, context, animationFrame; const char = "*"; const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ); // Re-initialise or destroy the cursor when the prefers-reduced-motion setting changes prefersReducedMotion.onchange = () => { if (prefersReducedMotion.matches) { destroy(); } else { init(); } }; function init() { // Don't show the cursor trail if the user has prefers-reduced-motion enabled if (prefersReducedMotion.matches) { console.log( "This browser has prefers reduced motion turned on, so the cursor did not init" ); return false; } canvas = document.createElement("canvas"); context = canvas.getContext("2d"); canvas.style.top = "0px"; canvas.style.left = "0px"; canvas.style.pointerEvents = "none"; canvas.style.position = "fixed"; element.appendChild(canvas); canvas.width = width; canvas.height = height; context.font = "21px serif"; context.textBaseline = "middle"; context.textAlign = "center"; possibleColors.forEach((color) => { let measurements = context.measureText(char); let bgCanvas = document.createElement("canvas"); let bgContext = bgCanvas.getContext("2d"); bgCanvas.width = measurements.width; bgCanvas.height = measurements.actualBoundingBoxAscent + measurements.actualBoundingBoxDescent; bgContext.fillStyle = color; bgContext.textAlign = "center"; bgContext.font = "21px serif"; bgContext.textBaseline = "middle"; bgContext.fillText( char, bgCanvas.width / 2, measurements.actualBoundingBoxAscent ); canvImages.push(bgCanvas); }); bindEvents(); loop(); } // Bind events that are needed function bindEvents() { element.addEventListener("mousemove", onMouseMove); element.addEventListener("touchmove", onTouchMove, { passive: true }); element.addEventListener("touchstart", onTouchMove, { passive: true }); window.addEventListener("resize", onWindowResize); } function onWindowResize(e) { width = window.innerWidth; height = window.innerHeight; canvas.width = width; canvas.height = height; } function onTouchMove(e) { if (e.touches.length > 0) { for (let i = 0; i < e.touches.length; i++) { addParticle( e.touches[i].clientX, e.touches[i].clientY, canvImages[Math.floor(Math.random() * canvImages.length)] ); } } } function onMouseMove(e) { window.requestAnimationFrame(() => { cursor.x = e.clientX; cursor.y = e.clientY; const distBetweenPoints = Math.hypot( cursor.x - lastPos.x, cursor.y - lastPos.y ); if (distBetweenPoints > 1.5) { addParticle( cursor.x, cursor.y, canvImages[Math.floor(Math.random() * possibleColors.length)] ); lastPos.x = cursor.x; lastPos.y = cursor.y; } }); } function addParticle(x, y, color) { particles.push(new Particle(x, y, color)); } function updateParticles() { if (particles.length == 0) { return; } context.clearRect(0, 0, width, height); // Update for (let i = 0; i < particles.length; i++) { particles[i].update(context); } // Remove dead particles for (let i = particles.length - 1; i >= 0; i--) { if (particles[i].lifeSpan < 0) { particles.splice(i, 1); } } if (particles.length == 0) { context.clearRect(0, 0, width, height); } } function loop() { updateParticles(); animationFrame = requestAnimationFrame(loop); } function destroy() { canvas.remove(); cancelAnimationFrame(animationFrame); element.removeEventListener("mousemove", onMouseMove); element.removeEventListener("touchmove", onTouchMove); element.removeEventListener("touchstart", onTouchMove); window.addEventListener("resize", onWindowResize); }; function Particle(x, y, canvasItem) { const lifeSpan = Math.floor(Math.random() * 30 + 60); this.initialLifeSpan = lifeSpan; // this.lifeSpan = lifeSpan; //ms this.velocity = { x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2), y: Math.random() * 0.7 + 0.9, }; this.position = { x: x, y: y }; this.canv = canvasItem; this.update = function (context) { this.position.x += this.velocity.x; this.position.y += this.velocity.y; this.lifeSpan--; this.velocity.y += 0.02; const scale = Math.max(this.lifeSpan / this.initialLifeSpan, 0); context.drawImage( this.canv, this.position.x - (this.canv.width / 2) * scale, this.position.y - this.canv.height / 2, this.canv.width * scale, this.canv.height * scale ); }; } init(); return { destroy: destroy } } )();