Jump to content
Search Community

coldgroove

Members
  • Posts

    6
  • Joined

  • Last visited

Posts posted by coldgroove

  1. Hi! We really like this 3d carousel idea and tried to implement our own version. We have two main problems:

    1 - Can't figure out how they made the scrolltrigger infinite

    2 - Tried to add a drag option as well with Draggable plugin and wanted to bind it to the scrolltrigger so the card positions are in sync with it.

     

    Here is our implementation (couldnt make it as a pen because I'm lazy and dont know how to create a react envo in codepen but hey, here is the live demo if you wanna see how it looks and behaves):

     

    vars and useEffect:

     

    const numberOfCards = allCases.length;
    const angleOfCards = 360 / numberOfCards;
    const cardGaps =
        numberOfCards > 6
          ? Math.pow(numberOfCards, 2.2)
          : -angleOfCards / numberOfCards;
    
    useEffect(() => {
        gsap.registerPlugin(ScrollTrigger, Draggable);
        const root = rootRef.current;
        const container = containerRef.current;
        const cards = cardRefs.current;
        const dragProxy = dragProxyRef.current;
        // Mouse Tracking animation
        const onMouseMove = (e) => {
          if (!root || !container || !cards) return;
          const { clientX, clientY } = e;
          const { width: clientWidth, height: clientHeight } =
            root.getBoundingClientRect();
          const rotateXVal = (clientY - clientHeight / 2) * 0.06;
          const rotateYVal = (clientWidth / 2 - clientX) * 0.01;
    
          container.style.transform = `rotateX(${rotateXVal}deg) rotate(${rotateYVal}deg)`;
        };
        document.addEventListener("mousemove", onMouseMove);
        // Gsap Animations with scroll and drag
        const ctx = gsap.context(() => {
          if (!root || !container || !cards || !dragProxy) return;
          // Drag
          Draggable.create(dragProxy, {
            type: "x",
            trigger: cards,
            bounds: container,
            onDrag: function () {
              const progress = (this.startX - this.x) * 0.008;
              cards.forEach((card, idx) => {
                card.style.transform = `rotateY(${
                  (idx + progress) * angleOfCards
                }deg) translateZ(320px) translate3d(0px,0px,${cardGaps}px)`;
              });
            },
          });
          // Scroll
          ScrollTrigger.create({
            pin: true,
            scrub: true,
            invalidateOnRefresh: false,
            start: "top top",
            end: "+=10000", //i set the end as a big number to at least rotate the carousel one whole turn since i can't make it infinite tried to mimic it
            trigger: root,
            // markers: true,
          });
          cards.forEach((card, idx) => {
            ScrollTrigger.create({
              scrub: true,
              start: "top top",
              end: "max",
              trigger: container,
              onUpdate: (self) => {
                gsap.delayedCall(0.1, () => {
                  card.style.transform = `rotateY(${
                    (idx + self.progress * 10) * angleOfCards
                  }deg) translateZ(320px)  translate3d(0px,0px,${Math.abs(
                    (cardGaps * (cards.length + 5)) / cards.length
                  )}px)`;
                });
                gsap.delayedCall(1, () => {
                  card.style.transform = `rotateY(${
                    (idx + self.progress * 10) * angleOfCards
                  }deg) translateZ(320px) translate3d(0px,0px,${cardGaps}px)`;
                });
              },
            });
          });
        });
    
        return () => {
          ctx.revert();
          document.removeEventListener("mousemove", onMouseMove);
        };
      }, []);

    JSX:

     

    <section className={s.root} ref={rootRef}>
          <div className={s.carouselContainer} ref={containerRef}>
            {allCases &&
              allCases.map((c, idx) => {
                return (
                  <div
                    key={c.id}
                    onMouseEnter={() => setSelectedCaseIdx(idx)}
                    style={{
                      transform: `rotateY(${
                        idx * angleOfCards
                      }deg) translateZ(320px) translate3d(0px,0px,${cardGaps}px)`,
                    }}
                    ref={(e) => createRefs(cardRefs, e, idx)}
                    className={s.carouselCard}
                  >
                    <CustomLink link={c.caseLink} className="z-10">
                      <NextImage
                        layout="fill"
                        media={c.bgImg}
                        objectFit="cover"
                        className="rounded-lg"
                      />
                      <div className={s.content}>
                        <p className={classNames(s.description, "text-lg")}>
                          {c.description}
                        </p>
                        <h3 className={s.bigTitle}>{c.bigTitle}</h3>
                      </div>
                    </CustomLink>
                  </div>
                );
              })}
          </div>
          <div ref={dragProxyRef} className="invisible absolute" />
    </section>

     

    Here is the related css classes just in case something is wrong with it?

     

    .root {
      @apply pt-20 2xl:pt-32 px-20 min-h-screen text-primary relative;
    }
    
    .carouselContainer {
      @apply relative flex justify-center items-center;
      min-height: 650px;
      width: 100%;
      transform-style: preserve-3d;
      perspective: 1300px;
      /* transform-origin: center center calc(-300px / 100); */
    }
    
    .carouselCard {
      @apply rounded-lg;
      overflow: hidden;
      width: 320px;
      aspect-ratio: 0.67;
      position: absolute;
      transition: transform 1s;
      -webkit-box-reflect: below 10px
        linear-gradient(transparent, transparent, #0005);
    }

     

    • Like 1
  2.  

    Hey there uhm, I'm trying to make a similar effect but got stuck so maybe you guys can help me?

    I've basically copied @PointC 's svg pattern with the mask one not the clipPath, and I only want to have a vertical mask with 2 fixed colors changing into each other. So I only changed the svg size to be width=100% height=100% just to make it a fullscreen one. There is no probs there.

     

    However why do you select the rect's that are inside of definition tags for x and y animations? I got confused about that because if I select those my mask doesn't change neither with scaleY: 0, or height:0. 

    Nonetheless if I attach my ref to the rect that you defined as "this is the fixed color background with white as the fill, but the stroke changes", it works. I mean it's kinda. The blueScreen is going upwards with scaleY: 0. But the mask doesnt work so the other text isn't showing up for some reason. I've removed the stroke stuff too if its related.

     

    This is my svg el with a few changes:

    <svg
    id="maskDemo"
    xmlns="http://www.w3.org/2000/svg"
    width="100%"
    height="100%"
    viewBox="0 0 400 200"
    >
      <title>Invert SVG text fill color with masks</title>
      <defs>
          <mask id="theMask">
            <rect
            id="maskerH"
            width="400"
            height="200"
            x="-400"
            y="0"
            fill="#fff"
            />
            <rect
            id="maskerV"
            width="400"
            height="200"
            x="0"
            y="200"
            fill="#fff"
            />
          </mask>
      </defs>
    {/* <!-- this is the fixed color background with white as the fill, but the stroke changes --> */}
      <rect
      id="bgFixed"
      ref={blueScreenRef}
      width="400"
      height="200"
      fill={data.textColorCode}
      // stroke="#94c356"
      // stroke-width="4"
      />
    
        {/* <!-- this text color changes based on the chosen color swatch--> */}
      <g id="startColor" fill={data.bgColorCode} fontSize="100">
        <text className="theCount" textAnchor="end" x="220" y="140">
          100
      </text>
      <text textAnchor="start" x="230" y="140">
        %
        </text>
      </g>
    
      <g mask="url(#theMask)">
        {/* <!-- this is the changeable color background based on the swatch click--> */}
      <rect id="bgChange" width="400" height="200" fill="#94c356"  />
    
        {/* <!-- this is the duplicate text group revealed by the masks it's always white --> */}
      <g id="end" fill="white" fontSize="100">
        <text className="theCount" textAnchor="end" x="220" y="140">
          100
      </text>
      <text textAnchor="start" x="230" y="140">
        %
        </text>
      </g>
      </g>
      </svg>

    and this is my useEffect:

    useEffect(() => {
        const ctx = gsap.context(() => {
          const sharedScrollTrigger = {
            start: "top top",
            trigger: sectionRef.current,
            invalidateOnRefresh: false,
            scrub: 1,
            end: () => "+=4500",
            // markers: true,
          };
          // pinning the section
          gsap.to(sectionRef.current, {
            scrollTrigger: {
              start: "top top",
              pin: true,
              trigger: sectionRef.current,
              invalidateOnRefresh: false,
              scrub: 1,
              end: () => "+=5000",
              // markers: true,
            },
          });
          // close the blueScreen
          gsap.fromTo(
            blueScreenRef.current,
            {
              scaleY: 1,
            },
            {
              scaleY: 0,
              scrollTrigger: sharedScrollTrigger,
            }
          );
          // other anims...
        }, sectionRef);
        return () => ctx.revert();
      }, []);

    What am i doing wrong in here brothas? I think i suck with the svg's @PointC ? any help would be greatly appreciated.

  3. @Rodrigo Big thanks, I'm new to GSAP and learning alot since I've started working with it, I've copied and pasted this sandbox that I've found on the internet because I'was too lazy to implement one-to-one copy of my production code, I'm using the latest version with context and everything.

     

    I've tried to tweak the params with hardcoded values but somehow couldnt get it working. Now it all makes sense, thanks again this solved my issue!

    • Like 2
×
×
  • Create New...