Jump to content
Search Community

All Activity

This stream auto-updates

  1. Past hour
  2. Yesterday
  3. Hi, This is the simplest way I can think of doing that: https://codepen.io/GreenSock/pen/LYvoaNb Hopefully this helps. Happy Tweening!
  4. I'd go even simpler with yoyo tweens and repeatDelay. Something like this. https://codepen.io/PointC/pen/JjVqxgY/63a3e8ecfaf75286961b6e2ec36abed7 Just my 2 cents. YMMV. Happy tweening. Just FYI - the original pen that I forked had tweens with a 0.1 → 1 second duration on a timeline that was 100 seconds long. With scrub set to true they were animating super fast because they were only a tiny fraction of the duration. .1% - 1%.
  5. I doubt you'll run into performance problems with this TBH, since you have a rather small collection of elements. IMHO though this seems simpler to understand, follow and maintain: const blockElements = gsap.utils.toArray(".block"); const block1 = blocks.querySelector(".block-1"); const block2 = blocks.querySelector(".block-2"); const block3 = blocks.querySelector(".block-3"); const blockSettings = [ { element: block1, position: 30 }, { element: block2, position: 50 }, { element: block3, position: 60 } ]; blockSettings.forEach((b, i) => { const block = blockElements[i]; const otherBlocks = blockElements.filter((block, j) => i !== j); tl.add(() => { gsap.to(block, { opacity: 1, duration: 1, scale: 1 }); gsap.to(otherBlocks, { opacity: 0, duration: 1, scale: 1.2 }); }, b.position); }); You don't have to resort to that conditional block and anything that results in simpler and shorter code, is going to make more sense if you have to look at it in 6 months. Here is a fork of your demo: https://codepen.io/GreenSock/pen/oNORmKL Is worth mentioning that using the add() to add an anonymous function is the same as using call(): const tl = gsap.timeline(); tl.add(() => {}, position); // Exactly the same tl.call(() => {}, [/*parameters*/], position); Hopefully this helps. Happy Tweening!
  6. Hi @MarOne, You can check the resources in the Webflow installation page on our Learning Center: https://gsap.com/resources/Webflow#installation Also we have a brand new installation video, where @Cassie goes into a lot of detail including webflow: This starts right with webflow, but you can go to youtube and check the chapters on the video so you can jump directly to a specific section. Hopefully this helps. Happy Tweening!
  7. wow it works fine, i replaced the useEffect with useGsap thanks a lot
  8. After more tinkering, i think i found a solution: https://codepen.io/Valentin-Ilas/pen/ZEZNVRX?editors=1111 Basically I'm using tl.add() to run animations independently from the scrub on the parent container. Then i can specify for each one where during the progress it should run. Seems to work backwards too. But is this approach efficient? or do you see any performance issues?
  9. Can Anybody please tell me how to use Gsap Club plugins in a webflow project? It's soooo complicated and it shouldn't be like that?
  10. Hi, (stack : Next JS) i'm close to achieve an effect but am stuck! Here is the result of where i am for now : https://youtu.be/tXUPHLRPiDA As you can see, I'd like to make a transition on clicking to a project thumbnail. Problem : I don't want the container to scaleY up, but i want to keep the infos/image inside to a normal ratio. I tied to animate height, instead of scaleY, but then I have to also animation the scrollTo, and it was a bit messy... Here is the ProjectThumb component (I do have a project list with several ProjectThumb) : import React, { useRef, useState } from "react"; import { useGSAP } from "@gsap/react"; import gsap from "gsap"; import { lineAnimation, projectContainerAnimation, scrollAnimation, subtitleAnimation, titleAnimation, } from "../animations/animations"; import { ScrollToPlugin } from "gsap/ScrollToPlugin"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import Image from "next/image"; import { useRouter } from "next/navigation"; gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(ScrollToPlugin); const ProjectThumb = ({ project, setClicked, clicked, }: { project: any; setClicked: React.Dispatch<React.SetStateAction<number | null>>; clicked: number | null; }) => { const lineRef = useRef<HTMLDivElement>(null); const thumbContainerRef = useRef<HTMLDivElement>(null); const titleRef = useRef(null); const subTitleRef = useRef(null); const imageRef = useRef(null); const router = useRouter(); const [timeline, setTimeline] = useState<any>(); const [timeline2, setTimeline2] = useState<any>(); // set line animation timeline up useGSAP(() => { if (lineRef.current) { const tl2 = gsap.timeline({ scrollTrigger: { trigger: lineRef.current, start: "top 85%", toggleActions: "play end resume reverse", }, }); tl2.add(lineAnimation(lineRef)); setTimeline2(tl2); } }, [lineRef]); // Set project elements timeline up useGSAP(() => { const tl = gsap.timeline(); setTimeline(tl); // show off all project container but the one clicked if (clicked && clicked !== project.id && thumbContainerRef.current) { timeline.to( thumbContainerRef.current, { opacity: 0, duration: 0.5, }, `<${Math.abs((clicked - project.id) * 0.5) / 3}` ); } }, [clicked, thumbContainerRef]); const handlePlayAnimation = () => { if ( thumbContainerRef.current && subTitleRef.current && titleRef.current && timeline2 && timeline ) { setClicked(project.id); timeline2.clear(); timeline2.to(lineRef.current, { scaleX: 0, duration: 0.5, }); const offset = window.innerHeight * 0.5 - thumbContainerRef.current.getBoundingClientRect().height / 2 - 32; const thumbContainerScale = window.innerHeight / thumbContainerRef.current.getBoundingClientRect().height; timeline .add(scrollAnimation(thumbContainerRef, offset)) .add(titleAnimation(titleRef), "-=0.4") .add(subtitleAnimation(subTitleRef), "-=0.25") .add( projectContainerAnimation(thumbContainerRef, thumbContainerScale), "<=0.3" ); // .then(() => router.push(`/projects/${project.id}`)); } }; return ( <div className={`project_container_${project.id} overflow-hidden min-h-[25vh] relative`} ref={thumbContainerRef} > <div ref={lineRef} className="projectLine scale-x-0 h-2 bg-slate-500 opacity-100" ></div> <div onClick={handlePlayAnimation} className="project_infos button absolute w-full h-full z-10 cursor-pointer" > <div></div> <div className="project_title flex gap-4 p-8 items-end text-white" key={project.id} > <h3 ref={titleRef} className="project_title text-2xl w-full text-slate-800" > {project.title} </h3> <p ref={subTitleRef} className="project_subtitle w-full text-slate-800" > {project.subtitle} </p> </div> </div> <Image ref={imageRef} src={project.images.thumbnail} width={1920} height={1080} alt={project.title} className="h-full object-cover aspect-square opacity-30 absolute" /> </div> ); }; export default ProjectThumb; And here are the animations called by the previous component : import gsap from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import { ScrollToPlugin } from "gsap/ScrollToPlugin"; gsap.registerPlugin(ScrollTrigger); gsap.registerPlugin(ScrollToPlugin); export const lineAnimation = (ref: any) => { return gsap.to(ref.current, { scaleX: 1, transformOrigin: "left", duration: 0.75, }); }; export const thumbContainerAnimation = (ref: any) => { return gsap.to(ref.current, { height: "100vh", transformOrigin: "top center", duration: 1, ease: "expo.in", }); }; export const scrollAnimation = (ref: any, offset: number) => { return gsap.to(window, { duration: 0.75, scrollTo: { y: ref.current, offsetY: offset }, ease: "expo.inOut", }); }; export const titleAnimation = (ref: any) => { return gsap.to(ref.current, { duration: 0.4, y: -50, opacity: 0, ease: "expo.in", }); }; export const subtitleAnimation = (ref: any) => { return gsap.to(ref.current, { duration: 0.35, y: -50, opacity: 0, ease: "expo.in", }); }; export const projectContainerAnimation = (ref: any, scale: number) => { return gsap.to(ref.current, { scaleY: scale, transformOrigin: "center", duration: 1.2, ease: "power4.inOut", }); };
  11. So basically i saw this demo here: https://codepen.io/snorkltv/pen/vYVBPJq and decided to try it out in react but for some reason the code only actvates when i've scrolled to the bottom is there any reason why? here's mine : https://stackblitz.com/edit/vitejs-vite-83dipo?file=src/App.jsx
  12. I've been trying to use the position parameter and I feel like I'm close but not quite there yet I noticed some interesting behaviour: if scrub is set to true, then the a duration seems to be needed. So i added duration: 100 which seems to work more as a percentage If i use the position parameter on each block, with values such as 20, 30 50 etc, they seem to trigger based on the percentage as well. Now what i can't seem to be able to do is to make the fade-in more animated instead of running it instantly and how to control the duration of the fade in. Please see the example below: https://codepen.io/Valentin-Ilas/pen/abxrRYe
  13. I'm not 100% sure I follow what you're trying to do. I forked the codepen and made some changes to it: https://codepen.io/GreenSock/pen/wvZbQKX At the end the pinnedContainer config is not needed since any of the pinned elements is adding any vertical space. Hopefully this helps. Happy Tweening!
  14. Just so you know, you could just call tween.revert() instead of doing both kill() and clearProps. You could also record the progress in your resize handler, and then re-apply that to make things continuous: https://codepen.io/GreenSock/pen/ZEZNmpz?editors=0010
  15. That is exactly what the position parameter can do. Another way is to keep the logic you have now, but find a way to run it just once and not everytime the GSAP instance updates, that's too wasteful. Happy Tweening!
  16. Hi Rodrigo, I tried to illustrate it in the following codepen but I think i didn't get it quite right. So the idea is that there is a pinned container which is connected to a scroll trigger and at precise moments in the timeline, for example when the progress is 0.3 then the block 1 should appear. If the progress goes to 0.74 then block 2 should appear. All 3 blocks are in the same position just only 1 visible at a time. https://codepen.io/Valentin-Ilas/pen/abxrRYe?editors=1111
  17. That is mostly because GSAP handles all the transforms using the 3D transform matrix: https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix3d While setting a single transform using CSS does just that, applies a single transform. When you do that and then animate the same property with GSAP, GSAP takes that value applied by CSS and uses it's own method for transforms. That is why we recommend doing what you tried and worked, use GSAP to set all the initial transforms that will be animated with GSAP, to avoid that extra step when the animation first runs. Also sometimes is a good idea to use the advice from the FOUC article to prevent annoying flashes before the JS runs. Happy Tweening!
  18. Hi, Just create a loop for the cards, add a couple of to() instances to the timeline and using the position parameter create an overlap with the previous card: https://codepen.io/GreenSock/pen/WNWBaKz Hopefully this helps. Happy Tweening!
  19. Maybe it was hardware acceleration issues? This morning, I also changed something in my storybook that seemed to help - even though I can't explain why. On initial load, I set the initial segment position with GSAP, and that seemed to make a huge difference. Any theories why setting GSAP styles on the initial item made the difference? The flickering i was seeing seemed to happen for the first transition between segments. After this change, it seems to be fluid. But I don't understand why. Is it best practice to set initial GSAP styles on all elements before animating? Thanks so much for your help @Rodrigo This is what i added: /** * Animation to play when the segment is the initial segment */ setInitialSegment: () => gsap.core.Timeline; .... // For setting the initial animation state with gsap values ctx.add("setInitialSegment", () => { return gsap.set(sectionRef.current, { ...TRANSLATION_BASE, yPercent: 0, autoAlpha: 1, scale: 1, }); }); .... useEffect(() => { /** * If the previous segment is null, then this is the initial segment */ if (previousSegment === null) { /** * Establish gsap styles on the initial segment */ if (isActive) { if (ctx && "setInitialSegment" in ctx) { ctx?.setInitialSegment?.(); } } return; }
  20. The demo I provided is just a small part of a larger animation I'm building, so svg wouldn't work in this case. But clearProps works perfectly. Thank you @Trapti and @PointC
  21. Hi, The animation looks good to me, I can't see any flickering/jittering or any other issue while changing the active element, everything looks fluent and nice on my end on Ubuntu 22 & 20 on the latest Firefox and Chrome 🤷‍♂️
  22. Hi, Besides echoing the need for a minimal demo, I'd advice you to not use all that convoluted conditional logic, keep in mind that this block: if (progress > 0 && progress <= 0.5) { gsap.killTweensOf([block2, block3]); // Cancel ongoing animations on block2 and block3 gsap.to(block1, { opacity: 1, y: 0, duration: opacityDuration }); gsap.set(block2, { opacity: 0, y: 15, }); gsap.set(block3, { opacity: 0, y: 15, }); } Will run 60 times per second while the progress value is less than 0.50000001, that means you'll create a new to() instance that will override a previously created one and you will be creating set() instances that are no longer needed. You should use the position parameter to add your instances at specific points of your timelne: https://gsap.com/resources/position-parameter/ If your instance is 10 seconds, then you can do something like this: const tl = gsap.timeline(); tl .to(blocks, { duration: 10 }, 0) .to(block1, { duration: opacityDuration, opacity: 1, y: 0, }, 0) .set(block2, { opacity: 0, y: 15, }, 0) .set(block3, { opacity: 0, y: 15, }, 0) .to(blocks, { duration: 10 }, 0.5) .to(block1, { duration: opacityDuration, opacity: 1, y: 0, }, 0) .set(block2, { opacity: 0, y: 15, }, 0.5) .set(block3, { opacity: 0, y: 15, }, 0.5) Of course ideally you'd have to find a dynamic way to achieve that, maybe using a combination of the position parameter and the call() method: https://gsap.com/docs/v3/GSAP/Timeline/call() Hopefully this helps. Happy Tweening!
  23. OMG @Rodrigo i'm sorry i realized my codesandbox was private: Here you can try again: https://codesandbox.io/p/sandbox/gsap-expedition-w96d45?file=%2Fsrc%2Fcomponents%2FExpedition%2FSegment.tsx Thank you for your time if you have any. I appreciate it Also, this is the core animation segment for reference: import React, { forwardRef, useEffect, useRef } from "react"; import styles from "./styles.module.scss"; import { useActiveSegment, BkExpeditionContextValues } from "."; import { mergeRefs } from "../util/mergeRefs"; import clsx from "clsx"; import { useGsapContext } from "../util/useGsapContext"; import gsap from "gsap"; /** * Registered animations for the context */ export type RegisteredContextAnimations = { /** * Animation to play when the segment is active */ segmentIn: () => gsap.core.Timeline; /** * Animation to play when the segment is inactive */ segmentOut: () => gsap.core.Timeline; }; /** * Segment component to be used within the BkExpedition component */ export type SegmentProps = React.ComponentPropsWithoutRef<"section"> & { segmentKey: BkExpeditionContextValues["activeSegment"]; }; export const Segment = forwardRef<HTMLDivElement, SegmentProps>( (props, ref) => { const { segmentKey, "aria-label": ariaLabel, className, children, ...restOfHtmlAttrs } = props; /** * Get the active segment from the context */ const { activeSegment, previousSegment } = useActiveSegment(); /** * Check if the segment is active */ const isActive = activeSegment === segmentKey; /** * Reference to the segment */ const sectionRef = useRef<HTMLDivElement>(null); /** * Get the context for the animations */ const ctx = useGsapContext<HTMLDivElement, RegisteredContextAnimations>( sectionRef ); /** * Animation constants */ const ANIMATION_DURATION = 1; const ANIMATION_SCALE = 0.9; const ANIMATION_EASING = "power1.inOut"; const TRANSLATION_BASE = { y: 0, x: 0, }; useEffect(() => { /** * Register the animations for the segment */ ctx.add("segmentIn", () => { return gsap .timeline({ paused: true, onComplete: () => { sectionRef.current?.focus(); }, }) .fromTo( sectionRef.current, { ...TRANSLATION_BASE, yPercent: 100, autoAlpha: 0, }, { ...TRANSLATION_BASE, yPercent: 0, autoAlpha: 1, scale: 1, duration: ANIMATION_DURATION, ease: ANIMATION_EASING, immediateRender: false, } ); }); /** * Register the animations for the segment to animate out */ ctx.add("segmentOut", () => { return gsap .timeline({ paused: true, onComplete: () => { gsap.set(sectionRef.current, { ...TRANSLATION_BASE, yPercent: 100, autoAlpha: 0, scale: 0.95, }); }, }) .fromTo( sectionRef.current, { ...TRANSLATION_BASE, yPercent: 0, autoAlpha: 1, scale: 1, }, { ...TRANSLATION_BASE, yPercent: -100, autoAlpha: 0, scale: ANIMATION_SCALE, duration: ANIMATION_DURATION, ease: ANIMATION_EASING, immediateRender: false, } ); }); return () => { ctx.revert(); }; }, []); useEffect(() => { if (previousSegment === null) { return; } if (activeSegment === segmentKey) { if (ctx && "segmentIn" in ctx) { ctx?.segmentIn?.().play(); } return; } if (previousSegment === segmentKey) { if (ctx && "segmentOut" in ctx) { ctx?.segmentOut?.().play(); } return; } return () => { ctx.revert(); }; // only run when the active segment changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeSegment, previousSegment, segmentKey]); /** * Add error handling for when the segmentKey is not set */ if (!segmentKey || segmentKey === "") { if (process.env.NODE_ENV === "development") { throw new Error( "The segmentKey prop must be passed to the BkExpedition.Segment component" ); } return null; } const inlineStyles: React.CSSProperties = { ...(previousSegment === null && { transform: isActive ? "translateY(0%)" : "translateY(100%)", opacity: isActive ? 1 : 0, }), }; return ( <section ref={mergeRefs(ref, sectionRef)} {...restOfHtmlAttrs} aria-hidden={!isActive} tabIndex={-1} aria-label={ariaLabel || segmentKey} className={clsx( className, styles.segment, isActive ? styles.isActive : styles.isInactive, isActive && previousSegment === null && styles.isInitial )} style={{}} data-segment-key={segmentKey} data-segment-active={isActive} data-testid={`${segmentKey}-segment`} > {children} </section> ); } ); Segment.displayName = "Segment";
  24. Hi, Your sandbox is not working, the response is a 404 Sandbox not found. Maybe you're having issues with FOUC, check this article from our Learning Center: https://gsap.com/fouc Finally a lot of performance problems are down to how browsers and graphics rendering work. It's very difficult to troubleshoot blind and performance is a DEEP topic, but here are some tips: Try setting will-change: transform on the CSS of your moving elements. Make sure you're animating transforms (like x, y) instead of layout-affecting properties like top/left. Definitely avoid using CSS filters or things like blend modes. Those are crazy expensive for browsers to render. Be very careful about using loading="lazy" on images because it forces the browser to load, process, rasterize and render images WHILE you're scrolling which is not good for performance. Make sure you're not doing things on scroll that'd actually change/animate the size of the page itself (like animating the height property of an element in the document flow) Minimize the area of change. Imagine drawing a rectangle around the total area that pixels change on each tick - the bigger that rectangle, the harder it is on the browser to render. Again, this has nothing to do with GSAP - it's purely about graphics rendering in the browser. So be strategic about how you build your animations and try to keep the areas of change as small as you can. If you're animating individual parts of SVG graphics, that can be expensive for the browser to render. SVGs have to fabricate every pixel dynamically using math. If it's a static SVG that you're just moving around (the whole thing), that's fine - the browser can rasterize it and just shove those pixels around...but if the guts of an SVG is changing, that's a very different story. data-lag is a rather expensive effect, FYI. Of course we optimize it as much as possible but the very nature of it is highly dynamic and requires a certain amount of processing to handle correctly. I'd recommend strategically disabling certain effects/animations and then reload it on your laptop and just see what difference it makes (if any). Hopefully this helps. Happy Tweening!
  25. I have 3 blocks of text which appear as you scroll and I am fading in each one depending on where in the timeline the progress is. I am wondering if there is any more efficient way to do this as the code below looks very redundant tl.to(blocks,{ ease: 'power1.inOut', duration: 100, onUpdate: function () { const progress = tl.progress(); if (progress > 0 && progress <= 0.5) { gsap.killTweensOf([block2, block3]); // Cancel ongoing animations on block2 and block3 gsap.to(block1, { opacity: 1, y: 0, duration: opacityDuration }); gsap.set(block2, { opacity: 0, y: 15, }); gsap.set(block3, { opacity: 0, y: 15, }); } else if (progress > 0.5 && progress <= 0.7) { gsap.killTweensOf([block1, block3]); // Cancel ongoing animations on block1 and block3 gsap.set(block1, { opacity: 0, y: 15, }); gsap.to(block2, { opacity: 1, y: 0, duration: opacityDuration }); gsap.set(block3, { opacity: 0, y: 15, }); } else if (progress > 0.7 && progress <= 1) { gsap.killTweensOf([block1, block2]); // Cancel ongoing animations on block1 and block2 gsap.set(block1, { opacity: 0, y: 15, }); gsap.set(block2, { opacity: 0, y: 15 }); gsap.to(block3, { opacity: 1, y: 0, duration: opacityDuration }); } } }, '<');
  26. You still have transition: all 0.25s ease; in your css which is conflicting with your JavaScript animations. highly recommend removing this and never to use transition: all 0.25s ease! Just change all to the property you want to transition eg transition: opacity 0.25s ease; but glad you’ve solved your issue. Happy tweeting
  1. Load more activity
×
×
  • Create New...