我正在研究 GSAP 动画,但在使用 React 实现它时遇到问题

问题描述 投票:0回答:1

我正在开发一个具有 GSAP 动画的 React 项目,我已经使用 JavaScript 示例实现了无限循环动画,但无法在 React 中实现它。当我使用一些预定义函数时,它会抛出很多错误,请检查屏幕截图以供参考。Error Screenshot

在这里我提供了我已经实现的javascript的codepen以及我想要实现它的stackbiz。感谢您的帮助。

https://codepen.io/EmpChirag/pen/bGZNYwo

https://stackblitz.com/edit/gsap-react-basic-f48716-rm5kfn?file=src%2FApp.js

我尝试过将函数放入 useEffect、useGsap、useLayoutEffect 中,但我不明白如何管理这些函数。

这是我想使用的功能。

// use the helper function to build a seamless looping gsap.timeline() with some special properties/methods
let tween = verticalLoop(".box-wrapper:nth-child(odd) div", {
  repeat: -1, 
  speed:0.5
});

let tween2 = verticalLoop(".box-wrapper:nth-child(even) div", {
  repeat: -1, 
  speed:1
});
let tween3 =  horizontalLoop(".cb-tagreel-row div", {
  repeat: -1, 
  speed:1
});





/*
This helper function makes a group of elements animate along the y-axis in a seamless, responsive loop.

Features:
 - Uses yPercent so that even if the widths change (like if the window gets resized), it should still work in most cases.
 - When each item animates up or down enough, it will loop back to the other side
 - Optionally pass in a config object with values like draggable: true, center: true, speed (default: 1, which travels at roughly 100 pixels per second), paused (boolean), repeat, reversed, and paddingBottom.
 - The returned timeline will have the following methods added to it:
   - next() - animates to the next element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc.
   - previous() - animates to the previous element using a timeline.tweenTo() which it returns. You can pass in a vars object to control duration, easing, etc.
   - toIndex() - pass in a zero-based index value of the element that it should animate to, and optionally pass in a vars object to control duration, easing, etc. Always goes in the shortest direction
   - current() - returns the current index (if an animation is in-progress, it reflects the final index)
   - times - an Array of the times on the timeline where each element hits the "starting" spot.
   - elements - an Array of the elements that are being controlled by the timeline
 */
function verticalLoop(items, config) {
    items = gsap.utils.toArray(items);
    config = config || {};
    let onChange = config.onChange,
      lastIndex = 0,
      tl = gsap.timeline({repeat: config.repeat, onUpdate: onChange && function() {
        let i = tl.closestIndex()
        if (lastIndex !== i) {
          lastIndex = i;
          onChange(items[i], i);
        }
      }, paused: config.paused, defaults: {ease: "none"}, onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100)}),
      length = items.length,
      startY = items[0].offsetTop,
      times = [],
      heights = [],
      spaceBefore = [],
      yPercents = [],
      curIndex = 0,
      center = config.center,
      clone = obj => {
        let result = {}, p;
        for (p in obj) {
          result[p] = obj[p];
        }
        return result;
      },
      pixelsPerSecond = (config.speed || 1) * 100,
      snap = config.snap === false ? v => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural
      timeOffset = 0, 
      container = center === true ? items[0].parentNode : gsap.utils.toArray(center)[0] || items[0].parentNode,
      totalHeight,
      getTotalHeight = () => items[length-1].offsetTop + yPercents[length-1] / 100 * heights[length-1] - startY + spaceBefore[0] + items[length-1].offsetHeight * gsap.getProperty(items[length-1], "scaleY") + (parseFloat(config.paddingBottom) || 0),
      populateHeights = () => {
        let b1 = container.getBoundingClientRect(), b2;
        items.forEach((el, i) => {
          heights[i] = parseFloat(gsap.getProperty(el, "height", "px"));
          yPercents[i] = snap(parseFloat(gsap.getProperty(el, "y", "px")) / heights[i] * 100 + gsap.getProperty(el, "yPercent"));
          b2 = el.getBoundingClientRect();
          spaceBefore[i] = b2.top - (i ? b1.bottom : b1.top);
          b1 = b2;
        });
        gsap.set(items, { // convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster.
          yPercent: i => yPercents[i]
        });
        totalHeight = getTotalHeight();
      },
      timeWrap,
      populateOffsets = () => {
        timeOffset = center ? tl.duration() * (container.offsetWidth / 2) / totalHeight : 0;
        center && times.forEach((t, i) => {
          times[i] = timeWrap(tl.labels["label" + i] + tl.duration() * heights[i] / 2 / totalHeight - timeOffset);
        });
      },
      getClosest = (values, value, wrap) => {
        let i = values.length,
          closest = 1e10,
          index = 0, d;
        while (i--) {
          d = Math.abs(values[i] - value);
          if (d > wrap / 2) {
            d = wrap - d;
          }
          if (d < closest) {
            closest = d;
            index = i;
          }
        }
        return index;
      },
      populateTimeline = () => {
        let i, item, curY, distanceToStart, distanceToLoop;
        tl.clear();
        for (i = 0; i < length; i++) {
          item = items[i];
          curY = yPercents[i] / 100 * heights[i];
          distanceToStart = item.offsetTop + curY - startY + spaceBefore[0];
          distanceToLoop = distanceToStart + heights[i] * gsap.getProperty(item, "scaleY");
          tl.to(item, {yPercent: snap((curY - distanceToLoop) / heights[i] * 100), duration: distanceToLoop / pixelsPerSecond}, 0)
            .fromTo(item, {yPercent: snap((curY - distanceToLoop + totalHeight) / heights[i] * 100)}, {yPercent: yPercents[i], duration: (curY - distanceToLoop + totalHeight - curY) / pixelsPerSecond, immediateRender: false}, distanceToLoop / pixelsPerSecond)
            .add("label" + i, distanceToStart / pixelsPerSecond);    
          times[i] = distanceToStart / pixelsPerSecond;
        }
        timeWrap = gsap.utils.wrap(0, tl.duration());
      }, 
      refresh = (deep) => {
         let progress = tl.progress();
         tl.progress(0, true);
         populateHeights();
         deep && populateTimeline();
         populateOffsets();
         deep && tl.draggable ? tl.time(times[curIndex], true) : tl.progress(progress, true);
      },
      proxy;
    gsap.set(items, {y: 0});
  populateHeights();
    populateTimeline();
  populateOffsets();
  window.addEventListener("resize", () => refresh(true));
    function toIndex(index, vars) {
        vars = clone(vars);
        (Math.abs(index - curIndex) > length / 2) && (index += index > curIndex ? -length : length); // always go in the shortest direction
        let newIndex = gsap.utils.wrap(0, length, index),
            time = times[newIndex];
    if (time > tl.time() !== index > curIndex) { // if we're wrapping the timeline's playhead, make the proper adjustments
            time += tl.duration() * (index > curIndex ? 1 : -1);
        }
    if (vars.revolutions) {
      time += tl.duration() * Math.round(vars.revolutions);
      delete vars.revolutions;
    }
    if (time < 0 || time > tl.duration()) {
      vars.modifiers = {time: timeWrap};
    }
        curIndex = newIndex;
        vars.overwrite = true;
    gsap.killTweensOf(proxy);
        return tl.tweenTo(time, vars);
    }
  tl.elements = items;
    tl.next = vars => toIndex(curIndex+1, vars);
    tl.previous = vars => toIndex(curIndex-1, vars);
    tl.current = () => curIndex;
    tl.toIndex = (index, vars) => toIndex(index, vars);
  tl.closestIndex = setCurrent => {
    let index = getClosest(times, tl.time(), tl.duration());
    setCurrent && (curIndex = index);
    return index;
  };
    tl.times = times;
  tl.progress(1, true).progress(0, true); // pre-render for performance
  if (config.reversed) {
    tl.vars.onReverseComplete();
    tl.reverse();
  }
  if (config.draggable && typeof(Draggable) === "function") {
    proxy = document.createElement("div")
    let wrap = gsap.utils.wrap(0, 1),
        ratio, startProgress, draggable, dragSnap,
        align = () => tl.progress(wrap(startProgress + (draggable.startY - draggable.y) * ratio)),
        syncIndex = () => tl.closestIndex(true);
    typeof(InertiaPlugin) === "undefined" && console.warn("InertiaPlugin required for momentum-based scrolling and snapping. https://greensock.com/club");
    draggable = Draggable.create(proxy, {
      trigger: items[0].parentNode,
      type: "y",
      onPressInit() {
        gsap.killTweensOf(tl);
        startProgress = tl.progress();
        refresh();
        ratio = 1 / totalHeight;
        gsap.set(proxy, {y: startProgress / -ratio})
      },
      onDrag: align,
      onThrowUpdate: align,
      inertia: true,
      snap: value => {
        let time = -(value * ratio) * tl.duration(),
            wrappedTime = timeWrap(time),
            snapTime = times[getClosest(times, wrappedTime, tl.duration())],
            dif = snapTime - wrappedTime;
        Math.abs(dif) > tl.duration() / 2 && (dif += dif < 0 ? tl.duration() : -tl.duration());
        return (time + dif) / tl.duration() / -ratio;
      },
      onRelease: syncIndex,
      onThrowComplete: syncIndex
    })[0];
    tl.draggable = draggable;
  }
  tl.closestIndex(true);
  onChange && onChange(items[curIndex], curIndex);
    return tl;
}



function horizontalLoop(items, config) {
  items = gsap.utils.toArray(items);
  config = config || {};
  let tl = gsap.timeline({
      repeat: config.repeat,
      paused: config.paused,
      defaults: { ease: "none" },
      onReverseComplete: () => tl.totalTime(tl.rawTime() + tl.duration() * 100),
    }),
    length = items.length,
    startX = items[0].offsetLeft,
    times = [],
    widths = [],
    xPercents = [],
    curIndex = 0,
    pixelsPerSecond = (config.speed || 1) * 100,
    snap = config.snap === false ? (v) => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural
    totalWidth,
    curX,
    distanceToStart,
    distanceToLoop,
    item,
    i;
  gsap.set(items, {
    // convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster.
    xPercent: (i, el) => {
      let w = (widths[i] = parseFloat(gsap.getProperty(el, "width", "px")));
      xPercents[i] = snap(
        (parseFloat(gsap.getProperty(el, "x", "px")) / w) * 100 +
          gsap.getProperty(el, "xPercent")
      );
      return xPercents[i];
    },
  });
  gsap.set(items, { x: 0 });
  totalWidth =
    items[length - 1].offsetLeft +
    (xPercents[length - 1] / 100) * widths[length - 1] -
    startX +
    items[length - 1].offsetWidth *
      gsap.getProperty(items[length - 1], "scaleX") +
    (parseFloat(config.paddingRight) || 0);
  for (i = 0; i < length; i++) {
    item = items[i];
    curX = (xPercents[i] / 100) * widths[i];
    distanceToStart = item.offsetLeft + curX - startX;
    distanceToLoop =
      distanceToStart + widths[i] * gsap.getProperty(item, "scaleX");
    tl.to(
      item,
      {
        xPercent: snap(((curX - distanceToLoop) / widths[i]) * 100),
        duration: distanceToLoop / pixelsPerSecond,
      },
      0
    )
      .fromTo(
        item,
        {
          xPercent: snap(
            ((curX - distanceToLoop + totalWidth) / widths[i]) * 100
          ),
        },
        {
          xPercent: xPercents[i],
          duration:
            (curX - distanceToLoop + totalWidth - curX) / pixelsPerSecond,
          immediateRender: false,
        },
        distanceToLoop / pixelsPerSecond
      )
      .add("label" + i, distanceToStart / pixelsPerSecond);
    times[i] = distanceToStart / pixelsPerSecond;
  }
  function toIndex(index, vars) {
    vars = vars || {};
    Math.abs(index - curIndex) > length / 2 &&
      (index += index > curIndex ? -length : length); // always go in the shortest direction
    let newIndex = gsap.utils.wrap(0, length, index),
      time = times[newIndex];
    if (time > tl.time() !== index > curIndex) {
      // if we're wrapping the timeline's playhead, make the proper adjustments
      vars.modifiers = { time: gsap.utils.wrap(0, tl.duration()) };
      time += tl.duration() * (index > curIndex ? 1 : -1);
    }
    curIndex = newIndex;
    vars.overwrite = true;
    return tl.tweenTo(time, vars);
  }
  tl.next = (vars) => toIndex(curIndex + 1, vars);
  tl.previous = (vars) => toIndex(curIndex - 1, vars);
  tl.current = () => curIndex;
  tl.toIndex = (index, vars) => toIndex(index, vars);
  tl.times = times;
  tl.progress(1, true).progress(0, true); // pre-render for performance
  if (config.reversed) {
    tl.vars.onReverseComplete();
    tl.reverse();
  }
  return tl;
}
*{
    margin: 0;
    padding: 0;
}

.slide-conten{
/*  display: flex;*/
    width: 100%;
    height: 100vh;
    background-color: #000000;
        overflow: hidden;
        gap: 5px;

}


.slide-box{
    display: flex;
  width: 100%;
  height: 100vh;
/*  background-color: #000000;*/
    overflow: hidden;
    gap: 5px;

}

.box-wrapper{
    
  width: 100%;
  height: 100vh;
/*  background-color: yellow;*/
}


.child-box{
    margin:  5px 5px;
    width: 100%;
    height: 300px; 
    background-color: #ffffff;
    border-radius: 10px;
}


section {
  display: block;
}

.cb-tagreel {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}

.cb-tagreel-content {
/*    background-color: yellow;*/
    width: 100%;
    height: 100vh;
      display: flex;
    align-items: center;
    justify-content: center;

}

.cb-tagreel-items {
   overflow: hidden;
  cursor: default;
  width: 100%;
}

.cb-tagreel-row {
  display: flex;
  position: relative;
  text-align: center;
  white-space: nowrap;
}

.cb-tagreel-item {
  position: relative;
  line-height: 100%;
  font-size: 3.75vw;
  flex: 0 0 33%;
/*  padding: 58px 0;*/
/*  text-transform: uppercase;*/
}

.cb-tagreel-item.-stroke {
  color: transparent;
  text-shadow: none;
  -webkit-text-stroke: 2px rgba(0, 0, 0, 0.2);
}

.cb-tagreel-item span {
  position: relative;
  display: inline-block;
  z-index: 1;
}


/**/
<div class="slide-conten">


    <div class="slide-box">
        <div class="box-wrapper">
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
        </div>

        <div class="box-wrapper">
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
        </div>

        <div class="box-wrapper">
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
        </div>

        <div class="box-wrapper">
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
        </div>

        <div class="box-wrapper">
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
        </div>
        <div class="box-wrapper">
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
            <div class="child-box"></div>
        </div>

    </div>
    <section class="cb-tagreel">
        <div class="cb-tagreel-content">
            <div class="cb-tagreel-items" role="marquee">
                <div class="cb-tagreel-row">
                    <div class="cb-tagreel-item"><span>1</span></div>
                    <div class="cb-tagreel-item"><span>2</span></div>
                    <div class="cb-tagreel-item"><span>3</span></div>
                    <div class="cb-tagreel-item"><span>4</span></div>
                    <div class="cb-tagreel-item"><span>5</span></div>
                    <div class="cb-tagreel-item"><span>6</span></div>
                </div>
            </div>
        </div>
    </section>

</div>
<script type="text/javascript" src="https://unpkg.com/gsap@3/dist/gsap.min.js"></script>

javascript html reactjs css-animations gsap
1个回答
0
投票

错误仅由 ESLint 造成。

© www.soinside.com 2019 - 2024. All rights reserved.