Vue Directive for Continuous List Scrolling Animation with Pause on Interaction
This article explains how to create a reusable Vue directive that animates a list scrolling continuously using requestAnimationFrame, allows pausing on mouse click, and lets users adjust the scroll speed, with a complete code example and detailed implementation notes.
Background and Requirements In large-screen projects, many lists often need continuous scrolling animations, leading to duplicated and hard‑to‑maintain code when speed or other parameters must be changed. The required features are: continuous up‑and‑down scrolling, pause on mouse click, resume on mouse leave, and adjustable scroll speed.
Demo An animated GIF demonstrates the scrolling effect, and the full implementation can be viewed at https://code.juejin.cn/pen/7400691681794162728 .
Explanation
Uses requestAnimationFrame to drive the animation and the timeStamp callback to control speed.
Encapsulated in a Vue directive; the mounted hook defines a variable isUsing to track whether the user is interacting (click, wheel, mouseenter/leave) and adds event listeners to pause or resume the animation accordingly.
Some Thoughts requestAnimationFrame is preferred over setTimeout or setInterval for data‑visualization animations because it synchronizes with the browser’s refresh cycle, offering better stability, interactivity, smoothness, and ease of use, and enjoys broad browser support. Fallbacks to setTimeout / setInterval can be added for legacy browsers.
Core Code
{
arr: {},
mounted(el, binding = { value: 200 }, vnode, prevVnode) {
const gap = 1000 / 45; // frame gap, approx 60fps => 1000/60
if (window.requestAnimationFrame) {
let isUsing = false;
// mouse leave – resume animation
el.addEventListener('mouseleave', () => {
isUsing && scrollAnimation();
isUsing = false;
}, { passive: true });
// wheel – pause animation
el.addEventListener('wheel', () => { isUsing = true; }, { passive: true });
// click – pause animation
el.addEventListener('click', () => { isUsing = true; }, { passive: true });
const scrollAnimation = () => {
if (window.requestAnimationFrame) {
const animationFun = (timeStamp, preTimeStamp = 0, diff = 0) => {
if (isUsing) return;
const currentDiff = preTimeStamp === 0 ? 0 : timeStamp - preTimeStamp;
const n_diff = currentDiff + diff;
if (n_diff < gap) {
window.requestAnimationFrame((_timeStamp) => animationFun(_timeStamp, timeStamp, n_diff));
return;
}
const scrollTop = el.scrollTop;
const clientHeight = el.clientHeight;
const scrollHeight = el.scrollHeight;
if (scrollTop + clientHeight < scrollHeight) {
el.scrollTop = scrollTop + 1;
window.requestAnimationFrame((_timeStamp) => animationFun(_timeStamp, timeStamp, 0));
} else {
el.scrollTop = 0;
scrollAnimation();
}
};
window.requestAnimationFrame(animationFun);
}
};
scrollAnimation();
} else {
// fallback implementation
}
},
unmounted(el, binding, vnode, prevVnode) {
// cleanup if needed
}
}Additional Extensions The directive can accept extra parameters to manually control whether the list scrolls, and the scrolling animation can be replaced with other effects such as color changes, shape transformations, or displacement while keeping the same overall approach.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.