You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

CSS 3D等轴测立方体——旋转至特定角度时部分面消失

CSS 3D等轴测立方体——旋转至特定角度时部分面消失

我太懂这种挠头的感受了!辛辛苦苦搭的3D立方体,转着转着某个面突然就“隐身”了——DOM里明明还在,就是看不见,简直让人怀疑浏览器是不是在搞恶作剧😤。你猜perspective: none是元凶,这点真的说到点子上了,咱们一步步把问题解决掉。

为啥面会突然“消失”?

核心问题就是perspective: none——这个设置直接关掉了3D场景的透视深度计算。浏览器在渲染3D元素时,本来靠透视来判断不同面的层级远近,现在没了这个参考,就会乱判层级:比如本该在前面的面被后面的面完全挡住,或者某个面的“背面”被backface-visibility: hidden给藏了起来,看起来就像消失了一样。

具体解决方案(附调整后的完整代码)

我针对你的代码做了3个关键修改,既保留等轴测效果,又能让所有面在旋转时正常显示:

  1. perspective: none换成合适的透视值,让浏览器能正确计算3D层级
  2. backface-visibility: hidden改成visible,避免面的背面被误隐藏
  3. 补全了缺失的底面样式,让立方体结构更完整
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fix Isometric Cube Disappearing Faces</title>
    <style>
:root {
    --s: min(20vmin, 340px);
    --isoX: -35.264deg;
    --isoY: 45deg;
    --lift-top: 0px;
    --fold-left: 0deg;
    --fold-right: 0deg;
    --fold-back: 0deg;
    --glass-rgb: 47 107 255;
    --glass-blur: 8px;
    --glass-sat: 160%;
    --glass-bright: 1.05;
}

.stage {
    width: 100%;
    height: 70vh;
    display: block;
    padding: 12px;
    box-sizing: border-box;
}

.controls {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 10px;
    font: 14px system-ui, sans-serif
}

.controls input[type="range"] {
    width: 320px
}

.cube-viewport {
    position: relative;
    width: 100%;
    height: calc(100% - 42px);
    overflow: visible;
    /* 关键修改1:替换none为大数值透视,既保证层级计算,又接近纯等轴测 */
    perspective: 2000px;
    perspective-origin: 50% 50%;
}

.cube-camera {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transform-style: preserve-3d;
    transform: rotateX(var(--isoX)) rotateY(var(--isoY));
}

.cube-world {
    position: relative;
    width: var(--s);
    height: var(--s);
    transform-style: preserve-3d;
}

.cube {
    position: absolute;
    inset: 0;
    transform-style: preserve-3d;
    will-change: transform;
}

.face {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--s);
    height: var(--s);
    transform-origin: center;
    transform-style: preserve-3d;
    /* 关键修改2:把hidden改成visible,避免面折叠时背面被隐藏 */
    backface-visibility: visible;
    border-radius: 12px;
    overflow: hidden;
    background: linear-gradient(135deg, rgb(var(--glass-rgb) / 0.22), rgb(var(--glass-rgb) / 0.08)), radial-gradient(120% 120% at 0% 0%, rgb(255 255 255 / 0.28), transparent 60%);
    backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)) brightness(var(--glass-bright));
    box-shadow: 0 8px 24px rgb(0 0 0 / 0.28), inset 0 0 0 1px rgb(255 255 255 / 0.06);
}

.face::after {
    content: "";
    position: absolute;
    inset: 0;
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06);
    pointer-events: none;
}

/* 补全底面样式,让立方体结构完整 */
#bottom {
    transform: translate(-50%, -50%) rotateX(90deg) translateZ(calc(var(--s) * -0.5));
}

#right {
    transform: translate(-50%, -50%) rotateY(-90deg) translateZ(calc(var(--s) * -0.5)) rotateX(var(--fold-right));
    transform-origin: 50% 100% 0;
}

#left {
    transform: translate(-50%, -50%) rotateY(-90deg) translateZ(calc(var(--s) * 0.5)) rotateX(var(--fold-left));
    transform-origin: 50% 100% 0;
}

#top {
    transform: translate(-50%, -50%) rotateX(90deg) translateZ(calc(var(--s) * 0.5 + var(--lift-top)));
}

#back {
    transform: translate(-50%, -50%) translateZ(calc(var(--s) * -0.5)) rotateY(var(--fold-back));
}
    </style>
</head>
<body>
    <div class="stage">
        <div class="controls">
            <input type="range" id="progress" min="0" max="100" value="0">
            <output id="progressOut">0%</output>
        </div>
        <div class="cube-viewport">
            <div class="cube-camera">
                <div class="cube-world">
                    <div id="cube" class="cube">
                        <div id="top" class="face"></div>
                        <div id="left" class="face"></div>
                        <div id="right" class="face"></div>
                        <div id="back" class="face"></div>
                        <div id="bottom" class="face"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
(() => {
    const onReady = (fn) => {
        const wait = () => (window.anime && document.readyState !== 'loading') ? fn() : setTimeout(wait, 40);
        wait();
    };

    onReady(() => {
        const stage = document.querySelector('.stage');
        const cube = stage.querySelector('#cube');
        const topFace = stage.querySelector('#top');
        const leftFace = stage.querySelector('#left');
        const rightFace = stage.querySelector('#right');
        const backFace = stage.querySelector('#back');
        const range = stage.querySelector('#progress');
        const out = stage.querySelector('#progressOut');

        if (!cube || !topFace || !leftFace || !rightFace || !backFace || !range) return;

        const getS = () => parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--s')) || 280;
        const S = getS();
        const LIFT_PX = Math.round(S * 0.7);
        const FOLD_DEG = 90;

        topFace.style.setProperty('--lift-top', '0px');
        leftFace.style.setProperty('--fold-left', '0deg');
        rightFace.style.setProperty('--fold-right', '0deg');
        backFace.style.setProperty('--fold-back', '0deg');

        const makeVarTL = (el, name, from, to, unit, duration = 1000, easing = 'linear') => {
            const holder = { v: from };
            return anime.timeline({ autoplay: false, duration, easing })
                .add({
                    targets: holder,
                    v: to,
                    update: () => el.style.setProperty(name, holder.v + unit)
                });
        };

        const tlRotate = anime.timeline({ autoplay: false, duration: 1600, easing: 'linear' })
            .add({
                targets: cube,
                rotateX: [0, 360],
                rotateY: [0, 360]
            });

        const tlTopLift = makeVarTL(topFace, '--lift-top', 0, LIFT_PX, 'px', 900);
        const tlLeft = makeVarTL(leftFace, '--fold-left', 0, -FOLD_DEG, 'deg', 700);
        const tlRight = makeVarTL(rightFace, '--fold-right', 0, FOLD_DEG, 'deg', 700);
        const tlBack = makeVarTL(backFace, '--fold-back', 0, FOLD_DEG, 'deg', 700);

        const seg = (g, a, b) => {
            if (g <= a) return 0;
            if (g >= b) return 1;
            return (g - a) / (b - a);
        };

        const seekAll = (g) => {
            const p1 = seg(g, 0.00, 0.25);
            const p2 = seg(g, 0.25, 0.50);
            const p3 = seg(g, 0.50, 0.75);
            const p4 = seg(g, 0.75, 1.00);

            tlRotate.seek(p1 * tlRotate.duration);
            tlTopLift.seek(p2 * tlTopLift.duration);
            tlLeft.seek(p3 * tlLeft.duration);
            tlRight.seek(p3 * tlRight.duration);
            tlBack.seek(p4 * tlBack.duration);
        };

        const onInput = () => {
            const g = (parseFloat(range.value) || 0) / 100;
            out.value = Math.round(g * 100) + '%';
            seekAll(g);
        };

        range.addEventListener('input', onInput);
        onInput();
    });
})();
    </script>
</body>
</html>

额外小技巧

如果你觉得透视带来的轻微近大远小变形不舒服,可以把perspective的值调得更大(比如3000px),数值越大,透视变形越弱,越接近纯等轴测的扁平化效果。要是某个角度还是有小问题,直接微调对应面的translateZ值加个几px偏移,就能轻松让它显示在正确层级啦😎

火山引擎 最新活动