// The springy emoji effect has been translated over from this old // code, to modern js & canvas // - http://www.yaldex.com/FSMessages/ElasticBullets.htm function springyEmojiCursor(options) { let emoji = (options && options.emoji) || "🤪" let hasWrapperEl = options && options.element let element = hasWrapperEl || document.body let nDots = 7 let DELTAT = 0.01 let SEGLEN = 10 let SPRINGK = 10 let MASS = 1 let GRAVITY = 50 let RESISTANCE = 10 let STOPVEL = 0.1 let STOPACC = 0.1 let DOTSIZE = 11 let BOUNCE = 0.7 let width = window.innerWidth let height = window.innerHeight let cursor = { x: width / 2, y: width / 2 } let particles = [] let canvas, context let emojiAsImage function init() { canvas = document.createElement("canvas") context = canvas.getContext("2d") canvas.style.top = "0px" canvas.style.left = "0px" canvas.style.pointerEvents = "none" if (hasWrapperEl) { canvas.style.position = "absolute" element.appendChild(canvas) canvas.width = element.clientWidth canvas.height = element.clientHeight } else { canvas.style.position = "fixed" document.body.appendChild(canvas) canvas.width = width canvas.height = height } // Save emoji as an image for performance context.font = "16px serif" context.textBaseline = "middle" context.textAlign = "center" let measurements = context.measureText(emoji) let bgCanvas = document.createElement("canvas") let bgContext = bgCanvas.getContext("2d") bgCanvas.width = measurements.width bgCanvas.height = measurements.actualBoundingBoxAscent * 2 bgContext.textAlign = "center" bgContext.font = "16px serif" bgContext.textBaseline = "middle" bgContext.fillText( emoji, bgCanvas.width / 2, measurements.actualBoundingBoxAscent ) emojiAsImage = bgCanvas let i = 0 for (i = 0; i < nDots; i++) { particles[i] = new Particle(emojiAsImage) } 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 if (hasWrapperEl) { canvas.width = element.clientWidth canvas.height = element.clientHeight } else { canvas.width = width canvas.height = height } } function onTouchMove(e) { if (e.touches.length > 0) { if (hasWrapperEl) { const boundingRect = element.getBoundingClientRect() cursor.x = e.touches[0].clientX - boundingRect.left cursor.y = e.touches[0].clientY - boundingRect.top } else { cursor.x = e.touches[0].clientX cursor.y = e.touches[0].clientY } } } function onMouseMove(e) { if (hasWrapperEl) { const boundingRect = element.getBoundingClientRect() cursor.x = e.clientX - boundingRect.left cursor.y = e.clientY - boundingRect.top } else { cursor.x = e.clientX cursor.y = e.clientY } } function updateParticles() { canvas.width = canvas.width // follow mouse particles[0].position.x = cursor.x particles[0].position.y = cursor.y // Start from 2nd dot for (i = 1; i < nDots; i++) { let spring = new vec(0, 0) if (i > 0) { springForce(i - 1, i, spring) } if (i < nDots - 1) { springForce(i + 1, i, spring) } let resist = new vec( -particles[i].velocity.x * RESISTANCE, -particles[i].velocity.y * RESISTANCE ) let accel = new vec( (spring.X + resist.X) / MASS, (spring.Y + resist.Y) / MASS + GRAVITY ) particles[i].velocity.x += DELTAT * accel.X particles[i].velocity.y += DELTAT * accel.Y if ( Math.abs(particles[i].velocity.x) < STOPVEL && Math.abs(particles[i].velocity.y) < STOPVEL && Math.abs(accel.X) < STOPACC && Math.abs(accel.Y) < STOPACC ) { particles[i].velocity.x = 0 particles[i].velocity.y = 0 } particles[i].position.x += particles[i].velocity.x particles[i].position.y += particles[i].velocity.y let height, width height = canvas.clientHeight width = canvas.clientWidth if (particles[i].position.y >= height - DOTSIZE - 1) { if (particles[i].velocity.y > 0) { particles[i].velocity.y = BOUNCE * -particles[i].velocity.y } particles[i].position.y = height - DOTSIZE - 1 } if (particles[i].position.x >= width - DOTSIZE) { if (particles[i].velocity.x > 0) { particles[i].velocity.x = BOUNCE * -particles[i].velocity.x } particles[i].position.x = width - DOTSIZE - 1 } if (particles[i].position.x < 0) { if (particles[i].velocity.x < 0) { particles[i].velocity.x = BOUNCE * -particles[i].velocity.x } particles[i].position.x = 0 } particles[i].draw(context) } } function loop() { updateParticles() requestAnimationFrame(loop) } function vec(X, Y) { this.X = X this.Y = Y } function springForce(i, j, spring) { let dx = particles[i].position.x - particles[j].position.x let dy = particles[i].position.y - particles[j].position.y let len = Math.sqrt(dx * dx + dy * dy) if (len > SEGLEN) { let springF = SPRINGK * (len - SEGLEN) spring.X += (dx / len) * springF spring.Y += (dy / len) * springF } } function Particle(canvasItem) { this.position = { x: cursor.x, y: cursor.y } this.velocity = { x: 0, y: 0, } this.canv = canvasItem this.draw = function(context) { context.drawImage( this.canv, this.position.x - this.canv.width / 2, this.position.y - this.canv.height / 2, this.canv.width, this.canv.height ) } } init() }