视差滚动

flyKot2025/10/22

效果预览

The Ultimate
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`)
})