视差滚动
flyKot2025/10/22
效果预览
The Ultimate
development studio
development studio
We build beautiful products with the latest technologies and
frameworks. We are a team of passionate developers and designers
that love to build amazing products.















分析
根据滚动距离百分比设置透明度
根据滚动距离百分比设置旋转角度
视差效果
图片方格的滚动速度要比正常滚动速度更快的 比如正常滚动距离[0,400]要映射为[-400,400]
页面结构
<div class="scroll_container">
<div class="scroll_content">
<div class="context">
<div class="hero_title">
The Ultimate
<br>
development studio
</div>
<p style="text-align:left;width:405px">
We build beautiful products with the latest technologies and
<br>
frameworks. We are a team of passionate developers and designers
<br>
that love to build amazing products.
</p>
</div>
<div class="img_container">
<div class="img_scroll_reverse">
<div class="img_box"><img src="./first/cremedigital.png" alt="" srcset=""></div>
<div class="img_box"><img src="./first/goldenbellsacademy.png" alt="" srcset=""></div>
<div class="img_box"><img src="./first/invoker.png" alt="" srcset=""></div>
<div class="img_box"><img src="./first/renderwork.png" alt="" srcset=""></div>
<div class="img_box"><img src="./second/algochurn.png" alt=""></div>
</div>
<div class="img_scroll">
<div class="img_box"> <img src="./second/aceternityui.png" alt=""></div>
<div class="img_box"> <img src="./second/algochurn.png" alt=""></div>
<div class="img_box"> <img src="./second/editorially.png" alt=""></div>
<div class="img_box"> <img src="./second/pixelperfect.png" alt=""></div>
<div class="img_box"> <img src="./first/invoker.png" alt="" srcset=""></div>
</div>
<div class="img_scroll_reverse">
<div class="img_box"> <img src="./third/cursor.png" alt=""></div>
<div class="img_box"> <img src="./third/moonbeam.png" alt=""></div>
<div class="img_box"> <img src="./third/renderwork.png" alt=""></div>
<div class="img_box"> <img src="./third/rogue.png" alt=""></div>
<div class="img_box"> <img src="./second/algochurn.png" alt=""></div>
<div class="img_box"> <img src="./first/invoker.png" alt="" srcset=""></div>
</div>
</div>
</div>
</div>
css
body,
html {
padding: 0;
margin: 0;
width: 100vw;
height: 100vh;
overflow-x: hidden;
--primary-gap: 80px;
--opicity: 0.2;
--t-y: -700px;
--z: 20deg;
--x: 15deg;
--s: 0;
}
.scroll_container {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
}
.scroll_content {
padding: 0;
margin: 0;
width: 100%;
position: relative;
z-index: 1;
height: 300vh;
perspective: 1000px;
}
.context {
width: 100%;
padding: 14px 16px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
margin-top: 100px;
z-index: 3;
}
.context .hero_title {
text-align: left;
font-size: 41px;
line-height: 1.2;
font-weight: 600;
outline-color: color-mix(in oklab, #09090b 50%, transparent);
}
.img_container {
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
z-index: 2;
row-gap: var(--primary-gap);
transform: translateY(var(--t-y)) rotateX(var(--x)) rotateZ(var(--z));
opacity: var(--opicity);
transition: transform 0.3s;
will-change: transform, opacity;
}
.img_scroll,
.img_scroll_reverse {
display: flex;
justify-content: center;
align-items: center;
gap: var(--primary-gap);
transition: transform 0.3s;
will-change: transform;
}
.img_box {
width: 100%;
height: 100%;
position: relative;
transition: all 0.2s ease;
cursor: pointer;
}
.img_box::after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 3;
}
.img_box:hover {
transform: translateY(-18px);
}
.img_box:hover::after {
background-color: #c4c4c4;
opacity: 0.5;
}
.img_scroll img,
.img_scroll_reverse img {
z-index: 2;
width: 30rem;
height: calc(.25rem * 96);
}
.img_scroll {
transform: translateX(var(--s));
}
.img_scroll_reverse {
transform: translateX(calc(-1 * var(--s)));
flex-direction: row-reverse;
}
js
const scroll_container = document.querySelector(".scroll_container")
const body = document.querySelector("body")
const targetMargin = 700
const totalDiff = 400
let oldScrollTop = 0
const inputMin = 0;
const inputMax = 600;
const outputMin = -700;
const outputMax = 500;
// 映射
function mapRange(value) {
return (value / inputMax) * (outputMax - outputMin) + outputMin;
}
scroll_container.addEventListener("scroll", (e) => {
const scrollTop = e.target.scrollTop
oldScrollTop = scrollTop
const rate = Math.min(scrollTop / totalDiff, 1)
const c = mapRange(scrollTop)
if (c <= targetMargin) {
body.style.setProperty("--t-y", `${c}px`)
}
body.style.setProperty("--x", `${15 * (1 - rate)}deg`)
body.style.setProperty("--z", `${20 * (1 - rate)}deg`)
body.style.setProperty("--opicity", Math.max(1 * rate, 0.2))
body.style.setProperty("--s", `${scrollTop * 0.3}px`)
})
