使用useEffect操作在不同屏幕上无响应的DOM事件

当前,我正在尝试在Reaction中实现以下视差效果,即我的图像垂直于固定位置,但随文本从左向右移动。

我已经使用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. 定义节的中心,这有助于找到需要停止移动图像并将其移回的位置。
  3. 同样需要处理文本。
  4. 调整窗口大小时,所有内容都以window.resize居中。

沙盒示例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(${0}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(${0}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(${0}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(${0}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(${0}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>

相关文章