-1

I am trying to create a "smooth" animation by "lerping" the difference between an element that follows the mouse and the mouse position.

This is just for demo purposes, the issue happens with scrolling events and other kinds of animations too.

In the original code, a requestAnimationFrame "loop" starts when JS is loaded, and never stops. And it feels to me like this is not the optimal way of doing it, but the animation works perfectly with this method.

Here is the original demo: https://codepen.io/ReGGae/pen/NQKENZ?editors=0010

let target = 0
let current = 0

const ease = 0.075
const element = document.querySelector('.js-lerp-me')

window.addEventListener('mousemove', (e) => {
  target = e.clientX // Stores the mouse (X) positon
})

function animate() {
  current += ((target - current) * ease) // This is where the magic happens

  element.style.transform = `translate3d(${current}px, 0, 0)`

  requestAnimationFrame(animate)
}

animate() // Runs 60 times per second

(This example kindly provided to me by Jesper Landberg in order to explain to me lerping)

In my code, I try to optimize it by running the requestAnimationFrame "loop" only when the mousemove event is fired and stop it when its nearly finished(nearly because it can never finish with lerping).

My version: https://codepen.io/samuelgozi/pen/QeWzWy?editors=0010

let target = 0
let current = 0

const ease = 0.075
const element = document.querySelector('.js-lerp-me')

// Checks that both numbers are within a range.
// The default range is 1 because the units passed to this are pixels,
// and with lerping the last pixel never really arrives at the target.
function nearlyEqual(a, b, targetDiff = 1) {
    return Math.abs(a - b) < targetDiff;
}

window.addEventListener('mousemove', (e) => {
  target = e.clientX // Stores the mouse (X) positon
  animate()
})

function animate() {
  current += ((target - current) * ease)

  element.style.transform = `translate3d(${current}px, 0, 0)`

  if (nearlyEqual(target, current)) return // If we are close enough to the target, then dont request another animation frame.

  requestAnimationFrame(animate)
}

As you can see in the demos, in my version it runs much faster, and feels less "eased", in other words the effect is lost. even if you bring down the ease multiplier down it still feels different.

Can someone please explain to me what is going on?

Samuel E.
  • 2,320
  • 2
  • 26
  • 31

1 Answers1

1

The original only runs one loop. and I think it's because you start a new animate every time you there is a mousemove event and then several will run at the same time so I modified your code a bit to only start a new animation until the current animation loop has stopped.

let target = 0
let current = 0
let animating = false

const ease = 0.075
const element = document.querySelector('.js-lerp-me')


// Checks that both numbers are within a range.
// The default range is 1 because the units passed to this are pixels,
// and with lerping the last pixel never really arrives at the target.
function nearlyEqual(a, b, targetDiff = 1) {
    return Math.abs(a - b) < targetDiff;
}

window.addEventListener('mousemove', (e) => {
  target = e.clientX // Stores the mouse (X) positon
  if (!animating) {
    animate()
    animating = true
  }
})

function animate() {
  current += ((target - current) * ease) // This is where the magic happens

  element.style.transform = `translate3d(${current}px, 0, 0)`

  if (nearlyEqual(target, current)) {
    animating = false
    return
  }

  requestAnimationFrame(animate)
}
Fredrik
  • 431
  • 3
  • 7