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

JJM*_*M50 5 javascript css parallax css-animations reactjs

目前,我正在尝试在 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>
    </>
Run Code Online (Sandbox Code Playgroud)

使用 vw 而不是 px 应该可以工作,但计算确实很复杂。

Ant*_*ton 3

For a solution is refactoring your code.

  1. to place all the elements in the center and create four sections.
  2. define the center of the sections this help to find where need stop move image and move it back.
  3. in the same way need to do with text.
  4. 当调整窗口大小时,所有内容都以window.resize为中心。

沙盒示例链接

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
);
Run Code Online (Sandbox Code Playgroud)
* {
  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;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)