Skip to content

Instantly share code, notes, and snippets.

@jonsecchis
Last active October 4, 2024 08:44
Show Gist options
  • Save jonsecchis/18c4d5de214467f5f94cffb0b1534c36 to your computer and use it in GitHub Desktop.
Save jonsecchis/18c4d5de214467f5f94cffb0b1534c36 to your computer and use it in GitHub Desktop.
Vanilla JS routine handlers for mimicking iOS inertia scrolling, accounting for dynamic force and friction.
const scroller = "YOUR SCROLLER ELEMENT HERE";
const minVelocity = 0.25;
const minForce = 1;
const maxForce = 2;
const minFriction = 0.93;
const maxFriction = 0.98;
let lastY = 0;
let lastT = 0;
let force = minForce;
let velocity = 0;
let friction = minFriction;
let direction = 0;
let lastDirection = 0;
let isMoving = false;
let inertiaFrame = null;
const ontouchstart = (e) => {
cancelAnimationFrame(inertiaFrame);
lastY = e.touches[0].clientY;
lastT = performance.now();
force = isMoving ? lerp(velocity,minForce,maxForce) : minForce;
isMoving = false;
velocity = 0;
}
const ontouchmove = (e) => {
const currentY = e.touches[0].clientY;
const currentT = performance.now();
const deltaY = currentY - lastY;
const deltaT = Math.max(16, currentT - lastT);
direction = Math.sign(deltaY);
velocity = (deltaY / deltaT) * 16;
lastY = currentY;
lastT = currentT;
scroller.vtop = Math.round((scroller.vtop + deltaY) / 0.25) * 0.25;
requestAnimationFrame(() => scroller.style.setProperty("--vtop", `${scroller.vtop}px`));
}
const ontouchend = (e) => {
isMoving = Math.abs(velocity) >= minVelocity;
if (!isMoving) return;
force = direction === lastDirection ? force : minForce;
velocity *= force;
lastDirection = direction;
inertiaFrame = requestAnimationFrame(applyInertia);
}
const applyInertia = () => {
isMoving = Math.abs(velocity) >= minVelocity;
if (!isMoving) return;
friction = lerp(velocity,minFriction,maxFriction);
velocity *= friction;
scroller.vtop = Math.round((scroller.vtop + velocity) / 0.25) * 0.25;
scroller.style.setProperty("--vtop", `${scroller.vtop}px`);
inertiaFrame = requestAnimationFrame(applyInertia);
}
const lerp = (value, min, max) => {
const abs = Math.abs(value);
return min + (Math.min(abs, 100) / 100) * (max - min);
}
@jonsecchis
Copy link
Author

jonsecchis commented Sep 27, 2024

Your scroller container should have these properties.

#scroller {
  --vtop: 0px;
  transform: translateY(var(--vtop));
  will-change: transform;
}

@jonsecchis
Copy link
Author

Your scroller container should have these properties.

#scroller {
  --vtop: 0px;
  transform: translateY(var(--vtop));
  will-change: transform;
}

Actually, don't do this. See this chromium issue. It bites hard. It seems obvious now, but whatever you do DON'T ANIMATE CSS PROPERTIES. Use inline styles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment