堆叠卡片翻页效果

flyKot2025/10/27

效果预览

These cards are amazing, I want to use them in my project. Framer motion is a godsend ngl tbh fam 🙏
Manu Arora
Senior Software Engineer
The first rule ofFight Club is that you do not talk about fight club. The second rule ofFight club is that you DO NOT TALK about fight club.
Elon Musk
Senior Shitposter
I dont like this Twitter thing, deleting it right away because yolo. Instead, I would like to call it X.com so that it can easily be confused with adult sites.
Manu Arora
Senior Shitposter

分析

  • 堆叠效果的实现

translateZ 实现 3d效果

层层缩小效果可以使用 scale 来实现 在有设置 perspective 的前提下,可以去掉 scale

  • 翻页动画的实现

翻页实际上就是 z-indextranslateZ 的不停变化 如果没设置 perspective 透视效果 那就还需要修改 scale

HTML

    <div class="stick_card_box">
        <div class="stick_card_c">
            <div class="stick_card_box_desc">
                These cards are amazing, I want to use them in my project. Framer motion is
                a godsend ngl tbh fam 🙏</div>
            <div class="stick_card_box_stitle">
                <div>Manu Arora</div>
                <div>Senior Software Engineer</div>
            </div>
        </div>
        <div class="stick_card_c">
            <div class="stick_card_box_desc">
                The first rule ofFight Club is that you do not talk about fight club. The second rule ofFight club is
                that you DO NOT TALK about fight club.
            </div>
            <div class="stick_card_box_stitle">
                <div>Elon Musk</div>
                <div>Senior Shitposter</div>
            </div>
        </div>
        <div class="stick_card_c">
            <div class="stick_card_box_desc">
                I dont like this Twitter thing, deleting it right away because yolo. Instead, I would like to call it
                X.com so that it can easily be confused with adult sites.
            </div>
            <div class="stick_card_box_stitle">
                <div>Manu Arora</div>
                <div>Senior Shitposter</div>
            </div>
        </div>
    </div>

CSS

body,
html {
    padding: 0;
    margin: 0;
    width: 100vw;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.stick_card_box {
    position: relative;
    height: 500px;
    width: 500px;
    perspective: 600px;
}

.stick_card_c {
    position: absolute;
    left: 0;
    top: 0;
    padding: 14px;
    height: 240px;
    width: 384px;
    border-radius: 24px;
    border: 1px solid #e4e4e4;
    background-color: #fff;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    transition: transform 0.3s;
    will-change: transform;
    box-shadow: 0 20px 25px -5px color-mix(in oklab, color-mix(in oklab, #000 10%, transparent) 100%, transparent), 0 8px 10px -6px color-mix(in oklab, color-mix(in oklab, #000 10%, transparent) 100%, transparent);
}

.stick_card_box_desc {
    box-sizing: border-box;
    line-height: 24px;
    word-spacing: 6px;
}

.stick_card_box_stitle :nth-child(1) {
    color: #747474;
    font-weight: 500;
}

.stick_card_box_stitle :nth-child(2) {
    color: #a1a1a1;
    font-weight: 400;
}

.stick_card_drump_c {
    animation: stick_card_drump 0.2s ease forwards;
}

@keyframes stick_card_drump {
    0% {
        top: 0;
    }

    50% {
        top: -15px;
    }

    100% {
        top: 0;
    }
}

JavaScript

const cards = document.querySelectorAll(".stick_card_c")

let range = [0, 1, 2]


function updateElement() {
    const temp = range.shift()
    range.push(temp)
    cards.forEach((ele, index) => {
        const idx = range.findIndex(i => i === index)
        // ele.style.transform = `scale(${1 - 0.1 * idx}) translateY(-${10 * idx}%) translateZ(${idx}rem)`
        const zOffset = -idx * 40;
        ele.style.transform = `translateY(-${10 * idx}%) translateZ(${zOffset}px)`
        ele.style.zIndex = 3 - idx
        if (idx === 0) {
            ele.classList.add("stick_card_drump_c")
        } else {
            ele.classList.remove("stick_card_drump_c")
        }
    })
}
updateElement()
setInterval(updateElement, 1000)