目前我正试图在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应该可以,但是计算非常复杂。
一个解决方案是重构你的代码。
- 将所有元素放置在中心并创建四个部分。
- 定义部分的中心,这有助于找到需要停止移动图像的地方,并将其移动回来。
- 以同样的方式需要处理文本。
- 当窗口大小调整时,所有内容都以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>
您有两个选择。
- 使此效果仅适用于您想要的屏幕宽度
- 计算多少像素你应该移动护照取决于浏览器的窗口大小。
后一种解决方案可能会使您的应用程序难以维护,因为需要考虑的设备太多了。
如何获取窗口大小
const [size, setSize] = useState({
x: window.innerWidth,
y: window.innerHeight
});
const updateSize = () =>
setSize({
x: window.innerWidth,
y: window.innerHeight
});
useEffect(() => (window.onresize = updateSize), []);