Search the Community
Showing results for 'overwrite'.
-
Hi there, I'm using near latest npm greensock (3.11.4), and there seems to be a change in behaviour since version 2.0.1 and I'm not sure how to resolve it. Before I could have 3 items with infinitely repeating tween inside a timeline, but later I want to stop those tweens on a delay (so they don't stop at the same time), but I don't want to pause the timeline... so I'd add my tweens to a timeline like this: ``` // start rows anim this.spinTimeline = new TimelineLite({paused:true}); _.each(this.rows, (row, i) => { var slideTween = new TweenMax(row.sprite, 3, {x: row.offset, ease: Power0.easeNone, repeat: -1 }); this.spinTimeline.add(slideTween , "startRowT" + i, "-=0.1"); }); this.spinTimeline.play(); ``` then later I could stop them animating repeatedly by just calling a new tween on the row.sprite, with a slightly increasing delay on each, and the animations would transition smoothly from repeating x to landing on a specific point on the x axis. ``` stopRows() { _.each(this.rows, (row, i) => { TweenMax.to(row.sprite, 0.75, {x: row.offset, ease: Elastic.easeOut, delay: 0.75 * i, onComplete: this.animComplete.bind(this) }); }); } ``` Now with latest gsap versions I can't figure out how to recreate this. the repeating tween just keeps playing after the stopping tween finishes. If I pause the timeline first, it works but the repeating anims pause immediately.. If I use overwrite: true, then the repeating anims pause immediately (not when the stop anim starts after the delay). If I use timeline.killTweensOf(row.sprite) onStart, then it happens immediately, (not after the delay).. so i can't transition from one tween to the other anymore. My new code looks like this: ``` // start anim const tl = this.tl; this.rows.map((row, i) => { const offsetX = row.container.width / 2; tl.to(row.container, {x: offsetX, duration: 3, ease: 'none', repeat: -1}); }); tl.play(); // stop anim: this.rows.map((row, i) => { const toX = row.stopOffset; gsap.to(row.container, { duration: 1, x: toX , ease: 'elastic.out', delay: 0.75 * i, overwrite: true, // i'd expect this overwrite to happen after the delay, not immediately. onComplete: () => { //this.tl.killTweensOf(row.container); this.state = 'ready'; }, }); }); Edit: I found the problem, I needed to remove the duration from my new version and use t.killTweensOf in the onComplete (the line i had commented!).. that works as I want now!
-
Hi I've been struggling with the issue of conflicting timelines/tweens over and over again. Notice how in the example, the "more info" divs are consistently not appearing after resizing the browser window while the animation is playing (you'll have to open the example in a new tab to see the problem). https://codepen.io/ynamite/pen/jOeovBm I can't seem to revert a timeline/tween to it's initial state and reinitializing the timeline/animation after resizing the browser window. I've tried killing and/or clearing a timeline, I've tried spamming `overwrite: true` everywhere, I've tried debouncing the resize event etc. I'm at a wits end ... I've checked the forum but could only find the solutions I've already tried. Neither overwriting nor killing/clearing tweens/timelines seems to do the job, at least not in all cases. Sometimes it works and in other cases it just doesn't. It feels inconsistent and my code is starting to feel quite hacky. I'm probably missing a thing or doing something wrong. The example shown on the following page seems to exhibit the same behaviour I'm experiencing: Once you click on "move back" and then click on "restart" the animation seems to break. At least in the way that I understand it. By clicking "restart" I'd expect the element to move back to the right edge and roll back to the left (like it doesk on page load). Instead it just hangs and rolls on the left. Thanks!
-
control animation speed with scrollTrigger scrub? versus duration?
pravSab replied to jeanettable's topic in GSAP
@Cassie @GreenSock Thanks for the reply, however I am using 2 timelines with same pin value, as I need to implement the following scenarios 1. I need to scale out the image or video from starting to its final value - Timeline 1 with same pin value. - It's working perfectly ( imageTimelineAnimation in the below code) 2. The second timeline is for snap scrolling the other images / Videos while the component is pinned. - Timeline 2 - This also works. (snapScrollAnimation in the below code) however the pinning is working and animation also plays smooth, but it's leaving large space at the bottom for desktop, tablet, mobile. I am adding margin bottom to the main container to adjust the bottom spacing. but while doing snap scrolling until I reach the last element it never appears on the screen, I have given fixed height for the container as well but not helping. I want to make the next element of the container to be maintaining the exact bottom spacing which I want to but struggling to achieve that. Need your suggestion if you know anything I'm missing. The elements I'm making animations are inside a grid container - FYI I understand that it'll be tough to understand to provide any suggestions based on the explanation on this, but whatever I know I have posted here to seek help on this. I would like to tell you that it's a really great package, you guys are Rockstars. Keep rocking. This is my code for your reference, it's huge but it gives you overall picture of what I'm trying to do ( Starting Point InitializeAnimation ) import gsap from 'gsap'; import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'; export function getScrollY() { return window.pageYOffset != null ? window.pageYOffset : document.documentElement.scrollTop != null ? document.documentElement.scrollTop : document.body.scrollTop; } export function isLandscape() { return window?.matchMedia('(orientation: landscape)')?.matches; } export function detectTabletOrientation() { return isLandscape() && window.innerWidth < 1180 ? '10% 65%' : isLandscape() && window.innerWidth >= 1180 && window.innerWidth < 1400 ? '10% 80%' : '20% 50%'; } let currentCount = 0; // Text Area - START export const slidePrevNextText = (current, next) => { const currentText = gsap.fromTo( current, { autoAlpha: 1, duration: 1, overwrite: 'auto', paused: true, x: 0, }, { autoAlpha: 0, duration: 1, overwrite: 'auto', paused: true, x: '-200px', } ); const nextText = gsap.fromTo( next, { autoAlpha: 0, duration: 1, overwrite: 'auto', paused: true, x: 0, }, { autoAlpha: 1, duration: 1, overwrite: 'auto', paused: true, x: 0, } ); currentText.play('<'); nextText.play('>'); return [currentText, nextText]; }; export const slidePrevNextTextReverse = (current, next) => { const currentText = gsap.fromTo( current, { autoAlpha: 1, duration: 1, overwrite: 'auto', paused: true, x: 0, }, { autoAlpha: 0, duration: 1, overwrite: 'auto', paused: true, x: 0, } ); const nextText = gsap.fromTo( next, { autoAlpha: 0, duration: 1, overwrite: 'auto', paused: true, x: '-200px', }, { autoAlpha: 1, duration: 1, overwrite: 'auto', paused: true, x: 0, } ); currentText.play('<'); nextText.play('>'); return [currentText, nextText]; }; // Text Area - END // Initial Move out animation for Device asset and Optional Element const mainContainerTween = (id, isTablet, isMobile) => gsap.fromTo( `div#device-${id} .device-outline-border`, { duration: 2, opacity: 1, overwrite: 'auto', paused: true, stagger: 0.1, visibility: 'visible', x: 0, y: 0, yPercent: 0, }, { duration: 2, opacity: 1, overwrite: 'auto', paused: true, stagger: 0.1, visibility: 'visible', x: isTablet ? -135 : -225, y: !isTablet && !isMobile ? -50 : 0, yPercent: !isTablet ? -4 : 0, } ); const childContainerTween = (id, isTablet) => gsap.fromTo( `div#device-${id} div.child-container`, { bottom: '0', duration: 2, left: isTablet ? '9%' : '14%', opacity: 0, overwrite: 'auto', paused: true, position: 'relative', right: '0', stagger: 0.1, top: '-35%', visibility: 'hidden', x: 0, y: !isTablet ? -150 : 0, }, { bottom: '0', duration: 2, opacity: 1, overwrite: 'auto', paused: true, position: 'relative', right: '0', stagger: 0.1, visibility: 'visible', x: isTablet && window.innerWidth < 1024 ? 350 : isTablet && window.innerWidth >= 1024 ? 420 : 520, y: !isTablet ? -125 : -36, } ); const progressButtonTween = (id) => gsap.fromTo( `.progress-container--${id}`, { opacity: 0, overwrite: 'auto', paused: true, visibility: 'hidden', }, { opacity: 1, overwrite: 'auto', paused: true, visibility: 'visible', } ); const progressButtonPositionSet = (id, buttonStyles?) => gsap.set(`.progress-container--${id}`, { ...buttonStyles, }); const progressElementPosition = (id, styles) => gsap.set(`.progress-container--${id}`, { ...styles, }); const fullScreenTween = (id, isTablet?) => gsap.fromTo( `div#device-${id} .full-screen`, { duration: 0.6, opacity: 0, overflowY: isTablet ? 'hidden' : 'auto', visibility: 'hidden', }, { duration: 0.6, opacity: 1, overflowY: 'auto', visibility: 'visible', } ); const firstImageGradientTween = (id) => gsap.fromTo( `div#device-${id} div.asset-overlay`, { duration: 0.6, opacity: 1, paused: true, visibility: 'visible', }, { duration: 0.6, opacity: 0, paused: true, visibility: 'hidden', } ); export const scrollWindowToTween = (value, seconds?) => gsap.to(window, { duration: seconds ? seconds : 2.25, overwrite: 'auto', paused: true, scrollTo: { y: value, }, }); const firstTextAreaTween = (id, isMobile?, isTablet?) => gsap.fromTo( `.tarea--${id}:first-child`, { paused: true, y: 0, }, { paused: true, y: isMobile ? -10 : 0, } ); export const initialImageAnimation = (id, isTablet, isMobile) => { const dCards = gsap?.utils?.toArray(`div.asset--${id} div.asset-component`); const sCards = gsap?.utils?.toArray(`div.optional--${id}`); const [, snapTrigger] = ScrollTrigger.getAll(); return scrollWindowToTween(snapTrigger.start, 2.5) .play() .eventCallback('onStart', () => { firstImageGradientTween(id).play('<'); firstTextAreaTween(id, isMobile, isTablet).play('<'); gsap.to(`div#device-${id} div.drop-shadow`, { opacity: 1, transition: 'opacity 1s, visibility 1s', visibility: 'visible', }); }) .eventCallback('onComplete', () => { if (!isMobile) { if (secondaryCards.length > 0 && window.innerHeight > 719) { mainContainerTween(id, isTablet, isMobile).play('-=2.5'); childContainerTween(id, isTablet) .play('-=2.5') .eventCallback('onStart', () => { if (isTablet && window.innerWidth > 600 && window.innerWidth < 1024) { gsap.to(`div#device-${id} div.child-container`, { inset: window.innerWidth < 820 ? `-20% 0px 0px 2%` : `-20% 0px 0px 7%`, }); } }); } if (dCards.length > 1) { progressButtonTween(id).play('>'); } if (dCards.length >= 1) { fullScreenTween(id, isTablet).play('>'); } } }); }; export const reverseInitialImageAnimation = (id, isTablet, isMobile) => { const dCards = gsap?.utils?.toArray(`div.asset--${id} div.asset-component`); const sCards = gsap?.utils?.toArray(`div.optional--${id}`); const [imageTrigger] = ScrollTrigger.getAll(); return scrollWindowToTween(imageTrigger.start - 50, 2.5) .play() .eventCallback('onStart', () => { firstImageGradientTween(id).reverse('<'); firstTextAreaTween(id).reverse('<'); if (!isMobile) { if (sCards.length > 0) { mainContainerTween(id, isTablet, isMobile).reverse('-=1.5'); childContainerTween(id, isTablet).reverse('-=1.5'); gsap.to(`div#device-${id} div.drop-shadow`, { opacity: 0, transition: 'opacity 1s, visibility 1s', visibility: 'hidden', }); } if (dCards.length > 1) { progressButtonTween(id).reverse(0); } if (dCards.length >= 1) { fullScreenTween(id, isTablet).reverse(0); } } }); }; async function finishOnFastLeave( { isActive, progress, getVelocity, animation, direction }: ScrollTrigger, current: number, id: any ) { const dCards = gsap?.utils?.toArray(`div.parent-container div.asset--${id}`); const sCards = gsap?.utils?.toArray(`div.child-container div.optional--${id}`); const textElements = gsap?.utils?.toArray(`.tarea--${id}`); const progressValue = +progress.toPrecision(2) * 100; const length = dCards.length - 1; const index = Math.round(+(+(progressValue / 100).toPrecision(1) * length).toPrecision(1)); const currentCard: any = dCards[current]; const currentSecondaryCard: any = sCards[current]; const targetCard: any = dCards[index]; const targetSecondaryCard: any = sCards[index]; if (!isActive && Math.abs(getVelocity()) > 1000) { await animation.progress(progress === 1 ? 1 : 0).pause(); slideTween(targetCard, targetSecondaryCard) .play() .eventCallback('onStart', () => { slidePrevNextText(textElements[current], textElements[index]); const currentElements: any = textElements.filter((ele, i) => i !== index); gsap.set([...currentElements], { autoAlpha: 0, }); }) .eventCallback('onComplete', () => { slideTween(currentCard, currentSecondaryCard).reverse(); }); } } // first card Animation here export const firstImageAnimation = (id, setInViewport, isTablet, isMobile, theme) => { const dCards = gsap?.utils?.toArray(`div.asset--${id} div.asset-component`); imageTimelineAnimation = gsap.timeline({ ease: 'Power3.easeOut', paused: true, reversed: true, scrollTrigger: { end: (self) => !isTablet && !isMobile ? self.next().start - 10 : isMobile ? self.next().start : self.next().start - 50, fastScrollEnd: true, invalidateOnRefresh: true, onEnter: () => { setInViewport(true); }, onEnterBack: ({ isActive, animation }) => { if (isActive) { animation.scrollTrigger.vars.scrub = 2; reverseInitialImageAnimation(id, isTablet, isMobile); setInViewport(false); } }, onLeave: () => { if (dCards.length === 1) { setInViewport(false); } }, onLeaveBack: () => { firstImageGradientTween(id).reverse(); }, onToggle: async ({ isActive, getVelocity, direction, animation, progress }) => { if (!isActive && currentCount > 0 && direction === -1 && getVelocity() < 0) { await animation.progress(progress === 1 ? 1 : 0).pause(); gsap.set([`.asset--${id}:first-child`, `.optional--${id}:first-child`], { opacity: 1, visibility: 'visible', yPercent: 0, }); gsap.set([`.asset--${id}:not(:first-child)`, `.optional--${id}:not(:first-child)`], { opacity: 0, visibility: 'hidden', yPercent: 100, }); } }, pin: `.container--${id} div.ui-demo-container--${id}`, scrub: 2, start: () => (isMobile ? '5% 50%' : isTablet ? detectTabletOrientation() : '20% 55%'), trigger: `div.asset-container--${id} div.asset-${id}-0`, // markers: {startColor: 'blue', endColor: 'black'}, }, }); imageTimelineAnimation .fromTo( `div#device-${id} .transparent-border`, { duration: 0.5, opacity: 0.95, outline: 'none', }, { duration: 0.5, onStart: async (self) => { await initialImageAnimation(id, isTablet, isMobile); }, opacity: 1, outline: !isMobile && !isTablet ? '4px solid' : isMobile ? '1.72px solid' : '2px solid', } ) return imageTimelineAnimation; }; export const slideTween = (nextCard, nextSecondaryCard?) => gsap.fromTo( [nextCard, nextSecondaryCard], { autoAlpha: 0, duration: 1.25, paused: true, stagger: 0.1, yPercent: 100, }, { autoAlpha: 1, duration: 1.25, paused: true, stagger: 0.1, yPercent: 0, } ); export const onStartTweens = (currentIndex, nextIndex, textElements) => { slidePrevNextText(textElements[currentIndex], textElements[nextIndex]); const currentElements: any = textElements.filter((ele, i) => i !== currentIndex); gsap.set([...currentElements], { autoAlpha: 0, }); }; function checkIfAnyOverlap(rect1: any, rect2: any) { return !( rect1.right < rect2.left || rect1.left > rect2.right || rect1.bottom < rect2.top || rect1.top > rect2.bottom ); } export const checkForElementOverlap = (id) => { const element = document ?.querySelector(`section#${id} div.ui-demo-container--${id}`) ?.getBoundingClientRect(); const nextSiblingElement = document.querySelector(`section#${id}`)?.nextElementSibling?.getBoundingClientRect(); const mainElement = document.querySelector(`main`).getBoundingClientRect(); if ( (!!nextSiblingElement && checkIfAnyOverlap(element, nextSiblingElement)) || (!!mainElement && checkIfAnyOverlap(element, mainElement)) ) { return !!nextSiblingElement ? nextSiblingElement?.top - element?.bottom : mainElement?.bottom - element?.bottom; } else { return false; } }; export const handleBottomSpacing = (id, isTablet, isMobile) => { const element = document ?.querySelector(`section#${id} div.ui-demo-container--${id}`) ?.getBoundingClientRect(); const childContainer = document?.querySelector(`section#${id} div.child-container`)?.getBoundingClientRect(); const value = element?.bottom > childContainer?.bottom ? Math.round(element?.bottom - childContainer?.bottom) : Math.round(childContainer?.bottom - element?.bottom); if (!isMobile && !isTablet) { gsap.set(`section#${id}`, { marginBottom: element?.bottom > childContainer?.bottom ? (value > 0 ? 96 - value : 96 + value) : 96, }); } else if (!isMobile && isTablet) { gsap.set(`section#${id}`, { marginBottom: element?.bottom > childContainer?.bottom ? (value > 0 ? 64 - value : 64 + value) : 64, }); } }; export const goToCard = (progress: number, direction: number, index: number, currenIndex: number, nextCount: number, id: string) => { const dCards = gsap?.utils?.toArray(`div.parent-container div.asset--${id}`); const sCards = gsap?.utils?.toArray(`div.child-container div.optional--${id}`); const textElements = gsap?.utils?.toArray(`.tarea--${id}`); const currentCard: any = dCards[currenIndex]; const currentSecondaryCard: any = sCards[currenIndex]; const nextCard: any = dCards[nextCount]; const nextSecondaryCard: any = sCards[nextCount]; const targetCard: any = dCards[index]; const targetSecondaryCard: any = sCards[index]; if (index === currenIndex && direction === 1) { slideTween(nextCard, nextSecondaryCard) .play() .eventCallback('onStart', () => onStartTweens(index, nextCount, textElements)) .eventCallback('onComplete', () => { slideTween(currentCard, currentSecondaryCard).reverse(); }); } else if (index === currenIndex && direction === -1) { slideTween(currentCard, currentSecondaryCard).reverse(-0.5); slideTween(nextCard, nextSecondaryCard) .play('-=0.5') .eventCallback('onStart', () => onStartTweens(index, nextCount, textElements)); } else if (index > currenIndex) { slideTween(targetCard, targetSecondaryCard) .play() .eventCallback('onStart', () => onStartTweens(currenIndex, index, textElements)) .eventCallback('onComplete', () => { slideTween(currentCard, currentSecondaryCard).reverse(); }); } else if (index < currenIndex) { slideTween(targetCard, targetSecondaryCard) .play() .eventCallback('onStart', () => { const currentElements: any = textElements.filter((ele, i) => i !== currenIndex); gsap.set([...currentElements], { autoAlpha: 0, }); slidePrevNextTextReverse(textElements[currentCount], textElements[index]); }) .eventCallback('onComplete', () => { slideTween(currentCard, currentSecondaryCard).reverse(); }); } }; export const snapTimeline = (id, stateMethods) => { const { setActive, setInViewport, isTablet, isMobile } = stateMethods; const dCards = gsap?.utils?.toArray(`div.parent-container div.asset--${id}`); const sCards = gsap?.utils?.toArray(`div.child-container div.optional--${id}`); const textElements = gsap?.utils?.toArray(`.tarea--${id}`); if (dCards.length > 1) { const getEnd = () => { return isMobile ? 'bottom' : isTablet ? 'bottom top' : '+=5000'; }; snapScrollAnimation = gsap.timeline({ ease: 'Power3.easeOut', reversed: true, scrollTrigger: { end: () => getEnd(), endTrigger: `.ui-demo-container--${id} div.snap-last`, fastScrollEnd: true, invalidateOnRefresh: true, onEnterBack: () => { currentCount = dCards.length - 1; setActive(currentCount); setInViewport(true); }, onLeave: (self) => { currentCount = dCards.length - 1; setActive(currentCount); }, onLeaveBack: () => { currentCount = 0; setActive(0); setInViewport(true); }, onToggle: (self) => { finishOnFastLeave(self, dCards.length - 1, id); }, pin: `.container--${id} .ui-demo-container--${id}`, preventOverlaps: true, scrub: 2, // markers: true, snap: { duration: 1, ease: 'none', onComplete: ({ progress, direction }) => { const progressValue = +progress.toPrecision(2) * 100; const length = dCards.length - 1; const index = Math.round(+(+(progressValue / 100).toPrecision(1) * length).toPrecision(1)); currentCount = index; handleBottomSpacing(id, isMobile, isTablet); }, onStart: ({ progress, direction }) => { const progressValue = +progress.toPrecision(2) * 100; const length = dCards.length - 1; const index = Math.round(+(+(progressValue / 100).toPrecision(1) * length).toPrecision(1)); const nextCount = currentCount + 1 < dCards.length && direction === 1 ? currentCount + 1 : currentCount - 1 > 0 ? currentCount - 1 : 0; if (index > currentCount) { setActive(index); } else if (index < currentCount) { setActive(index); } else { setActive(nextCount); } goToCard(progress, direction, index, currentCount, nextCount, id); }, snapTo: 1 / (dCards?.length - 1), }, start: () => (isMobile ? 'top 9%' : isTablet ? 'top 4.5%' : 'top 5%'), trigger: `.ui-demo-container--${id} div.snap-first`, }, }); } return snapScrollAnimation; }; // tslint:disable-next-line: no-big-function export const initializeAnimation = async (id, stateMethods) => { const dCards = gsap?.utils?.toArray(`div.asset--${id} div.asset-component`); const secondaryCards = gsap?.utils?.toArray(`div.optional--${id}`); const { setInViewport, isTablet, isMobile, theme } = stateMethods; firstImageAnimation(id, setInViewport, isTablet, isMobile, theme); snapTimeline(id, stateMethods); const matchMediaRef: gsap.MatchMedia = gsap?.matchMedia(); const desktopTabletSet = () => { gsap.set(`.asset--${id}:first-child`, { opacity: 1, visibility: 'visible', yPercent: 0, }); gsap.set(`.asset--${id}:not(:first-child)`, { opacity: 0, visibility: 'hidden', yPercent: 100, }); gsap.set(`div#device-${id} div.asset-overlay`, { visibility: 'visible', opacity: 1 }); dCards.forEach((assetCard: HTMLDivElement, i) => { const secondaryCard: any = secondaryCards[i]; if (i === 0) { gsap.set(`div#device-${id} .outline-border`, { background: 'transparent', scale: isTablet ? 2 : 1.5, }); gsap.set(`div#device-${id} .transparent-border.${theme}`, { background: 'transparent', borderColor: 'transparent', borderRadius: '0px', }); gsap.set(`.tarea--${id}:first-child`, { opacity: 1, visibility: 'visible', x: 0, y: 0, }); gsap.set(secondaryCard, { opacity: 1, visibility: 'visible', yPercent: 0, }); } else { gsap.set(secondaryCard, { opacity: 0, visibility: 'hidden', yPercent: 100, }); } }); if (sCards.length === 0) { mainContainerTween(id, isTablet, isMobile).kill(); progressButtonTween(id).kill(); } if (dCards.length === 1) { progressButtonTween(id).kill(); } else if (dCards.length === 0) { fullScreenTween(id).kill(); progressButtonTween(id).kill(); } }; const mobileSet = () => { gsap.set(`.asset--${id}:first-child`, { opacity: 1, visibility: 'visible', yPercent: 0, }); gsap.set(`.asset--${id}:not(:first-child)`, { opacity: 0, visibility: 'hidden', yPercent: 100, }); gsap.set(`div#device-${id} div.asset-overlay`, { visibility: 'visible', opacity: 1 }); gsap.set( [`div#device-${id} div.child-container`, `div#device-${id} .full-screen`, `.progress-container--${id}`], { display: 'none' } ); childContainerTween(id, isTablet).kill(); fullScreenTween(id, isTablet).kill(); progressButtonTween(id).kill(); mainContainerTween(id, isTablet, isMobile).revert(); gsap.set(`div#device-${id} .outline-border`, { background: 'transparent', scale: 2, }); gsap.set(`div#device-${id} .transparent-border.${theme}`, { background: 'transparent', borderColor: 'transparent', borderRadius: '0px', }); gsap.set(`.tarea--${id}:first-child`, { opacity: 1, visibility: 'visible', x: 0, y: 0, }); }; matchMediaRef .add( [ '(min-width: 960px)', '(min-width: 1440px)', '(min-width: 1920px)', '(min-width: 768px) and (max-width: 1023px)', '(min-width: 768px) and (max-width: 1023px) and (orientation: landscape)', '(min-width: 1024px) and (max-width: 1180px) and (orientation: landscape)', ], () => desktopTabletSet() ) .add( [ '(min-width: 320px) and (max-width: 719px)', '(min-width: 320px) and (max-width: 719px) and (orientation: landscape)', '(max-height: 719px) and (orientation: landscape)', ], () => mobileSet() ); }; export let snapScrollAnimation: gsap.core.Timeline; export let imageTimelineAnimation: gsap.core.Timeline; -
Hi, The issue is that the calculations ScrollTrigger is making here when creating the linkST instances: let menu = gsap.utils.toArray(".nav a"); menu.forEach((a) => { let element = document.querySelector(a.getAttribute("href")), linkST = ScrollTrigger.create({ trigger: element, start: "top top" }); a.addEventListener("click", (e) => { e.preventDefault(); gsap.to(window, { duration: 1, scrollTo: linkST.start, overwrite: "auto" }); }); }); Are being affected by the ScrollTrigger instances created in the panels loop. Since you know that the home section is at the top of the document just use zero there: a.addEventListener("click", (e) => { e.preventDefault(); const target = e.target.getAttribute("href") === "#home" ? 0 : linkST.start; gsap.to(window, { duration: 1, scrollTo: target, overwrite: "auto", }); }); Finally is worth noticing that pinType : "sticky" is not an option. It's either "fixed" or "transform" pinType "fixed" | "transform" - by default, position: fixed is used for pinning only if the scroller is the <body>, otherwise transforms are used (because position: fixed won't work in various nested scenarios), but you can force ScrollTrigger to use position: fixed by setting pinType: "fixed". Typically this isn't necessary or helpful. Beware that if you set the CSS property will-change: transform, browsers treat it just like having a transform applied, breaking position: fixed elements (this is unrelated to ScrollTrigger/GSAP). Hopefully this helps. Happy Tweening!
-
Hello! Is it intended that the Promise object, which is returned by a Tween's .then() method, isn't terminated when the animation has been overwritten? At first I planned on using it in combination with the async/await syntax, which sometimes resulted in a permanent halt of the respective function. (In my tests the status of the Promise remains "pending" after the animation has been overwritten.) But even when I use it with a then-catch-structure, no clause is executed. In the end I promisified the GSAP animations myself: // for example function hide(elmnt, duration = 0) { return new Promise((resolve, reject) => { gsap.to(elmnt, { duration: duration, autoAlpha: 0, overwrite: 'auto', onComplete: resolve, onInterrupt: reject }); }); } I feel like this undermines the usefulness of the .then() method (at least for some usecases). Have a good one!
-
Hey, I have found a great and straightforward demo on the forum of repeat and reverse animation, which was created by @mikel. Here it is https://codepen.io/mikeK/pen/dyooKjJ But using the latest GSAP version, the reverse animation doesn’t work the same as in the 3.1.1 version. The animation doesn’t stop on mouseleave. My main goal is to have repeated animation on mouseenter. But on mouseleave, I need to reverse only once instead of reversing the whole repeats. I understand I can do that with tweens and overwrite properties on mouseleave, but I'm looking for a better solution with timeline. Can you help me, please? Below is the same animation with the latest GSAP version.
-
Hi @GreenSock, Thanks for pointing out my misunderstanding. It really helps me understand how the timeline and overwrite works! After building a tiny playground, I found my problem was not about overwriting but states. https://codepen.io/latteouka/pen/gOBmYxz I was trying to tween some "current" elements which need to be set again because "current index" have changed. That is why I was trying to find some solutions about overwriting conflicts. I also had some problems with tweening something before the last delay animation is not yet played. I am happy to stay with the default overwrite: false and more confident about tuning my page transition now. Thank you again for the help.
-
Hi @latteouka. Thanks for sharing. I don't have time to dig through all that code but I wanted to make sure I pointed out that this is not true: There are no such limits. But you shouldn't create multiple simultaneous tweens that are all trying to control the same property of the same element because obviously the same value can't go in a bunch of different directions at the same time. You might want to consider overwrite: "auto" which only overwrites individual overlapping properties rather than entire tweens (which is what overwrite: true does). If you've got any GSAP-specific questions that you'd like answered, feel free to post them here.
-
Really like the idea because I don't need to install any other dependencies. Also the logic of the intro/outro animation can be clear thanks to GSAP. I tried to do some page transitions with Next.js and three.js using this technique. What I want to achieve is to combine both html and threejs element in intro/outro animations. Demo After I finished the demo, I wrap the context into a tiny package for my own reuse. @chundev/gtranz I remove the setTimeline, background, setBackground in the context because I didn't use them at all and that also make sure context does not trigger re-render because of state change. The context itself is very simple you can just grab the code from src folder if you need it. I want to share the biggest problem I encountered. As I wrote in the Github readme, if I tweened something that is already be set in the outro. (I need instant scroll animation of the images which are also the outro elements) That will break the transition later because as I researched, one element can only have one tween at a time. (I could barely find information about this topic and struggled for a while) So I set the default to overwrite:true (default is false, in the original post, it only do the simplest css intro/outro so no problems) and arrange the order of animation setup by myself. What I did was: 1. use a state to determine the setup order of intro/outro. (if you overwrite some tweens that are used in the outro.) 2. use custom event to re-setup outro whenever you want (mostly after the animation that tweened the same element, with onComplete callback) I don't really know if it's good practice or not. But I did solve my problem and make nice transition by doing so. Just want to share my experience. I really love this community and learn so much things here. Thank you!
-
Hi, I'm trying to figure out how to reposition and scale an element that has been scaled by a previous animation. The previous animation may have been completed and killed at the time the next animation is called. Overwrite appeared to work only if the animation that positioned and scaled the element previously had not been killed. In this example, I'd like to first apply new position and size attributes, and then apply a new scale. I'd like ellipse1 to end up at the position and scale of ellipse2 after the sequence is completed. Is a sequence like this best accomplished by recording several states in a different order with gsap flip, and animating to those states in the order I'd like them to be played? Thanks for your help!
-
Hi Everyone! I have a timeline that plays and reverses based on scroll direction. I also use a mouse enter/leave function to trigger/control the same timeline. When using ScrollSmoother the direction overrides the mouse enter/leave functions (the timeline won't play until the scroll is at rest, even when I trigger mouse enter/leave events). My question: Can I overwrite the scroll control to prioritize the "mouse enter/leave" functions? Thanks, as always.
-
Hi all. I have been tasked my by company to revamp our site. We have designs and a concept for a single-page site that makes use of animations tied to scroll progress. GSAP and Scrolltrigger has been amazing for this so far. At this point I have the full animation orchestrated from the top to the bottom of the page. Scrolling at a reasonable speed results in the desired effect. However scrolling too quickly or jumping to certain points on the page results in elements ending up in places where they shouldn't be. The animation is quite a complex one when all put together. It involves the same elements being animated multiple times, as they move around the page while scrolling. I've divided the single-page into various sections, and have a separate timeline that handles each section. I'm unable to share a full codepen as I'm not allowed to share certain information such as our assets, but I have included code below that I hope adequately shows my approach. I've tried various solutions suggested already on this forum, including: setting "immediateRender" and "overwrite", using only fromTo's, relative vs absolute values. I think that my mistake lies in my approach to the task, I believe I'm not employing the optimal practices in order to complete a complex animation of this nature. useEffect(() => { gsap.registerPlugin(ScrollTrigger); planetFloat(); landingPhase(); discoverPhase(); discussPhase(); decidePhase(); }, []); export const landingPhase = () => { gsap .timeline({ scrollTrigger: { trigger: ".scroll-trigger", start: "10% bottom", end: "+=2000", scrub: 0.25, }, }) // Move landing text off screen .to(".landing-text", { opacity: "-=1", x: "+=300", }) // Bring greatness text onto screen .fromTo( ".greatness-text", { autoAlpha: 0, x: -300 }, { autoAlpha: 1, x: 0, }, ">-50%" ) // Scale up discover system .fromTo( ".discover-system", { opacity: 0, scale: 0 }, { opacity: 1, scale: 1, }, // ">-60%" "<" ) // Telescope ledge rises into view .fromTo( ".telescope", { y: 350 }, { y: 0, }, // ">-60%" "<" ) // Greatness text descends below telescope ledge .to( ".greatness-text", { opacity: "-=1", y: "+=550", }, ">100%" ) // Journey text descends into view .fromTo( ".journey-text", { y: -550, autoAlpha: 0 }, { y: 0, autoAlpha: 1, }, ">-70%" ) // Beginning of system rearrange --> .to( "#moon-container", { x: "-=50", y: "-=100", scale: "-=0.7", overwrite: true }, ">100%" ) .to( "#galaxy-swirl-container", { x: "-=400", y: "+=50", rotate: 7, scale: "+=0.4", overwrite: true }, "<" ) .fromTo("#sun-container", { scale: 0 }, { scale: 0.3 }, "<") .to( "#ringed-planet-container", { x: "-=500", y: "+=100", overwrite: true }, "<" ) .fromTo("#red-planet-container", { scale: 0 }, { scale: 1 }, "<") .fromTo("#darkBlue-planet-container", { scale: 0 }, { scale: 1 }, "<") .to( "#lightBlue-planet-container", { x: "-=160", y: "+=240", scale: "+=0.5", overwrite: true }, "<" ) .to( "#dark-planet-container", { x: "+=50", y: "-=150", scale: "-=0.5", overwrite: true }, "<" ) .to( "#turquoise-planet-container", { x: "-=260", y: "+=60", scale: "+=1.5", overwrite: true }, "<" ) // <-- Ending of system rearrange // Telescope ledge zooms out of view .to( ".telescope", { y: "+=500", x: "-=800", }, "<" ) // Journey text zooms out of view .fromTo( ".journey-text", { y: 0, x: 0, }, { y: 500, x: -800, }, "<" ); }; export const discoverPhase = () => { gsap .timeline({ scrollTrigger: { trigger: ".scroll-trigger", start: "25% bottom", end: "+=2000", scrub: 0.25, }, }) // Phase 1 text moves in from the right .fromTo(".phase1-text", { x: 1500 }, { x: 1200 }, "") // Discover text fades in .fromTo( ".discover-text", { autoAlpha: 0, scale: 2.5 }, { autoAlpha: 1, scale: 2.5 }, "<" ) // Phase 1 text moves over the screen .to(".phase1-text", { x: "-=135%" }, ">100%") // Beginning of system rearrange --> .to( "#galaxy-swirl-container", { rotate: -15, x: "+=300", scale: "-=0.4", overwrite: true }, ">-50%" ) .to( "#sun-container", { x: "+=250", y: "+=25", scale: "-=0.05", overwrite: true }, "<" ) .to("#moon-container", { x: "+=250", overwrite: true }, "<") .to( "#lightBlue-planet-container", { x: "+=200", y: "-=50", scale: "-=0.5", overwrite: true }, "<" ) .to( "#dark-planet-container", { x: "+=200", y: "+=25", scale: "-=0.1", overwrite: true }, "<" ) .to( "#turquoise-planet-container", { x: "+=200", y: "-=75", scale: "-=0.5", overwrite: true, }, "<" ) .to( "#red-planet-container", { x: "+=400", y: "+=50", overwrite: true }, "<" ) .to( "#darkBlue-planet-container", { x: "+=400", y: "+=50", overwrite: true }, "<" ) .to( "#ringed-planet-container", { x: "+=450", scale: "-=0.2", overwrite: true }, "<" ) // <-- Ending of system rearrange // Discover text moves away with system .to( ".discover-text", { rotate: -25, x: "+=450", scale: "-=0.6", opacity: "-=1" }, "<" ) // Curiosity header text slides in .fromTo( ".curiosity-header-text", { x: -700, }, { x: 0 }, "<" ) // Curiosity paragraph 1 text slides in .fromTo( ".curiosity-paragraph1-text", { x: -700, }, { x: 0 }, ">-70%" ) // Curiosity paragraph 2 text slides in .fromTo( ".curiosity-paragraph2-text", { x: -700, }, { x: 0 }, ">-70%" ) // Beginning of system rearrange --> .to( "#lightBlue-planet-container", { x: "-=500", y: "+=500", scale: "+=15", overwrite: true, }, ">200%" ) .to( "#turquoise-planet-container", { x: "-=825", y: "+=30", scale: "+=1.5", overwrite: true, }, "<" ) .to( "#ringed-planet-container", { y: "-=50", x: "-=50", scale: "+=1", overwrite: true, }, "<" ) .to( "#red-planet-container", { x: "-=1500", opacity: "-=1", overwrite: true, }, "<" ) .to( "#darkBlue-planet-container", { x: "-=1500", opacity: "-=1", overwrite: true, }, "<" ) .to( "#dark-planet-container", { x: "-=900", y: "-=150", scale: "+=0.5", overwrite: true, }, "<" ) .to( "#moon-container", { y: "-=0", x: "-=200", scale: "+=0.5", overwrite: true, }, "<" ) .to( "#galaxy-swirl-container", { x: "-=1500", opacity: "-=1", scale: "+=10", overwrite: true, }, "<" ) .to( "#sun-container", { opacity: "-=1", x: "-=1500", y: "-=200", scale: "+=2", overwrite: true, }, "<" ) // <-- Ending of system rearrange // Curiosity header text moves off screen .to( ".curiosity-header-text", { x: "-=700", }, "<" ) // Curiosity paragraph 1 text moves off screen .to( ".curiosity-paragraph1-text", { x: "-=700", }, "<" ) // Curiosity paragraph 2 text moves off screen .to( ".curiosity-paragraph2-text", { x: "-=700", }, "<" ) // Phase 1 text moves off screen .to( ".phase1-text", { x: "-=1000", }, "<" ) // Discuss text fades up and in .fromTo( ".discuss-text", { autoAlpha: 0, scale: 0, y: 500, }, { autoAlpha: 1, scale: 2.5, y: 0, }, ">-50%" ) // Phase 2 texts slides on screen .fromTo( ".phase2-text", { x: -1700, }, { x: -800, }, "<" ); };
-
Yeah, you had !important CSS styles that were interfering. Also, you can simplify this: images.forEach((image) => { if (isEnter) { gsap.to(image, { opacity: 1, scale: 1 }); } else { gsap.to(image, { opacity: 0, scale: 0 }); } }); To this: gsap.to(images, { opacity: isEnter ? 1 : 0, scale: isEnter ? 1 : 0 }); You should also make sure to set opacity/scale to 1 right before the flip in case there's a hover animation running and things are partially-tweened. Like if you hover and then almost immediately click the button, the scale might be 0.5 at that point which could contaminate the final state you're flipping to. So do a gsap.set() with overwrite: true to make sure that other tween is killed too. If you click back and forth between the trigger and reverse buttons in @Rodrigo's demo, you'll see some odd behavior because of that. https://codepen.io/GreenSock/pen/dygzdPE?editors=0010
-
Hey @Rodrigo, Thanks for the recommendations here! I have implemented them and everything is much smoother. My message above was about creating a lerp type of effect fpr the cards using the velocityY property - that's what that this chunk is trying to do using onUp or onDown: const posSetter = gsap.quickSetter(cards, "x", "%"); const clamp = gsap.utils.clamp(-20, 20); const lerp = [8, 16, 24]; const proxy = { x: 0 }; Observer.create({ target: cardItems, type: "wheel,touch,pointer", onUp: (self) => { let lapsedProgress; if (lapsedProgress === 1) return; lapsedProgress = scrollTl.progress() - 0.075; progressTo(lapsedProgress); const x = clamp((self.velocityY / -300) * lerp[Math.floor(Math.random() * lerp.length)]); if (Math.abs(x) > Math.abs(proxy.x)) { proxy.x = x; gsap.to(proxy, { x: 0, duration: 0.8, ease: "none", overwrite: true, onUpdate: () => posSetter(proxy.x) }); } }, }) I guess this is a better example of what i'm trying to do, but wanted to use the velocityY property: https://codepen.io/GreenSock/pen/wvMeNee?editors=0010 Do you think this is doable or should I go down the route of the above example? Here is the updated Codepen for reference: https://codepen.io/jackkemm/pen/RwearyE?editors=0011 EDIT: One thing I noticed too, on mobile/touch devices, the scroll is in the opposite direction. I have tried adding -1 to wheelSpeed and scrollSpeedfor touch devices but the scroll is still in the opposite direction. Is there a way to change this? Thanks, Jack
-
I'll also have to read up on the overwrite: true. I hadn't come across that in my Googling, either.
-
I'd probably add a small delay on the "reset" tween and set overwrite to true. Something like this: https://codepen.io/PointC/pen/ZEMjdLj/364a470e414172b8fbd6a7420de056f6 More info about overwrite. Happy tweening.
-
Yep, sorta like this: https://codepen.io/GreenSock/pen/oNaxdje?editors=1010 Better? And the reason the killTweensOf() helped is because you were creating a TON of conflicting tweens all trying to control the same properties of the same element. You could have solved it equally well by setting overwrite: true. None of that is necessary if you use the gsap.quickTo() technique.
-
@GreenSock I would like to re-open this topic once more. On the same example, try to scroll using laptop trackpad, as using mouse it seems to be working fine. And try to do long, short scrolls. The scrollbar then goes to the end of next section, but automatically scrolls back to to top section. overwrite or autoKill seems not helping
-
Hmm.. So I tried the Attribute Plugin using this code, but it doesn't work with my SVG. Actually it doesn't seem to work with the basic div's either. let elements = document.querySelectorAll('.button'); let clickEvent = (e) => { console.log('some event content here...') console.log("----------------" + e.target.id + "----------------------") } elements.forEach((item) => { item.addEventListener("mouseenter", ()=>{ gsap.to(item, {attr: {fill: "#F2653A"}, duration: 1, overwrite: "auto" }); }); item.addEventListener("mouseout", ()=>{ gsap.to(item, {attr: {fill: "red"} ,duration: 1, overwrite: "auto" }); }); item.addEventListener('click', clickEvent) }); });
-
Hmm.. So I tried the Attribute Plugin using this code, but it doesn't work with my SVG. let elements = document.querySelectorAll('.button'); let clickEvent = (e) => { console.log('some event content here...') console.log("----------------" + e.target.id + "----------------------") } elements.forEach((item) => { item.addEventListener("mouseenter", ()=>{ gsap.to(item, {attr: {fill: "#F2653A"}, duration: 1, overwrite: "auto" }); }); item.addEventListener("mouseout", ()=>{ gsap.to(item, {attr: {fill: "red"} ,duration: 1, overwrite: "auto" }); }); item.addEventListener('click', clickEvent) }); });
-
It looks like you're creating a ton of conflicting tweens that are fighting with each other. Set overwrite: true on your tween to have it immediately kill all other tweens of the same target. And I'm not sure why you're using two different ScrollTriggers for each element - why not just use one? https://stackblitz.com/edit/vue-fchh5m?file=src%2FApp.vue
-
Hi, I'm in this situation and I don't know how to manage the fact that MotionPath animation takes control on the translateX property when reversing the scroll ( go to the very end of the scroll animation, then reverse scrolling back, you will see the circles going to the straight right instead of doing the bending curve as they did in forward mode ). `overwrite:auto` does not seem to help, and `overwrite:true` breaks the animation. TIA.
-
Hi @Haribo, Yeah I see the issue. It seems that adding overwrite into the onLeave an onLeaveBack scroll tweens seems to fix the problem: onLeave: () => { if (i !== panels.length - 1) { let nextPanelId = `panel-${i + 1}`; gsap.to(window, { overwrite: true, scrollTo: { y: ScrollTrigger.getById(nextPanelId).start + 1, autoKill: false }, }); } }, onLeaveBack: () => { if (i) { let prevPanelId = `panel-${i - 1}`; gsap.to(window, { overwrite: true, scrollTo: { y: ScrollTrigger.getById(prevPanelId).end - 1, }, }); } }, From the DOCS (https://greensock.com/docs/v3/GSAP/Tween/vars) : overwrite If true, all tweens of the same targets will be killed immediately regardless of what properties they affect. If "auto", when the tween renders for the first time it hunt down any conflicts in active animations (animating the same properties of the same targets) and kill only those parts of the other tweens. Non-conflicting parts remain intact. If false, no overwriting strategies will be employed. Default: false. Hopefully this helps. Let us know if you have more questions. Happy Tweening!
-
Hi everyones I got stuck at the rotation so my question is how can i Overwrite the rotation ? ?? please go to the Codepen dont know only show the mobile version ... thanks for your time !
-
Issue with playing/reversing animations on mouseenter / mouseleave
Rodrigo replied to andrewycode's topic in GSAP
Hi, The issue with overwrites is basically generated when one or more GSAP instances affect the same property (blur filter in this case), on the same element at the same time. Right now you are creating two different animations for each element before even running one of them. On top of that you are not correctly reversing all the animations, for example you move from heading-2 to heading-3. When you enter heading-2 the heading-1 element gets affected by one animation, when you move to heading-3, one heading-1 animation is reversed but as soon as you enter heading-3 another one starts while the other is still reversing. You have two different animations affecting the same property on the same element. On top of that overwrite won't help you in this case, you can test it by using this configuration in your methods: function animation_blur(elem) { const theAnimation = gsap.timeline(); gsap.set(elem, { filter: 'blur(0px)' }); theAnimation.to(elem, { filter: 'blur(5PX)', ease: 'none', duration: 0.5, overwrite: true, }); return theAnimation } function animation_blur2(elem) { const theAnimation = gsap.timeline(); gsap.set(elem, { filter: 'blur(0px)' }); theAnimation.to(elem, { filter: 'blur(20PX)', ease: 'none', duration: 0.5, overwrite: true, }); return theAnimation } You'll notice that this definitely doesn't work the way you want. Here is what overwrite does: If true, all tweens of the same targets will be killed immediately regardless of what properties they affect. If "auto", when the tween renders for the first time it hunt down any conflicts in active animations (animating the same properties of the same targets) and kill only those parts of the other tweens. Non-conflicting parts remain intact. If false, no overwriting strategies will be employed. Default: false. Since your animations affect just one property everything will be flushed out and only the animations created by the animation_blur2 method will remain and work, that's why I think the best approach is the one I suggested, as that takes the element from it's current state to the state you want (no blur, low blur or high blur). If you need to use reverse, that means that you are using an event handler for doing that, so why using a method that creates a new GSAP instance is not an option? If you want to keep the current approach you have, you have to look into reversing everything and then playing the instances you want, but you already ran into some logic issues with your code. Based on the Codepen example you gave us I provided a solution for the issue I saw there which should be scalable into a larger setup without too much problems. If you keep having issues, let us know. Happy Tweening!