Jump to content

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


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. Hi @eliphino and welcome to the GreenSock forums! First, thank you for being a Club GreenSock member and supporting GreenSock! 🥳 I think this approach works better, since you are also adding the event listener to the window object on every loop: window.addEventListener("mousemove", e => { gsap.to(targets, { duration: 0.35, x: e.pageX, y: e.pageY, ease: "none", overwrite: "auto", stagger: 0.035, }); }); Also I'd recommend you to use less elements, since adding a bunch of them tends to look a bit off, but I'll leave that to your better judgement and the needs of your project. Let us know if you have more questions. Happy Tweening!
  2. If you want to overwrite the default scroll behavior of the browser you'll need to have a clear goal with it and know what you're doing. If you do not have this or you are new to coding I would not recommend doing this. ScrollTrigger taps in the to default scroll behavior. If you have some other element you want to use as the scroll container you can define a scroller (see the docs https://greensock.com/docs/v3/Plugins/ScrollTrigger) String | Element - By default, the scroller is the viewport itself, but if you'd like to add a ScrollTrigger to a scrollable <div>, for example, just define that as the scroller. You can use selector text like "#elementID" or the element itself. If you instead just want to watch for the scroll event you can also use the Observer plugin https://greensock.com/docs/v3/Plugins/Observer Instead of showing the issue you're having can you maybe explain what your goal is? If we know what it is you want to do it will be easier to help you with an appropriate solution.
  3. Hi @Rodrigo, Thank you for your detailed answer! All clear. I finally managed to do it but without using the snap functionality, cause I needed to scroll up and down. The solution was based on another example found on the forum by @ZachSaucier (the codepen : https://codepen.io/GreenSock/pen/NWxNEwY , and the thread: Here is the (a) solution : let panels = gsap.utils.toArray('.block-scroll'), scrollTween; function goToSection(i) { scrollTween = gsap.to(window, { scrollTo: {y: i * innerHeight, autoKill: false}, duration: 1, ease: Power3. easeOut, onComplete: () => scrollTween = null, overwrite: true }); } panels.forEach((panel, i) => { ScrollTrigger.create({ //markers: true, trigger: panel, start: 'top 75%', end: 'top 25%', onEnter: self => { self.isActive && !scrollTween && goToSection(i+1); console.log(panel); }, onEnterBack: self => { self.isActive && !scrollTween && goToSection(i); } }); }); Anyway, thanks for your quick answer.
  4. Hi all, i have a timeline animation where i add one object with a yoyo animation to the scene. But in the middle of the yoyo i want to overwrite, but the yoyo repeat animation is still going afterwards... I thought i can do it with overwrite, which works, but after the overwrite is finished the previous yoyo continues ... tl_scene_1 is the timeline animation object and female_02 an asset on the stage which is added next to other elements which are moving. tl_scene_1.to(female_02, { duration: 5, y: '-=200', repeat:3, yoyo: true, yoyoEase:true, ease: 'power2.out'}, 14); tl_scene_1.to(female_02, { duration: 1, y: '+=200', repeat: 0, yoyo: false, ease: 'power2.out', overwrite: 'auto' }, 16); I tried also to use killTweensOf to stop it, but than the relation to the timeline is completely removed. So i guessed with a simple overwrite i can keep the whole animation in the timeline, without removing a tweening object, only because i want to stop them. Any ideas, how to solve this. Peter
  5. @Rodrigo - I truly appreciate your detailed explanation. Everything you said makes sense and speaks to the core of the issue that I was facing. I was not aware of the overwrite property, so that was super helpful as well. I believe I'll be able to come up with a good solution based on your feedback. Thanks again for your time and knowledge!!
  6. Hi, Your setup is a bit convoluted IMHO and you are clearly running into overwrite issues. I think this approach is better and a bit more flexible: https://codepen.io/GreenSock/pen/oNyGLgv Granted, due to the fact that you are using two different blur animations in different situations makes it a bit difficult to find a flexible and dynamic solution, but at least for three elements this works. Let us know if you have more questions. Happy Tweening!
  7. Ha!, well this is not the case, by defining an extra ScrollTrigger object within a tween you'll define a second ScrollTrigger that has nothing to do with your timeline and will just overwrite what you'll try to do within the ScrollTrigger in your timeline. So don't do that! Removing the ScrollTrigger in your tween will fix your issue. https://codepen.io/mvaneijgen/pen/zYadQYP?editors=0010
  8. Hi, Scrolltrigger version 3.11.3 (and probably older versions too) save the history.scrollRestoration value at the beginning and it seems it's not possibile to overwrite that value later: _scrollRestoration = _win.history.scrollRestoration || "auto"; Then the stored value is applied again every clearScrollMemory call: _isString(scrollRestoration) && (_win.history.scrollRestoration = _scrollRestoration = scrollRestoration); Even setting history.scrollRestoration to manual at the beginning of my script, due to Webpack bundle, the Scrolltrigger code is extecuted before mine. In my case I need to set the scrollRestoration to manual because I use Barba.js Is there a way to do it? Am I missing something?
  9. Hey, I'm new to GSAP and absolutely loving it! I'm currently working on my first commercial Gatsby React site and while experimenting with the useLayoutEffect hook I came across an issue. managed to rectify it and now I was just wondering if I could get a little context. When I started setting up my animations I was nesting each animation into a separate useLayoutEffect hook like so: const h2Ref = useRef(null); const contentRef = useRef(null); useLayoutEffect(() => { const el = h2Ref.current; gsap.set(el, { y: -30, opacity: 0 }); gsap.to(el, { y:0, opacity:1, delay:.25 }); gsap.to(el, { y: -50, opacity: 0, immediateRender: false, overwrite: 'auto', scrollTrigger: { trigger: el, start: "top+=100px center", scrub: .5, } }); }, []) useLayoutEffect(() => { const el2 = contentRef.current; gsap.set(el2, { y: -30, opacity: 0 }); gsap.to(el2, { y:0, opacity:1, delay:.25 }); gsap.to(el2, { y: -30, opacity: 0, immediateRender: false, overwrite: 'auto', scrollTrigger: { trigger: el2, start: "top+=100px center", scrub: .5, } }); }, []) Everything works fine just like this. As an experiment, I had a shift around and dropped several animations into a single useLayoutEffect hook to see if I could clean up my code a little bit. Like this: const h2Ref = useRef(null); const contentRef = useRef(null); useLayoutEffect(() => { const el = h2Ref.current; gsap.set(el, { y: -30, opacity: 0 }); gsap.to(el, { y:0, opacity:1, delay:.25 }); gsap.to(el, { y: -50, opacity: 0, immediateRender: false, overwrite: 'auto', scrollTrigger: { trigger: el, start: "top+=100px center", scrub: .5, } }); const el2 = contentRef.current; gsap.set(el2, { y: -30, opacity: 0 }); gsap.to(el2, { y:0, opacity:1, delay:.25 }); gsap.to(el2, { y: -30, opacity: 0, immediateRender: false, overwrite: 'auto', scrollTrigger: { trigger: el2, start: "top+=100px center", scrub: .5, } }); }, []) When I did that, I was ending up with masses of white space appearing at the bottom of the page whenever I changed to a new route. So really my question is more out of curiosity, why did this happen? Is this something to do with how hooks work or how GSAP works? Just hoping to get a little enlightened Many thanks, and thanks for such an awesome animation tool!
  10. That would create significant logic problems. Trust me - a feature like that would be a disaster. Imagine you've got a "power4.out" ease on a tween and then halfway through you call reverse() and you want it to use a "power2.in" ease in reverse. Well at 50%, the interpolation would be in a completely different place, so you'd suddenly see a jump. For example, let's say you're tweening element.x from 0 to 100. With a power4.out tween, 50% might render element.x at 94.342, but a power2.in may render it at 29.851. So at the moment of reversing, element.x would JUMP from 94.342 to 29.851. GSAP already allows for the yoyo phase of an ease to be different and that's possible because it only switches when it is all the way complete (thus the eases would render identically). See the yoyoEase property. The most common way of dealing with the situation you described where you have a menu opening animation that you don't want a true reverse on (because you're CHANGING the ease) is to dynamically create it at that time. Don't pre-bake everything into a single timeline. Sorta like: function open() { gsap.to(..., {x: 100, ease: "power2", overwrite: true}) // and all the other parts... } function close() { gsap.to(..., {x: 0, ease: "power2", overwrite: true}); // and all the other parts... } I hope that helps.
  11. @Isla that is correct, bootstrap will apply a lot of CSS to your layout. You either have to overwrite that or write your own logic for this particular block of content. It's really important how you build your layout if you want to animate it and with bootstrap this is not something you get great control over, because it will imply that you want everything underneath each other on mobile.
  12. No, it's not a GSAP bug - it's a problem with your code: you keep adding more and more event listeners to the same element each time you navigate. So for example, if you go to 3 pages and then you roll over the "Fixed button", it will call 3 event handlers, firing 3 identical animations that are all fighting for control of the same properties of the same element. The best way to solve this is for you to not add a bunch of the same event handlers. Make sure you removeEventListener() when you leave the page, or add conditional logic to not add a new event handler when there's already one added. Alternatively, you could set overwrite: true or overwrite: "auto" on the tween(s) but even though that'd technically prevent conflicts, it's just masking the fundamental problem in your code. It's bad for performance to just keep stacking up more and more event handlers like that. If your user visits 30 pages, you really don't want a rollover to fire of 30 different handlers, creating 30 new animations and overwriting 29 of them. Does that clear things up?
  13. Dear GSAP community, I’ve a question about an ‘overwrite’ feature and some timeline time aspects. If I set an animated parameter without changing the time, it works pretty well! Example: https://codepen.io/belyanskii/pen/vYLBZQE But once I am trying not only to set (or change) an animated parameter but to set delay and duration, overwriting doesn’t seem to occur: https://codepen.io/belyanskii/pen/XWXrgQy Is there a way to overwrite the whole animation w/o clearing or deleting the tweens/timelines?
  14. Hello everyone, I'm doing an animation with a big overlay on page so I'm using the timeline of TimelineMax but I have issue when I'm using .className. To be brief, when I'm using className, it remove all classes on my element and I don't understand why it overwrite it all. Should I write it differently to keep existing classes ? Or is it possible to add an overwriting setting for .className ? This is a codePen that I simplify to troubleshoot : https://codepen.io/FrenchCooder/pen/ZEQpWJe Thanks in advance
  15. I was looking at this reference link here: // Set overwrite on a tween gsap.to(".line", { x: 200, overwrite: true }); // Set overwrite globally for all tweens gsap.defaults({ overwrite: true }); // Set overwrite for all tweens in a timeline const tl = gsap.timeline({ defaults: { overwrite: true } }); Maybe I need to set the overwrite in a difference place than a the begining of the tween. Basically seems like the timelines are conflicting each other, and not terminating the movements of the previous timeline. There are other problems , but it is better to keep them in a separate thread.
  16. GreenSock

    Animation break

    That's because of two problems in your code: You're creating an entirely new timeline on every click and you didn't kill() the old one or set overwrite: true on your tweens, so you've got conflicting tweens (multiple running that are fighting for control of the same property of the same elements). You've created a logic problem by using .from() tweens. Remember that .from() tweens use the CURRENT value as the destination. So let's say opacity starts at 1 and you do a .from(...{opacity: 1}). That would grab the current value (0) as the destination and IMMEDIATELY set opacity to 1 and then animate back to the destination (0). Great. But what if you click again quickly and opacity is at 0.98 when you create ANOTHER .from(...{opacity: 1}) tween? That would take the CURRENT value (0.98 in this case) and use it as the destination, thus your final animation would go from 1 to 0.98 and stop! Again, this is a logic problem in your code, not a bug in GSAP. There are many, many approaches you could take to solve this. Here is one where you just create your timeline up front once, and then simply toggle the playback direction via timeScale() on each click. I'm playing it 50% faster when closing: https://codepen.io/GreenSock/pen/oNqBjoK?editors=0010 Another solution would be to avoid .from tweens. Instead, you could gsap.set() the initial values and then animate to() the destination ones. And make sure you set overwrite: true on your tweens or it may be easier to just use a variable to remember the previous timeline so you can just .kill() it before creating the new one to avoid conflicts. Good luck!
  17. Hi! I'm sorry, but I have some issue with mouseleave, in demo as you can see image with overlay is not dissapearing, if I make tl.reverse() on mouseleave everything is fine, but I need my timeline to hide everything immediately. What am I doing wrong?
  18. Welcome to the forums, @Olly Grunt. Yeah, that's not a bug - that's just a logic issue on your code. You've got overwrite set to "auto", so each tween will wait to execute any overwriting until the very first time it renders at which point it will look for any other CURRENTLY ACTIVE tweens of the same properties of the same targets and kill just those parts. But you've set up your staggers and delays in a way that makes it pretty easy to create a scenario where the old/stale tweens haven't even started yet when the new tweens render for the first time, so overwriting doesn't occur. Simple solution: set overwrite: true instead of overwrite: "auto". When you set overwrite to true, that IMMEDIATELY kills all animations of the same targets regardless of which properties are getting animated or if those other animations have started yet. There are other solutions too, like you could create a variable to store the current animation (onEnter or onLeave, whichever was last to fire) and simply .kill() that before you create your new tween. That's basically like a manual overwrite system Good luck!
  19. Hey is this a bug or a misunderstood feature. I have issue with overwrite. In this demo if we make {overwrite:true} The timeLineId 'action' should override the timeLineId 'pre' only at 3 seconde no ? And if we make {overwrite:false} The timeLine 'pre' will continue in background and make big spike after 'action'. https://codepen.io/djmisterjon/pen/VwwJeJQ would it be possible technically to make override only act when the child timeLine execute has 3 seconds? thanks for help or suggest.
  20. Yep, @akapowl is exactly right, and I'd follow his advice. If you really need to create the tweens dynamically each time (which can be beneficial sometimes like if you want them to pick up from the current state, but since you're using mostly .fromTo() tweens that doesn't matter), you could set overwrite: true or overwrite: "auto" on your tweens to tell them to find and kill any currently-animating ones of the same elements. https://codepen.io/GreenSock/pen/dydBMXE?editors=0010 Also, I'd strongly recommend always setting transforms via GSAP and use the shortcuts like scale, x, y, rotation, etc. Those are faster and they avoid the ambiguities that can come with using a "transform" value. See: Happy tweening!
  21. I've been tinkering with it a bit, but I don't want to judge wether this is a bug or not. One thing I found is that if you do not set box-sizing: border-box inline (as in 1.: deactivate it via dev-tools or 2.: alternatively overwrite/clear it in a refresh callback and refresh the smoother instance) on the pin-spacer of the first ScrollTrigger it appears to work as intended by @thei. Maybe this can serve as a workaround for now and if this atually is a bug, help find the root of the problem. Works for me in both, the OPs codepen and @Cassie simplified demo. https://codepen.io/akapowl/pen/rNvJXbd https://codepen.io/akapowl/pen/xxjWxRV
  22. import React from "react"; import "./FollowImage.css" import gsap from "gsap"; import Image from "react-bootstrap/esm/Image"; function FollowImage (){ let FollowBox = "#Wrap .FollowBox"; gsap.set(FollowBox, { xPercent: 1, yPercent: 1, scale: 0 }); const MyDiv = document.getElementById('Wrap') function clamp(num, min, max) { return num <= min ? min : num >= max ? max : num } window.addEventListener("mousemove", (e) => { gsap.to(FollowBox, { duration: 0.5, overwrite: "auto", x: clamp(-100+e.clientX - e.target.offsetLeft,-50,150), y: clamp(-100+e.clientY- e.target.offsetTop,0,150), stagger: 0.15, ease: "none" }); }); return ( <div id="Wrap"> <Image id="FollowBox" className="FollowBox" src="1000x600(mykhaylo).png" alt="" /> <Image className="FollowBox" src="1000x600(music).png" alt="" /> </div> ) } export default FollowImage; This is my current code, so far I failed to translate it for codepen.
  23. 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' }) }); }) } })();
  24. Well, CodePen has been down for a while so I can't provide a forked demo but here's how I'd do the JS: let n = 15; let parallax = []; // we'll store the animations in here. let clamp = gsap.utils.clamp(0, 1); let currentX = 0; let snap = gsap.utils.pipe(gsap.utils.snap(450), gsap.utils.clamp(n * -450, 0)); // Set #slides width for draggable bounds gsap.set('#slides', {width:n*450}); // Populate slide boxes for (var i=0; i<n; i++){ var box = document.createElement('div'), img = new Image(), link = document.createElement('a'); gsap.set(box, { width:400, height:600, overflow:'hidden', position:'absolute', top:50, left:i*450, attr:{ class:'box b'+i }, background:'#333' }); gsap.set(img, { position:'absolute', left:-300,//-i*50, attr:{src:'https://picsum.photos/id/'+(i+10)+'/700/600/'} }); parallax[i] = gsap.to(img, {x: 300, ease: "none", paused: true}); gsap.set(link, { position:'absolute', textAlign:'center', width:105, height:70, paddingTop:'7px', top:490, left:-25, rotation:90, fontSize:'45px', color:'#000', background:'#fff', mixBlendMode:'lighten', textDecoration:'none', innerHTML:'<span style="font-size:20px">IMG </span>'+(i+1), attr:{ class:'imgLink', href:'https://picsum.photos/id/'+(i+10)+'/700/600/', target:'_blank' }, }); box.appendChild(img); box.appendChild(link); slides.appendChild(box); } // Make #slides draggable Draggable.create('#slides', { type:'x', bounds: {left: innerWidth/2, width:1}, zIndexBoost: false, onDrag:updateParallax, inertia: true, onRelease: function() { currentX = this.endX }, onThrowUpdate: updateParallax, snap: snap }) function updateParallax() { // parallax should start from the right edge of the screen and we know that the #slides starts with its left edge centered, thus we add innerWidth/2 let x = gsap.getProperty('#slides', 'x') + window.innerWidth / 2, // convert the position in the viewport (right edge of viewport to -400 because that's when the right edge of the element would go off-screen to the left) into a progress value where it's 0 at the right edge and 1 when it reaches the left edge normalize = gsap.utils.mapRange(window.innerWidth, -400, 0, 1); // apply the clamped value to each animation parallax.forEach((animation, i) => animation.progress(clamp(normalize(x + i * 450)))); } updateParallax(); // Update draggable bounds onResize window.addEventListener('resize', ()=>{ Draggable.get("#slides").applyBounds({left: innerWidth/2, width:1}) }); // Previous & next buttons $('#prev, #next').on('click', function(e) { let nextX = snap(currentX + (e.currentTarget.id === "next" ? -450 : 450)); if (nextX !== currentX) { gsap.to("#slides", {x: nextX, duration: 0.3, onUpdate: updateParallax, overwrite: true}) currentX = nextX; } }); $('#prev, #next').on('mouseenter', (e)=>{ gsap.to('#'+e.currentTarget.id + ' circle', {attr:{r:22}, ease:'expo', overwrite: true}) }); $('#prev, #next').on('mouseleave', (e)=>{ gsap.to('#'+e.currentTarget.id + ' circle', {attr:{r:20}, ease:'expo', overwrite: true}) }); // Img Link rollover/out behavior $('.imgLink').on('mouseenter', (e)=>{ gsap.to(e.currentTarget, {x:10, duration:0.3, ease:'power3', overwrite: true}) }); $('.imgLink').on('mouseleave', (e)=>{ gsap.to(e.currentTarget, {x:0, duration:0.3, ease:'power4.inOut', overwrite: true}) }); The general idea is: You only want the parallax effect to exist while each individual element is inside the viewport (not the entire movement of the #slides). I created a simple linear animation of x from 0 to 300 for EACH element. Paused. Dumped them into an Array. The updateParallax() function loops through each one and sets the progress() according to its position (which we know because they're 450px apart). It's all based on the viewport so that progress would be 0 when it's on the far right edge of the screen and 1 when the element's right edge reaches the left edge of the viewport. I also made the following improvements: I applied inertia with snapping directly on the draggable so it's super smooth and users can flick it. The logic in the next/previous buttons allows users to click the buttons quickly and it still works (instead of ignoring clicks while animation is running). Sorry, that's my pet peeve when the interface ignores user clicks. I hope that helps, Tom!
  25. Hello @F.D.C.H - welcome to the Forums. You could extend that helper function by a new variable - in the demo below I added startIndex = 0 up top where all the neccessary variables are being set up. In the draggables onPress you could set the startIndex = curIndex so at a later point (like on throwcomplete e.g.) you could check wether the value of the new curIndex is equal to the value of the startIndex. From outside the curIndex is accessible via tl.current() and I made the startIndex accessible via tl.dragStart() - but you could of course change the names to whatever you'd like. Of course the tl part here you would have to then exchange with whatever variable you are applying that horizontalLoop helper-function to. Then from outside you could do something like this. That's just an idea - I hope that will help loop.draggable.addEventListener("throwcomplete", log); function log() { console.log(loop.dragStart(), loop.current()) if( loop.dragStart() === loop.current() ) { console.log('Same Index') gsap.to(document.body, { backgroundColor: '#f00000', overwrite: 'auto' }) } else { console.log('New Index') gsap.to(document.body, { backgroundColor: '#111', overwrite: 'auto' }) } } https://codepen.io/akapowl/pen/KKQmPxB Edit: In this following pen the startIndex is also being set in the toIndex() function, so calling the check will also properly integrate with click on the buttons - and at this point the naming of tl.dragStart() doesn't seem appropriate anymore https://codepen.io/akapowl/pen/zYRwxBd