From aa2bf68e2de09a7edafc88c0a49e67b3bc51d734 Mon Sep 17 00:00:00 2001 From: blek Date: Fri, 22 Dec 2023 01:47:10 +1000 Subject: [PATCH] add a funny cursor effect --- static/script/cursor.js | 212 ++++++++++++++++++++++++++++++++++++ static/script/cursor.min.js | 2 + templates/base.html | 2 + 3 files changed, 216 insertions(+) create mode 100644 static/script/cursor.js create mode 100644 static/script/cursor.min.js diff --git a/static/script/cursor.js b/static/script/cursor.js new file mode 100644 index 0000000..41c7647 --- /dev/null +++ b/static/script/cursor.js @@ -0,0 +1,212 @@ +(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 + } + } +)(); \ No newline at end of file diff --git a/static/script/cursor.min.js b/static/script/cursor.min.js new file mode 100644 index 0000000..bd6a859 --- /dev/null +++ b/static/script/cursor.min.js @@ -0,0 +1,2 @@ +// https://github.com/tholman/cursor-effects/blob/master/src/fairyDustCursor.js +!function(){let t=["#D61C59","#E7D84B","#1B8798"],e=document.body,n=window.innerWidth,i=window.innerHeight;const o={x:n/2,y:n/2},s={x:n/2,y:n/2},h=[],a=[];let c,r,d;const l="*",u=window.matchMedia("(prefers-reduced-motion: reduce)");function f(){if(u.matches)return console.log("This browser has prefers reduced motion turned on, so the cursor did not init"),!1;c=document.createElement("canvas"),r=c.getContext("2d"),c.style.top="0px",c.style.left="0px",c.style.pointerEvents="none",c.style.position="fixed",e.appendChild(c),c.width=n,c.height=i,r.font="21px serif",r.textBaseline="middle",r.textAlign="center",t.forEach(t=>{let e=r.measureText(l),n=document.createElement("canvas"),i=n.getContext("2d");n.width=e.width,n.height=e.actualBoundingBoxAscent+e.actualBoundingBoxDescent,i.fillStyle=t,i.textAlign="center",i.font="21px serif",i.textBaseline="middle",i.fillText(l,n.width/2,e.actualBoundingBoxAscent),a.push(n)}),e.addEventListener("mousemove",p),e.addEventListener("touchmove",v,{passive:!0}),e.addEventListener("touchstart",v,{passive:!0}),window.addEventListener("resize",m),w()}function m(t){n=window.innerWidth,i=window.innerHeight,c.width=n,c.height=i}function v(t){if(t.touches.length>0)for(let e=0;e{o.x=e.clientX,o.y=e.clientY,Math.hypot(o.x-s.x,o.y-s.y)>1.5&&(x(o.x,o.y,a[Math.floor(Math.random()*t.length)]),s.x=o.x,s.y=o.y)})}function x(t,e,n){h.push(new function(t,e,n){const i=Math.floor(30*Math.random()+60);this.initialLifeSpan=i,this.lifeSpan=i,this.velocity={x:(Math.random()<.5?-1:1)*(Math.random()/2),y:.7*Math.random()+.9},this.position={x:t,y:e},this.canv=n,this.update=function(t){this.position.x+=this.velocity.x,this.position.y+=this.velocity.y,this.lifeSpan--,this.velocity.y+=.02;const e=Math.max(this.lifeSpan/this.initialLifeSpan,0);t.drawImage(this.canv,this.position.x-this.canv.width/2*e,this.position.y-this.canv.height/2,this.canv.width*e,this.canv.height*e)}}(t,e,n))}function w(){!function(){if(0!=h.length){r.clearRect(0,0,n,i);for(let t=0;t=0;t--)h[t].lifeSpan<0&&h.splice(t,1);0==h.length&&r.clearRect(0,0,n,i)}}(),d=requestAnimationFrame(w)}function g(){c.remove(),cancelAnimationFrame(d),e.removeEventListener("mousemove",p),e.removeEventListener("touchmove",v),e.removeEventListener("touchstart",v),window.addEventListener("resize",m)}u.onchange=(()=>{u.matches?g():f()}),f()}(); \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 6cff80d..8b84520 100644 --- a/templates/base.html +++ b/templates/base.html @@ -38,5 +38,7 @@

+ + \ No newline at end of file