CSS 3D等轴测立方体——旋转至特定角度时部分面消失
CSS 3D等轴测立方体——旋转至特定角度时部分面消失
我太懂这种挠头的感受了!辛辛苦苦搭的3D立方体,转着转着某个面突然就“隐身”了——DOM里明明还在,就是看不见,简直让人怀疑浏览器是不是在搞恶作剧😤。你猜perspective: none是元凶,这点真的说到点子上了,咱们一步步把问题解决掉。
为啥面会突然“消失”?
核心问题就是perspective: none——这个设置直接关掉了3D场景的透视深度计算。浏览器在渲染3D元素时,本来靠透视来判断不同面的层级远近,现在没了这个参考,就会乱判层级:比如本该在前面的面被后面的面完全挡住,或者某个面的“背面”被backface-visibility: hidden给藏了起来,看起来就像消失了一样。
具体解决方案(附调整后的完整代码)
我针对你的代码做了3个关键修改,既保留等轴测效果,又能让所有面在旋转时正常显示:
- 把
perspective: none换成合适的透视值,让浏览器能正确计算3D层级 - 把
backface-visibility: hidden改成visible,避免面的背面被误隐藏 - 补全了缺失的底面样式,让立方体结构更完整
<!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偏移,就能轻松让它显示在正确层级啦😎




