|
@@ -0,0 +1,241 @@
|
|
|
+// 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()
|
|
|
+ }
|