How to Use the Stretch Method for Dynamic Duration Changes in Anime.js
The stretch method in Anime.js allows you to dynamically rescale the total duration of running animations, timelines, or timers by recalculating internal timing values while preserving the current playback position.
The stretch method is a powerful runtime API in the juliangarnier/anime library that enables dynamic duration changes without destroying and recreating animation instances. This functionality is essential for building responsive animations that must adapt to user interactions, scroll positions, or changing viewport conditions while maintaining smooth playback continuity.
What Is the Stretch Method?
The stretch method is available on three core classes within the Anime.js architecture: JSAnimation, Timeline, and Timer. It accepts a single parameter—newDuration—and proportionally scales all internal timing properties to match the requested length. Unlike simply changing playback speed, stretch modifies the underlying duration values themselves, making it ideal for scenarios where you need to synchronize animation length with external data sources like audio tracks or scroll distances.
How Stretch Works Under the Hood
All three implementations of stretch follow a consistent algorithm defined across the source tree. When you call stretch(newDuration), the engine performs the following operations:
- Early termination check – If the normalized
newDurationequals the current duration, the method returns immediately to avoid unnecessary calculations. - Calculate time-scale factor – The engine computes
timeScale = newDuration / currentDuration. - Resize child components – Every child tween, animation, or timer has its timing values multiplied by the scale factor.
- Delegate to base class – The
Renderablebase class updates generic fields likedurationanditerationDuration, then returnsthisto enable method chaining.
Animation Stretching in src/animation/animation.js
In the JSAnimation class, the stretch(newDuration) method iterates through every Tween instance stored in the animation. For each tween, it rescales _updateDuration, _changeDuration, _currentTime, _startTime, and _absoluteStartTime by the computed time-scale factor. This ensures that the animation's easing curves and keyframe distributions remain proportional while occupying the new temporal space.
Timeline Stretching in src/timeline/timeline.js
The Timeline class implements stretch(newDuration) by recursively invoking stretch on every child animation or timer added to the timeline. Additionally, it updates the position of all labels stored on the timeline instance, multiplying their offsets by the same scale factor. This maintains the relative synchronization between sequenced animations and labelled markers, ensuring that tl.add({...}, '+=200') remains proportionally correct after stretching.
Timer Stretching in src/timer/timer.js
For the Timer class—which powers scroll-based and generic timekeeping—stretch(newDuration) rescales internal offsets including _offset, _delay, and _loopDelay. It also adjusts the timer's duration and iterationDuration fields. This implementation is particularly important for scroll-linked animations where the "duration" might represent a scroll distance in pixels that changes based on viewport resizing.
Practical Examples
Stretching a Simple Animation
Create a basic animation and dynamically slow it down mid-playback:
// Create an animation that moves a box over 1 second
const boxAnim = anime({
targets: '#box',
translateX: 300,
duration: 1000,
easing: 'linear',
autoplay: true
});
// After 400ms, double the remaining duration
setTimeout(() => {
boxAnim.stretch(2000); // Total duration becomes 2s, animation slows down
}, 400);
The internal timeScale calculates as 2000 / 1000 = 2. All tween timings multiply by this factor, causing the box to continue moving at half speed while preserving its current position.
Rescaling a Timeline
Adjust the total duration of a complex sequence without breaking synchronization:
const tl = anime.timeline({ autoplay: true });
tl.add({
targets: '.dot',
translateY: [-50, 0],
duration: 500,
easing: 'easeOutQuad'
})
.add({
targets: '.dot',
opacity: [0, 1],
duration: 800,
offset: '-=200' // Overlap with previous animation
});
// After 500ms, expand the whole timeline to 3 seconds total
setTimeout(() => {
tl.stretch(3000);
}, 500);
Both child animations and their relative offsets scale proportionally. The 200ms overlap becomes a 600ms overlap after the stretch, maintaining the sequence's rhythmic structure.
Adjusting Timer Duration Dynamically
Use stretch with scroll-based or responsive timers:
const timer = anime.createTimer({
duration: 2000,
autoplay: true,
update: anim => console.log(`Progress: ${anim.progress}%`)
});
// Responsive adjustment: user resizes scroll container
function onScrollRangeChanged(newRangeMs) {
timer.stretch(newRangeMs);
}
The timer rescales its internal _offset, _delay, and _loopDelay values, ensuring that progress calculations remain accurate relative to the new duration.
Method Chaining
All stretch implementations return this, enabling fluent API usage:
anime({
targets: '.ball',
translateX: 400,
duration: 1200
})
.stretch(1800) // Slow down to 1.8s total
.pause() // Pause immediately
.play(); // Resume with new timing
Edge Cases and Considerations
Zero Duration Handling – When newDuration approaches or equals the internal minValue (used for setter animations), the timer forces its duration to minValue and treats the instance as an instant setter. This prevents division-by-zero errors in the time-scale calculation.
Floating-Point Precision – After scaling, tween durations are normalized using the internal normalizeTime function to minimize accumulated floating-point errors during long-running animations.
Label Synchronization – Only Timeline.stretch updates label positions. If you manually store external references to timeline offsets, you must recalculate them independently or rely on the built-in label system.
Summary
- The
stretch(newDuration)method dynamically rescales animation duration without restarting playback or losing progress. - It is implemented across three core classes:
JSAnimation(src/animation/animation.js),Timeline(src/timeline/timeline.js), andTimer(src/timer/timer.js). - The algorithm calculates a time-scale factor (
newDuration / currentDuration) and applies it to all child tweens, offsets, delays, and labels. - Method chaining is supported, allowing
anim.stretch(2000).pause().play()syntax. - Edge cases include zero-duration handling (setter animations) and floating-point normalization to prevent timing drift.
Frequently Asked Questions
Can I use stretch on an animation that has already completed?
Yes, you can call stretch on a completed animation, but it will only affect the duration values. To see the effect, you would need to restart the animation using .restart() or seek to a new position. The stretch method itself does not trigger playback; it only modifies the timing structure.
Does stretching an animation change its easing curve?
No, the easing function remains unchanged. The stretch method scales the duration and timing values (start times, delays, offsets) but preserves the interpolation method defined in the easing property. The animation will simply take longer or shorter to complete while maintaining the same acceleration profile.
How does stretch affect timeline labels?
When you call stretch on a Timeline instance, the method automatically scales all registered label positions by the same time-scale factor. For example, if you have a label at 1000ms and stretch the timeline from 2000ms to 4000ms, the label moves to 2000ms. This ensures that relative positioning using labels remains synchronized after duration changes.
Is there a performance penalty for calling stretch frequently?
The stretch method involves iterating through all child tweens or animations and recalculating timing values, which is an O(n) operation where n is the number of active tweens. For simple animations with few properties, this is negligible. However, for complex timelines with hundreds of child animations, calling stretch inside a high-frequency event handler (like mousemove or scroll) could impact performance. For such cases, consider throttling the calls or using the Timer class specifically designed for scroll-linked duration changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →