Jump to content

Search In
  • More options...
Find results that contain...
Find results in...

Search the Community

Showing results for 'overwrite'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


  • GreenSock Forums
    • GSAP
    • Banner Animation
    • Jobs & Freelance
  • Flash / ActionScript Archive
    • GSAP (Flash)
    • Loading (Flash)
    • TransformManager (Flash)

Product Groups

  • Club GreenSock
  • TransformManager
  • Supercharge


  • FAQ


  • Examples
  • Showcase


  • Products
  • Plugins


  • Learning Center
  • Blog


  • ScrollTrigger Demos


There are no results to display.

Find results in...

Find results that contain...

Date Created

  • Start


Last Updated

  • Start


Filter by number of...


  • Start



Personal Website



Company Website



  1. Not sure I fully understand your question, but you probably shouldn't be making an animation just for the initial state. Click Animation On Array Index i Overwrite Issue (codepen.io)
  2. we can play animations when we click on the Nav "Slides 1 2 3..." and i want it to start as Slides[0] index array element that is Slide A as already Played or Finished animation, and then we can click on Nav slides to play the animation the issue im having is, i want when i click on Slide 2 3 4 or 5 it should Overwrite gsap.set property Slides[0] by GSAPSlideTL or in other words Slides[0] is not working when click animation to play GSAPSlideTL thank you
  3. Yeah, it's a fun challenge. Very doable, but I don't have time to build it all for you. One general idea is to treat the horizontal movement as one animation (wired to the scroll with a scrub number to smooth it out), Then... Create an individual tween for each element that handles the skew and scale to the maximum values. Let's say that's a scale of 1.5 and a skewY of 15 (or whatever). Now create a generic object with a property that'll handle the "power" (doesn't matter what you call it), like {power: 0}. This is just a value between 0 and 1 that controls how much of the zoom/skewY gets applied. The faster you scroll, the closer to 1 it gets. When you're not scrolling, it goes back to 0. In an onUpdate on your ScrollTrigger, animate that "power" value based on the velocity. So it'll always lag a little behind, depending how long your duration is on that tween. This gives it a nice smooth feel. Don't forget to overwrite: true on that tween so it doesn't stack up. In an onUpdate of that tween, loop through each of the scale/skewY tweens and adjust their progress based on the power and the proximity to the center. So if power is 1, the progress of the tween of the element at exactly center is 1. The tween that's a little further from the center may be 0.6, for example. You just use a multiplier. This is relatively efficient because you're just reusing one tween for each element and adjusting the progress accordingly. And there's a single tween doing all of the horizontal movement. I hope that helps! If you'd like our help to actually build it, contact us for a paid consulting estimate.
  4. Hello, We have a project built in Gatsby.js which is nearing completion and we're interested in hiring a freelancer to have a second look at a few issues that persist. We have a page made up of sections and each section consists of many ScrollTriggers. We have the individual animations looking quite good. We're attempting to also integrated the ScrollToPlugin in order to snap the user between sections, which also works, but when animating the 'window' between section it is extremely choppy. We've loaded the UMD distribution files, utilized several React hooks (useEffect, useLayoutEffect, useCallback). We are trying to overwrite all tweens, kill all other tweens. You name it, we've tried it --- the kicker is that it does work, almost beautifully, but as is, we cannot launch the site. We also have a side navigation which makes use of the AnchorLink module. We need to have this converted to ScrollToPlugin so that we can add an offset (currently the anchors break the animations and cause overlapping --- which is obvious, they are currently positioned where the other ScrollTriggers are positioned to pin). Lastly, we're having issues with elements becoming visible that should not be, when resizing, refreshing. We're invalidating the animations and killing all of the ScrollTriggers within our useEffects. This one is not so simple to describe without seeing it. We utilize Bitbucket so we could definitely give you access to the repository to have a look before you commit to anything. If you'd like to jump on a phone call or chat via Slack --- or --- whatever works for you, let me know. Even if you do not have the time to devote to the project and you think you know how to resolve these issues --- definitely message me because we can also just do a consultation. We're quite capable, but we've tried everything we could think of. We've also tried to post in the forum and have gotten lots of great help but the issues persists. For us to really resolve the issues we need to provide a Codesandbox, and we have when possible, but it's just not feasible to build a distilled down version of this; there are far too many moving parts. Timeframe: No timeframe, we can begin with a consultation. I would like to speak with you as soon as possible though! If we find that the scope is more than we can handle or you do have time to just jump in, you can estimate how much time you think will be required. Compensation: Name your price; hourly or fixed
  5. Yep, @OSUblake and @PointC are exactly right. One other option: set up your animations with ease: "none" and then you can animate the playhead with any ease you want with another tween: let tl = gsap.timeline({defaults: {ease: "none"}, paused: true}); tl.to(...) .to(...); // here's the magic: gsap.to(tl, {time: tl.duration(), ease: "power3.out"}); // then to go backwards with another ease: gsap.to(tl, {time: 0, ease: "power4.inOut", overwrite: true});
  6. Of course it helps. I agree that it is much better approach: better functionallity and more readable. After seeing this solution it seem obvious. I learned some interesting things like overwrite property. And some javascript basics: I thought this way of declaring the isOpen variable would put it in the global scope, but the variable belongs to the scope of each menu, just like box and items. Thank you much.
  7. Sure, in your onUpdate(), put this at the bottom: gsap.to(tl, {timeScale: 1, overwrite: true});
  8. I have these two animations, a box rotating and contact buttons popping onto the screen. ScrollTrigger.create({ trigger: "#topLetterTrigger", animation: gsap.fromTo("#topLetter", {rotateY: -180}, {duration: 2.2, rotateY: 0, ease: "back.out(1.7)"}), once: true }); gsap.set(".contact-icon", {scale: 0}); ScrollTrigger.batch(".contact-icon", { trigger: "#topLetterTrigger", interval: 3, batchMax: 4, once: true, onEnter: batch => gsap.to(batch, {delay: 0.5, duration: 0.8, scale: 1, stagger: {each: 0.25, grid: [1, 4]}, overwrite: true, ease: "back.out(1.7)"}).delay(1.4), }); The delay on the batch animation is supposed to make the animations end at roughly the same time. However it doesn't, it seems sometimes the second trigger doesn't fire, or it starts the second animation when the first animation has completed, so instead of completing together the second animation commences at the end of the 1st animation + 1.4 second delay. Note: is there an issue where you shouldn't have 2 animations given the same trigger: value? I think using a timeline and the ScrollTrigger-ing them both will make it more robust. Or there is some other better way?
  9. It'd definitely help to see a minimal demo so we can make sure we're addressing the real issue adequately, but here are some thoughts: If you've built a page in a way that requires that one ScrollTrigger-based animation finishes before another one starts (thus it looks weird if the user scrolls fast and they're both playing), you can force the previous one to completion with a callback: let anim1 = gsap.to(... { scrollTrigger: { onEnterBack: () => anim2.progress(0) } }); let anim2 = gsap.to(... { scrollTrigger: { onEnter: () => anim1.progress(1), onEnterBack: () => anim3.progress(0) } }); let anim3 = gsap.to(... { scrollTrigger: { onEnter: () => anim2.progress(1) } }); Overwriting Keep in mind that overwrite logic runs once: overwrite: true - immediately when the tween is created overwrite: "auto" when the tween renders for the first time And when overwriting occurs, it is permanent. So this is not ideal for what you're talking about because you don't actually want to kill...as in dead...permanently. You're just wanting the animation to finish right away, correct? That's why I suggested the code above. For the record, the reason overwriting occurs once (not every time the animation plays) is for performance reasons and because it's almost never helpful to run more than the first time. We're VERY performance-minded. If you're still struggling, please provide a minimal demo and we'd be happy to take a peek.
  10. In general, what do GSAP users have in their arsenal to mitigate animations overlapping when users are triggering multiple non-scrubbed ScrollTriggers and/or tweens while scrolling/moving through a site at various speeds. These are what we've come across: Ensure timelines/tweens do not clash (not always simple) --- however, if you're scrolling through several ScrollTriggers, even if you have a delay or long duration between ScrollTriggers, they can clash if the user scrolls too quickly. I don't think this is the case but it appears that tweens/and callbacks can be skipped if the user scrolls too quickly; we've tested this with .set() and .call(). Add OnEnter/OnLeave logic to check if another timeline is "tweening" and then kill the offending timeline gracefully. I'm not seeing any examples of this between multiple ScrollTriggers, but the logic is fairly straightforward although can be extremely convoluted. Set overwrite on your tweens/timelines. I don't think this applies to separate ScrollTriggers --- I've tried multiple ways and haven't seen any affect between ScrollTriggered timelines that clash. I guess how could it; they are separate timelines? Set overflow hidden on the scrolling container so that the user cannot scroll when a tween is occurring; use very sparingly and only in critical areas; best used with a custom scrollbar to remove visual anomalies. Are there any other ways to mitigate clashes between ScrollTriggers and tweens if a user scrolls/moves too quickly through a site? It's not only too quickly in one direction, but when they reserve direction is when we probably see the most clashing. Is there a default overwrite setting that will have an effect between ScrollTriggered timelines? Does the overwrite already do this and we're just not seeing it/using it correctly? Kind Regards, Nitro Interactive
  11. It doesn't look like you are animating the blur property. Look at the proxy object. There is no filter property. gsap.to(proxy, {blur: 0, duration: 0.8, ease: "power3", overwrite: true, onUpdate: () => filterSetter("blur(" + proxy.blur + "px)")});
  12. @OSUblake Thanks a lot! Generally this seems to work, but I have the issue that in my setup, it starts blurring on scrolling, but it does not seem to animate back to 0 when I stop. Here is the code that I currently have: let proxy = { blur: 0 }, filterSetter = gsap.quickSetter(".shape", "filter"), // fast clamp = gsap.utils.clamp(0, 40); ScrollTrigger.create({ onUpdate: (self) => { let blur = clamp(self.getVelocity() / -300); if (Math.abs(blur) > Math.abs(proxy.blur)) { proxy.blur = blur; gsap.to(proxy, {filter: "blur(0)", duration: 0.8, ease: "power3", overwrite: true, onUpdate: () => filterSetter("blur(" + proxy.blur + "px)")}); } } }); Any idea if there is something wrong maybe? Thanks a lot!
  13. Try adding wrapper around your element, and separate your animations. You could also try adding overwrite: "auto" to your animation.
  14. Everything seems to be working fine. You're not scrolling, so there's that... const roll1 = this.rollText(".rollingText", {duration: 10}); gsap.to([roll1], {timeScale: direction * -1, overwrite: true}); this.direction doesn't exists. And your animation has an ease on it. Look at the original. vars = vars || {}; vars.ease || (vars.ease = "none");
  15. I have a long page with multiple scroll based simple animations, some of them use the same class name to run different places. At some point it seems the trigger position gets off for all the page and animations at the bottom of the screen are happening in the wrong place, If I open console in the browser the animations are back at the correct position. (function () { gsap.registerPlugin(ScrollTrigger); gsap.defaults({ease: "power4.out"}); ScrollTrigger.matchMedia({ // desktop "(min-width: 800px)": function () { mySplitText = new SplitText(".corona-hero-section h2", {type: "words,chars"}); chars = mySplitText.chars; //an array of all the divs that wrap each character gsap.set(".corona-hero-section h2", {perspective: 400}); var tl = gsap.timeline(); tl .from(".logo", {duration: 2, autoAlpha: 0, y: -60, stagger: 0.3}, 1) .from(chars, { duration: 0.8, opacity: 0, scale: 0, y: 80, rotationX: 180, transformOrigin: "0% 50% -50", ease: "back", stagger: 0.01 }, "+=0"); tl.restart(); scheneChange(5, 140, '.viewer', '.scene', false, 'bottom bottom'); scheneChange(4, 138, '.viewer2', '.scene2', false, 'bottom bottom'); scheneChange(1, 122, '.viewer4', '.scene4', false, 'bottom bottom'); scheneChange(3, 146, '.viewer5', '.scene5', false, 'bottom bottom'); scheneChange(2, 139, '.viewer6', '.scene6', false, 'bottom bottom'); animateImageReveal(); animateStaggerIcons('.icon-list', '.icon-list__item'); animateStaggerIcons('.icon-num', '.icon-num__item'); animateStaggerIcons('.icon-top', '.icon-top__item'); aminateStaggerLi('.map-wrap__item'); aminateStaggerLi('.program-list-wrap li'); }, // mobile "(max-width: 799px)": function () { // Icons and text animation gsap.utils.toArray('.icon-list__item').forEach((section) => { const tls = gsap.timeline({ scrollTrigger: { trigger: section, scrub: true, start: "top center", // markers: true, end: "+=20%", }, }); const delay = 0.5; const textIcon = section.querySelectorAll(".icon-list__icon"); const textTitle = section.querySelectorAll(".icon-list__title"); const textText = section.querySelector(".icon-list__text"); const textNum = section.querySelector(".icon-list__num"); tls .from(textNum, {duration: 3, autoAlpha: 0, y: -60, stagger: delay}, 0) .from(textIcon, {duration: 1, autoAlpha: 0, y: -60, stagger: delay}, 0.3) .from(textTitle, {duration: 1, autoAlpha: 0, y: -60, stagger: delay}, 0.6) .from(textText, {duration: 1, autoAlpha: 0, y: -60, stagger: delay}, 0.9) }); scheneChange(5, 140, '.viewer', '.scene'); scheneChange(4, 138, '.viewer2', '.scene2'); scheneChange(1, 122, '.viewer4', '.scene4', false); scheneChange(3, 146, '.viewer5', '.scene5'); scheneChange(2, 139, '.viewer6', '.scene6', false); scheneImageReveal(); animateIconNum(); scheneChange(1, 163.5, '.viewer3', '.scene3'); }, "all": function () { animateTitleSection(); morphImg('.scene3'); } }); function scheneChange(frames, offset, classToAnimate, action, pin = true, start = "center center") { var frame_count = frames, offset_value = offset; gsap.to(classToAnimate, { backgroundPosition: (-offset_value * frame_count * 2) + "px 50%", ease: "steps(" + frame_count + ")", // use a stepped ease for the sprite sheet scrollTrigger: { trigger: action, start: start, end: "+=" + (frame_count * offset_value), pin: pin, // markers: true, scrub: true, } }); } function morphImg(trigger) { const tls3 = gsap.timeline({ scrollTrigger: { trigger: trigger, scrub: true, start: "top center", // markers: true, end: "+=40%", }, }); tls3 .to('#inIsrael', {duration: 2, fill: "#9191a0", morphSVG: "#inMaoz"}, 0) .to('#israelText', {duration: 2, morphSVG: "#maozText"}, 0) .to('#israelUnderText', {duration: 2, morphSVG: "#maozUnderText"}, 0.2) } function animateIconNum() { const sections = gsap.utils.toArray('.icon-num__item'); sections.forEach((section) => { const tls = gsap.timeline({ scrollTrigger: { trigger: section, scrub: true, start: "top center", // markers: true, end: "+=20%", }, }); const delay = 0.5; const textIcon = section.querySelectorAll(".icon-num__icon"); const textTitle = section.querySelectorAll(".icon-num__title"); const textText = section.querySelector(".icon-num__text"); // const textNum = section.querySelector(".icon-list__num"); tls .from(textText, {duration: 3, autoAlpha: 0, y: -60, stagger: delay}, 0) .from(textIcon, {duration: 1, autoAlpha: 0, y: -60, stagger: delay}, 0.3) .from(textTitle, {duration: 1, autoAlpha: 0, y: -60, stagger: delay}, 0.6) // .from(textText, {duration: 1, autoAlpha: 0, y: -60, stagger: delay}, 0.9) }); } function animateTitleSection() { const sections = gsap.utils.toArray(".corona-header"); sections.forEach((section) => { const splitTimeline = gsap.timeline({ scrollTrigger: { trigger: section, scrub: true, end: "+=70%", // onToggle: self => gsap.to(".split-text", {opacity: self.isActive ? 1 : 0}), toggleActions: "restart pause restart none", //markers: true } }); const split = new SplitText(section); splitTimeline .to(section, {duration: 1, backgroundColor: '#fff', ease: "none"}, 0) .to(section, {duration: 1, backgroundColor: '#222241', ease: "none"}, 1) .from(split.chars, { duration: 2, opacity: 0, x: "random(-500, 500)", y: "random(-500, 500)", z: "random(-500, 500)", scale: .1, yoyo: true, stagger: 0.02 }); }); } function animateImageReveal() { let revealContainers = gsap.utils.toArray(".reveal"); revealContainers.forEach((container) => { let tlImage = gsap.timeline({ scrollTrigger: { trigger: container, scrub: true, start: "top 100%", end: "bottom 70%", } }); tlImage.from(container, {duration: 0.3, autoAlpha: 0, y: -60, stagger: 0.3}, 1); }); } function scheneImageReveal() { let revealContainers = gsap.utils.toArray(".reveal"); revealContainers.forEach((container) => { let image = container.querySelector('img'); let tlImage = gsap.timeline({ scrollTrigger: { trigger: container, start: "20% 70%", toggleActions: "restart none none reset", } }); tlImage.set(container, {autoAlpha: 1}); tlImage.from(container, 1.5, { xPercent: -100, ease: Power2.out }); tlImage.from(image, 1.5, { xPercent: 100, // scale: 0.7, delay: -1.5, ease: Power2.out }); }); } function aminateStaggerLi(trigger) { gsap.set(trigger, {y: 100}); ScrollTrigger.batch(trigger, { onEnter: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: {each: 0.15, grid: [1, 3]}, overwrite: true}), onLeave: batch => gsap.set(batch, {opacity: 0, y: -100, overwrite: true}), onEnterBack: batch => gsap.to(batch, {opacity: 1, y: 0, stagger: 0.15, overwrite: true}), onLeaveBack: batch => gsap.set(batch, {opacity: 0, y: 100, overwrite: true}) }); ScrollTrigger.addEventListener("refreshInit", () => gsap.set(trigger, {y: 0})); } function animateStaggerIcons(list, listItem) { gsap.utils.toArray(list).forEach(section => { const elems = section.querySelectorAll(listItem); // Set things up gsap.set(elems, {y: 50, opacity: 0}); ScrollTrigger.create({ trigger: section, start: 'top 60%', onEnter: () => gsap.to(elems, { y: 0, opacity: 1, duration: 1, stagger: 0.2, delay: 0.3, ease: 'power3.out', overwrite: 'auto' }), onLeaveBack: () => gsap.to(elems, { y: 50, opacity: 0, duration: 1, stagger: 0.2, delay: 0.3, ease: 'power3.out', overwrite: 'auto' }) }); }) } })();
  16. Hi everyone, Sorry to revive this not so old topic, I have a similar problem but with React hooks, Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. I'm pretty new to React and I'm not sure what I can do to clean the gsap process. I'm using gsap in two useEffects to drive simultaneously a countdown (in seconds going down) and a loading bar (in percent going up) I tried the following : ///// COUNTDOWN ///// const [countdown, setCountdown] = useState(props.seconds); const proxyCount = useRef({ countdown }); useEffect(() => { gsap.to(proxyCount.current, { countdown: 0, duration: props.seconds, ease: "linear", overwrite: "auto", onUpdate: () => { setCountdown(proxyCount.current.countdown); } }); return function cleanup() { gsap.killTweensOf(proxyCount, "countdown"); }; }, [setCountdown]); ///// PERCENT ///// const [percent, setPercent] = useState(0); const proxy = useRef({ percent }); useEffect(() => { gsap.to(proxy.current, { percent: 100, duration: props.seconds, ease: "linear", overwrite: "auto", onUpdate: () => { setPercent(proxy.current.percent); } }); return function cleanup() { gsap.killTweensOf(proxy, "percent"); }; }, [setPercent]); I thought that would be it but gsap.killTweensOf, either with or without the 2nd argument ( "countdown" / "percent" ) doesn't seem to fix it. I'm not using timelines yet so I wondered if anyone has an alternative to the t1.kill() here. Thanks in advance ! Julien
  17. You can tie the progress in to values from a range slider like this. https://codepen.io/cassie-codes/pen/a7fd24fd8d7fd6bfdc25f866bf3c00b5?editors=1011 Doing both scroll and a slider will definitely be possible but you'll have to work out what to do when the slider and scroll conflict. Which values overwrite which?
  18. @mikel that's because you keep creating new "action" timelines that are conflicting (bad). The last one created always "wins" because it renders last, but the others are still rendering and stacking up. When you set the timeScale() to 0 on the latest one, of course that one doesn't keep rendering because it's essentially paused, thus the LAST one you created is getting rendered. So it makes it appear as though the timeScale(0) didn't work but it actually did. The problem is the old/conflicting ones that were never properly killed or overwritten. Those are what continue to animate. Also, there's no such thing as "overwrite: true" on a timeline (that's just for tweens because those have "targets").
  19. You might want to look into the "overwrite" special property: overwrite: true immediately kills all animations of the same targets, regardless of what properties they're affecting. overwrite: "auto" only kills the individual parts of other active tweens of the same targets that are affecting the same properties. But to answer your original question most directly, I assume you're looking for gsap.killTweensOf(). And you can kill() any animation. Tweens and Timelines both have that method. There are actually quite a few ways to do what you're asking. If you still need some help, please provide a minimal demo so that we can see the context and quickly show you how to tweak the code. Happy tweening!
  20. Thanks for the detailed explanation @OSUblake, I will go with overwrite: "auto" in this case then, as I indeed need to avoid this weird stuff, but also have other animations (of other properties) running on the same target.
  21. Technically it doesn't. You just see the changes from the newest animation. Once the old animation runs it course, it will be removed, so in that sense you really don't need to worry about it. This would only cause a problem if the durations are different, like here. If you quickly hover on and off the circles, it's going to do some weird stuff. https://codepen.io/osublake/pen/24b9ba7b0403dc58f3493c6cf1410c9e The solution is to use overwrite: true. This will immediately kill the previous animation. https://codepen.io/osublake/pen/331b8647c24abb889eec9fb9af2655a3
  22. Hi! This is a strange one. I assume it's because the tweens in the third timeline are conflicting with the second timeline but I can't quite pin it down, and as you say - it only occurs when you scroll quickly. I tried overwrite true and some other things, but no luck. It's annoying to suggest a fix without a reason, so apologies. But if I were in your position I would handle the section pinning in one timeline and the dot animation in another - then there aren't conflicts (however they're arising and however strange...) https://codepen.io/cassie-codes/pen/89e49c2af76bc0dca02749e75ce54dee?editors=0110
  23. I tweaked the helper function so that toIndex() will always go in the shortest direction: /* This helper function makes a group of elements animate along the x-axis in a seamless, responsive loop. Features: - Uses xPercent so that even if the widths change (like if the window gets resized), it should still work in most cases. - When each item animates to the left or right enough, it will loop back to the other side - Optionally pass in a config object with values like "speed" (default: 1, which travels at roughly 100 pixels per second), paused (boolean), and repeat. - The returned timeline will have the following methods added to it: - next() - animates to the next element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc. - previous() - animates to the previous element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc. - toIndex() - pass in a zero-based index value of the element that it should animate to, and optionally pass in a vars object to control duration, easing, etc. Always goes in the shortest direction - current() - returns the current index (if an animation is in-progress, it reflects the final index) - times - an Array of the times on the timeline where each element hits the "starting" spot. There's also a label added accordingly, so "label1" is when the 2nd element reaches the start. */ function horizontalLoop(items, config) { items = gsap.utils.toArray(items); config = config || {}; let tl = gsap.timeline({repeat: config.repeat, paused: config.paused, defaults: {ease: "none"}}), length = items.length, startX = items[0].offsetLeft, times = [], widths = [], xPercents = [], curIndex = 0, pixelsPerSecond = (config.speed || 1) * 100, snap = config.snap === false ? v => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural totalWidth, curX, distanceToStart, distanceToLoop, item, i; gsap.set(items, { // convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster. xPercent: (i, el) => { let w = widths[i] = parseFloat(gsap.getProperty(el, "width", "px")); xPercents[i] = snap(parseFloat(gsap.getProperty(el, "x", "px")) / w * 100 + gsap.getProperty(el, "xPercent")); return xPercents[i]; } }); gsap.set(items, {x: 0}); totalWidth = items[length-1].offsetLeft + xPercents[length-1] / 100 * widths[length-1] - startX + items[length-1].offsetWidth * gsap.getProperty(items[length-1], "scaleX") for (i = 0; i < length; i++) { item = items[i]; curX = xPercents[i] / 100 * widths[i]; distanceToStart = item.offsetLeft + curX - startX; distanceToLoop = distanceToStart + widths[i] * gsap.getProperty(item, "scaleX"); tl.to(item, {xPercent: snap((curX - distanceToLoop) / widths[i] * 100), duration: distanceToLoop / pixelsPerSecond}, 0) .fromTo(item, {xPercent: snap((curX - distanceToLoop + totalWidth) / widths[i] * 100)}, {xPercent: xPercents[i], duration: (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond, immediateRender: false}, distanceToLoop / pixelsPerSecond) .add("label" + i, distanceToStart / pixelsPerSecond); times[i] = distanceToStart / pixelsPerSecond; } function toIndex(index, vars) { vars = vars || {}; (Math.abs(index - curIndex) > length / 2) && (index += index > curIndex ? -length : length); // always go in the shortest direction let newIndex = gsap.utils.wrap(0, length, index), time = times[newIndex]; if (time > tl.time() !== index > curIndex) { // if we're wrapping the timeline's playhead, make the proper adjustments vars.modifiers = {time: gsap.utils.wrap(0, tl.duration())}; time += tl.duration() * (index > curIndex ? 1 : -1); } curIndex = newIndex; vars.overwrite = true; return tl.tweenTo(time, vars); } tl.next = vars => toIndex(curIndex+1, vars); tl.previous = vars => toIndex(curIndex-1, vars); tl.current = () => curIndex; tl.toIndex = (index, vars) => toIndex(index, vars); tl.times = times; return tl; } Here's a fork with a slightly different color scheme https://codepen.io/GreenSock/pen/ZELPxWW?editors=0010 Better?
  24. Hi, In the animation, overwrite: true is not working with mask animation, works fine on other normal tweens. Works fine once animations are finished. Thanks for your help, always appreciated.