[Bug]: scrollNext() / scrollPrev() does not reset scrollVelocity, causing bounce when called during active animation
Description
When scrollNext() (or scrollPrev()) is called while a previous scroll animation is still running, the internal scrollVelocity from that animation is not cleared. The new animation then adds to the existing velocity:
// ScrollBody.seek()
scrollVelocity += displacement / scrollDuration;
scrollVelocity *= scrollFriction;
location.add(scrollVelocity);And in goTo():
engine.scrollBody.useBaseFriction().useDuration(options.duration);
engine.scrollTo.index(index, direction);useBaseFriction() resets friction but does not reset scrollVelocity. The leftover velocity from the previous animation stacks on top of the new displacement / scrollDuration term, pushing the carousel past the target snap point. Embla then corrects back to the snap — visually a noticeable bounce/overshoot.
Steps to reproduce
- Create a vertical snap carousel (
axis: 'y',loop: false,skipSnaps: false) - Call
scrollNext()programmatically in rapid succession (e.g. on each wheel tick with a short debounce) - Observe the bounce/overshoot on each advance
This does not happen with swipe/drag, because drag goes through the proper velocity pipeline and the mouseup release lets the physics settle naturally before any new gesture starts.
Workaround
Zero out velocity manually before calling scrollNext() by setting scrollDuration = 0 for one animation frame (which triggers the isInstant branch that sets scrollVelocity = 0), then calling scrollNext() on the next frame:
embla.internalEngine().scrollBody.useDuration(0);
requestAnimationFrame(() => embla.scrollNext());This works but relies on internalEngine() which is not a stable public API.
Expected behaviour
scrollNext() / scrollPrev() should reset scrollVelocity to 0 before starting the new animation, so programmatic calls produce a clean snap with no overshoot regardless of whether an animation is currently running.
Embla version
9.0.0-rc02
Source: davidjerleke/embla-carousel