Last active
October 4, 2024 08:44
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
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
Your scroller container should have these properties.