使用 useEffect 操纵 DOM 事件在不同屏幕上没有响应

Using useEffect to manipulate DOM events not Responsive on different screens

目前我正在尝试在 React 中实现以下视差效果,其中我的图像在垂直方向上处于固定位置,但随着文本从左向右移动。

我使用 useEffect 实现了这一点,我获取了总高度像素并相应地移动了我的组件。这个问题是它在我的屏幕上看起来很完美,但是一旦我将它调整到更大或更小的屏幕,布局就会变得卡顿。无论如何有同样的效果但响应友好。随意编辑 CodeSandBox

CodeSandBox(全屏查看以获得更好的参考):https://codesandbox.io/s/stoic-rumple-s8cr6?file=/src/App.js

代码:

export default function App() {
  const [index, setIndex] = useState(false);
  const [display, setDisplay] = useState(false);
  const [number, setNumber] = useState(false);
  const [screen, setScreen] = useState(false);

  useEffect(function onFirstMount() {
    const changeBackground = () => {
      let value = window.scrollY;
      console.log(value);
      let img = document.getElementById("moveLeft");
      let text = document.getElementById("moveUp");
      let text2 = document.getElementById("text2");
      let text3 = document.getElementById("text3");
      let text4 = document.getElementById("text4");

      let imgWidth = 280;

      text.style.marginTop = "-" + value * 0.5 + "px";
      text2.style.transform = `translateX(${value * 1.3}px)`;
      text3.style.transform = `translateX(-${value * 1.3}px)`;
      text4.style.transform = `translateX(${value * 1.3}px)`;

      if (value > 600) {
        img.style.transform = `translateX(${value * 0.8 - 480 - imgWidth}px)`;
      } else {
        img.style.transform = `translateX(-${value * 0.5}px)`;
      }

      if (value > 1400) {
        img.style.transform = `translateX(${
          -1 * (value * 0.8 - 1120) + 80 + imgWidth
        }px)`;
      }

      if (value > 1700) {
        setNumber(true);
      } else {
        setNumber(false);
      }

      if (value > 1100) {
        setIndex(true);
      } else {
        setIndex(false);
      }
    };
    window.addEventListener("scroll", changeBackground);
    return () => window.removeEventListener("scroll", changeBackground);
  }, []);

  return (
    <>
      <div className="App">
        <div className="middletext" id="moveUp" style={{ zIndex: "9" }}>
          Random Text
        </div>

        <div class="inflow">
          <div class="positioner">
            <div class="fixed" style={{ zIndex: "11" }}>
              <div id="moveLeft">
                <img
                  alt="passport"
                  src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
                />
              </div>
            </div>
          </div>
          <div className="halfWindow" style={{ zIndex: "8" }}></div>
          <div>
            <div class="fixedText" style={{ zIndex: "7" }}>
              <div id="text2" className="text2">
                Random Text
              </div>
            </div>
          </div>
          <div className="secondhalfWindow" style={{ zIndex: index ? "10" : "6" }}></div>
          <div>
            <div
              class="secondfixedText"
              style={{
                zIndex: index ? "9" : "5",
                display: "block"
              }}
            >
              <div id="text3" className="text3">
                Random Text 2
              </div>
            </div>
          </div>

          <div className="thirdhalfWindow" style={{ zIndex: "4" }}></div>
          <div>
            <div
              class="thirdfixedText"
              style={{
                zIndex: number ? "10" : "3"
              }}
            >
              <div id="text4" className="text4">
                Random Text 3
              </div>
            </div>
          </div>
        </div>
      </div>
    </>

使用 vw 代替 px 应该可行,但计算非常复杂。

你有两个选择。

  1. 使此效果仅适用于您想要的屏幕宽度尺寸
  2. 根据浏览器的 window 大小计算护照应该移动多少像素。

后一种解决方案会使您的应用程序难以维护,因为要考虑的设备太多。

如何获得window尺寸

  const [size, setSize] = useState({
    x: window.innerWidth,
    y: window.innerHeight
  });
  const updateSize = () =>
    setSize({
      x: window.innerWidth,
      y: window.innerHeight
    });
  useEffect(() => (window.onresize = updateSize), []);

解决方案是重构您的代码。

  1. 将所有元素放在中心并创建四个部分。
  2. 定义部分的中心,这有助于找到需要停止移动图像并将其移回的位置。
  3. 同样需要对文字进行处理。
  4. 当 window 调整大小时,所有内容都以 window.resize.
  5. 居中

沙盒示例link

function App() {
  const [screen, setScreen] = React.useState(false);

  const ref = React.useRef(null);

  // Reduce value if want the image to be closer to the edges
  // otherwise to the center
  const setImageLimitMovement = 2;

  const setTextLimitMovement = 4;
  const opacityRange = 400;
  // Speed text movement
  const speed = 2; // .5

  React.useEffect(() => {
    window.addEventListener("resize", () => {
      if (window.innerWidth !== 0) {
        setScreen(window.innerWidth);
      }
    });
  }, []);

  React.useEffect(() => {
    const app = [...ref.current.children];
    const titles = app.filter((el) => el.matches(".titles") && el);
    const blocks = app.filter((el) => el.matches(".blocks") && el);
    const img = app.find((el) => el.matches("#passport") && el);

    // Get the center point of  blocks in an array
    const centerPoints = blocks.map((blockEl, idx) => {
      const blockindex = idx + 1;
      const blockHeight = Math.floor(blockEl.getBoundingClientRect().height);
      const blockHalf = blockHeight / 2;
      return blockHeight * blockindex - blockHalf;
    });

    const leftMoveLimitImg = -centerPoints[0] / setImageLimitMovement;
    const rightMoveLimitImg = centerPoints[0] / setImageLimitMovement;

    const textLimit = centerPoints[0] / setTextLimitMovement;

    const changeBackground = () => {
      const value = window.scrollY;

      titles[0].style.transform = `translateY(-${value * speed}px)`;
      // IMAGE BOUNCE
      // Move to <==
      if (centerPoints[0] > value) {
        img.style.transform = `translateX(-${
          value * (1 / setImageLimitMovement)
        }px)`;

        titles[1].style.transform = `translateX( ${
          0 + value / setTextLimitMovement
        }px)`;
        titles[1].style.opacity = value / opacityRange;
        return;
      }

      // Move to ==>
      if (centerPoints[1] > value) {
        const moveTextToRight =
          centerPoints[1] / setTextLimitMovement - textLimit;
        const hideText = centerPoints[0] / opacityRange;
        const checkDirection = Math.sign(
          textLimit + (textLimit - value / setTextLimitMovement)
        );

        const moveImageToRight =
          (value - centerPoints[0]) / setImageLimitMovement;
        img.style.transform = `translateX(${
          leftMoveLimitImg + moveImageToRight
        }px)`;

        if (checkDirection === -1) {
          titles[1].style.opacity = 0;
          titles[1].style.transform = `translateX([=10=]px)`;

          titles[2].style.opacity =
            Math.abs(hideText - value / opacityRange) - 1;
          titles[2].style.transform = `translateX(${
            moveTextToRight - value / setTextLimitMovement
          }px)`;
          return;
        }
        if (checkDirection === 1) {
          titles[1].style.opacity = 1 + (hideText - value / opacityRange);
          titles[1].style.transform = `translateX(${
            textLimit + (textLimit - value / setTextLimitMovement)
          }px)`;

          titles[2].style.opacity = 0;
          titles[2].style.transform = `translateX([=10=]px)`;
        }
        return;
      }

      // Move to <==
      if (centerPoints[2] > value) {
        const moveTextToLeft =
          centerPoints[2] / setTextLimitMovement - textLimit;
        const hideText = centerPoints[1] / opacityRange;
        const checkDirection = Math.sign(
          moveTextToLeft - value / setTextLimitMovement
        );

        const moveImageToLeft =
          (-value + centerPoints[1]) / setImageLimitMovement;
        img.style.transform = `translateX(${
          rightMoveLimitImg + moveImageToLeft
        }px)`;

        if (checkDirection === -1) {
          titles[2].style.opacity = 0;
          titles[2].style.transform = `translateX([=10=]px)`;

          titles[3].style.opacity =
            Math.abs(hideText - value / opacityRange) - 1;
          titles[3].style.transform = `translateX(${Math.abs(
            moveTextToLeft - value / setTextLimitMovement
          )}px)`;
        }

        if (checkDirection === 1) {
          titles[2].style.opacity = 1 + (hideText - value / opacityRange);
          titles[2].style.transform = `translateX(-${
            moveTextToLeft - value / setTextLimitMovement
          }px)`;

          titles[3].style.opacity = 0;
          titles[3].style.transform = `translateX([=10=]px)`;
        }
        return;
      }

      // Move to ==>
      if (centerPoints[3] > value) {
        const moveTextToRight =
          centerPoints[3] / setTextLimitMovement - textLimit;
        const hideText = centerPoints[2] / opacityRange;
        const checkDirection = Math.sign(
          moveTextToRight - value / setTextLimitMovement
        );

        const moveImageToRight =
          (value - centerPoints[2]) / setImageLimitMovement;
        img.style.transform = `translateX(${
          leftMoveLimitImg + moveImageToRight
        }px)`;

        if (checkDirection === -1) {
          titles[3].style.opacity = 0;
          titles[3].style.transform = `translateX([=10=]px)`;
        }
        if (checkDirection === 1) {
          titles[3].style.opacity = 1 + (hideText - value / opacityRange);
          titles[3].style.transform = `translateX(${
            moveTextToRight - value / setTextLimitMovement
          }px)`;
        }
        return;
      }

      window.requestAnimationFrame(changeBackground);
    };
    window.addEventListener("scroll", changeBackground);
    return () => window.removeEventListener("scroll", changeBackground);
  }, [screen]);

  return (
    <div className="App" ref={ref}>
      <h1 id="title" className="titles">
        Random Title
      </h1>
      <section id="block1" className="blocks">
        <h4>Block 1</h4>
      </section>
      <figure id="passport">
        <img
          alt="passport"
          src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
        />
      </figure>
      <h2 id="text1" className="titles text1">
        Random Text 1
      </h2>
      <section id="block2" className="blocks">
        <h4>Block 2</h4>
      </section>
      <h2 id="text2" className="titles text2">
        Random Text 2
      </h2>
      <section id="block3" className="blocks">
        <h4>Block 3</h4>
      </section>
      <h2 id="text3" className="titles text3">
        Random Text 3
      </h2>
      <section id="block4" className="blocks">
        <h4>Block 4</h4>
      </section>
    </div>
  );
}


const rootElement = document.getElementById("root");
ReactDOM.render( <
  App / > ,
  rootElement
);
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
.App {
  font-family: sans-serif;
  width: 100%;
  background-color: hsl(220, 65%, 16%);
}
figure {
  width: 280px;
  height: max-content;
  position: fixed;
  inset: 0;
  margin: auto;
  z-index: 100;
}
img {
  width: 100%;
}

.blocks {
  height: 100vh;
  display: flex;
  position: relative;
  grid-column: 1 / -1;
  color: grey;
}

.titles {
  width: max-content;
  height: max-content;
  position: fixed;
  inset: 0;
  margin: auto;
  color: white;
  z-index: 99;
}

h1 {
  font-size: 3.5em;
}
h2 {
  display: flex;
  opacity: 0;
  font-size: 2.5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>