[BUG] onScroll autoplay animations aren't working with timelines after timeline sync.
Describe the bug
onScroll() events aren't firing the animation when inview just because it is inserted on a timeline.
Provide a minimal reproduction demo of the bug
the box we want to animate. in its own, its working:
const box = animate(utils.$(".box"), {
autoplay: onScroll({
debug:true,
enter: "bottom start",
leave: "start bottom",
sync: "resume pause reverse reset", //so when inview, it alternates
onEnter: () => {
console.log("FIRING AND THE ANIMATION ALSO PLAYS AFTER A FIRE ");
},
}),
x: "-2em",
ease: "inOutExpo",
});
but, if we put this inside of a timeline. we need to:
- explicitly tell the scrollObserver that we are targeting the current element we are animating.
- cant use
syncproperty inside autoplay, which means the animation can't alternate based on scroll events. (dont get confused of thealternateproperty with loop, thats not what i meant.)
putting the .box animation inside of a timeline then .sync() with other timelines:
const boxTimeline = createTimeline()
boxTimeline.add(utils.$(".box"), {
autoplay: onScroll({
debug: true,
target: utils.$(".box"),
enter: "bottom start",
leave: "start bottom",
sync: "resume pause reverse reset", //no longer alternates!
onEnter: () => {
console.log("FIRING BUT IT AINT PLAYING ");
},
}),
x: "-2em",
ease: "inOutExpo",
});
//master timeline -> main control
const masterTimeline = createTimeline();
masterTimeline
.sync(introTimeline)
.sync(heroTimeline, "1050")
.sync(boxTimeline) //with the intention that it only plays after all these timelines finished
Alternatives/Working Solutions
First option
single them out as a standalone animation, not a timeline. then you sync() them. this will play from start. maybe scrollObserver have nothing to do with sync(). i didnt know yet.
const box = animate(utils.$(".box"), {
autoplay: onScroll({
debug:true,
enter: "bottom start",
leave: "start bottom",
sync: "resume pause reverse reset", //so when inview, it alternates
onEnter: () => {
console.log("FIRING AND THE ANIMATION ALSO PLAYS AFTER A FIRE ");
},
}),
x: "-2em",
ease: "inOutExpo",
});
const masterTimeline = createTimeline();
masterTimeline
.sync(introTimeline)
.sync(heroTimeline, "1050")
.sync(box) //this will play EVEN IF the other timelines haven't finished. i have no idea why yet
Second option (what i use)
single them out as a standalone animation, not a timeline. then you call() them. this will play exclusively after the call() is initiated.
const masterTimeline = createTimeline();
masterTimeline.sync(introTimeline).sync(heroTimeline, "1050").call(() => { //it only creates this animation AFTER the whole timeline finished, sorted by the method chaining.
animate(utils.$(".box"), {
autoplay: onScroll({
debug: true,
enter: "bottom start",
leave: "start bottom",
sync: "resume pause reverse reset", //alternates!
onEnter: () => {
console.log("FIRING ONLY AFTER OTHER TIMELINES FINISHED, IT ALTERNATES TOO ");
},
}),
x: "-2em",
ease: "inOutExpo",
duration: 1000,
});
});
Conclusion
if this is actually intentional, the official docs didn't mention anything/show any example on having autoplay NOT as a createTimeline() property. the mindset is so that each animation is observed inside a single timeline.
from the official docs on using onScroll() on a timeline.
// Timeline
const circles = utils.$('.circle');
createTimeline({
alternate: true,
loop: true,
autoplay: onScroll({ //only on timeline level, not a single animate
target: circles[0],
container,
debug
})
})
.add(circles[2], { x: '9rem' })
.add(circles[1], { x: '9rem' })
.add(circles[0], { x: '9rem' });
also if this intentional for the best practice to be only declared on the timeline level, then it should be logical to have the target property to accept array/nodelist/htmlcollections. i was thinking like:
// Timeline
const circles = utils.$('.circle');
const box = utils.$('.box');
const tri = utils.$('.tri);
createTimeline({
alternate: true,
loop: true,
autoplay: onScroll({
target: [circles, box, tri], //something like this, now it make sense to have 1 master scrollObserver
container,
debug
})
})
.add(circles[2], { x: '9rem' })
.add(box, { x: '9rem' })
.add(tri, { x: '9rem' });
Thank You
i love anime.js, been using it for years! but i keep doing hacky things for some interactions to work natively so now i try to contribute
Source: juliangarnier/anime