切换@property为var后CSS 3D动画异常的原因排查
问题:替换@property实现CSS 3D旋转动画后方块间距异常
我在做一个3D旋转方块的Demo,用@property定义旋转角度变量时动画正常,但Firefox不支持@property,改成直接在@keyframes里修改transform后,原本保持固定间距的方块在动画过程中挤在一起了。
正常运行的代码(仅支持Chrome等支持@property的浏览器)
CSS代码
:root{ --cubeSize: 32vw; } * { margin: 0; padding: 0;} body, html{ height: 100%; width: 100%; overflow: hidden; } body{ display: flex; justify-content: center; align-items: center; height: 100%; } * { perspective: 70vw; transform-style: preserve-3d; } #boxContainer { display: flex; justify-content: center; align-items: center; width: 0; height: 0; position: relative; transform: translateZ(-50vw); } #boxContainer > *:nth-child(1){ background-color: #f00; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : -16vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(2){ background-color: #f40; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : -12vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(3){ background-color: #ff0; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : -8vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(4){ background-color: #8f0; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : -4vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(5){ background-color: #0f0; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : 0; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(6){ background-color: #0f8; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : 4vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(7){ background-color: #0ff; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : 8vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(8){ background-color: #66f; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : 12vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } #boxContainer > *:nth-child(9){ background-color: #f0f; --Xoffset : 0cm; --Yoffset : 0cm; --Zoffset : 16vw; --Xangle : 0deg; --Yangle : 90deg; --Zangle : 0deg; } @property --animAngY { syntax : '<angle>'; initial-value: 0deg; inherits: false; } @property --animAngX { syntax : '<angle>'; initial-value: 0deg; inherits: false; } @property --animAngZ { syntax : '<angle>'; initial-value: 0deg; inherits: false; } #boxContainer > * { --gridColour: #0005; --spacing: 5mm; background-image: repeating-linear-gradient(90deg, var(--gridColour) 0px, var(--gridColour) 1px, #fff0 0px, #fff0 var(--spacing)), repeating-linear-gradient(0deg, var(--gridColour) 0px, var(--gridColour) 1px, #fff0 0px, #fff0 var(--spacing)) ; position: absolute; opacity: 1; display: inline-block; width: var(--cubeSize); height: var(--cubeSize); transform-origin: 50% 50%; transform: rotateX(calc(var(--Xangle) + var(--animAngX))) rotateY(calc(var(--Yangle) + var(--animAngY))) rotateZ(calc(var(--Zangle) + var(--animAngZ))) translateX(var(--Xoffset)) translateY(var(--Yoffset)) translateZ(var(--Zoffset)) ; animation-name: cubeSpin; animation-duration: 10s; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes cubeSpin { to { --animAngX : 1080deg; --animAngY : 360deg; --animAngZ : 720deg; } } #floor{ --gridColour : #8af; --bgColour: #0000; --spacing: 10vw; --zPos: -10vw; --lineWidth: 0.25vw; position: absolute; display: inline-block; width: 500vw; height: 500vw; background-color: #000; border-radius: 50%; background-image: repeating-linear-gradient( 90deg, var(--gridColour) 0vw, var(--gridColour) var(--lineWidth), var(--bgColour) var(--lineWidth), var(--bgColour) var(--spacing) ), repeating-linear-gradient( 0deg, var(--gridColour) 0vw, var(--gridColour) var(--lineWidth), var(--bgColour) var(--lineWidth), var(--bgColour) var(--spacing) ) ; transform: rotateX(90deg) rotateZ(0deg) translateZ(var(--zPos)); animation-name: floorSpin; animation-duration: 10s; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes floorSpin { to { transform: rotateX(90deg) rotateZ(360deg) translateZ(var(--zPos)); } } body{ background-image: linear-gradient( black 0, #1a0450 25%, #1e8ad9 56% ); }
HTML代码
<body> <div id="floor"></div> <div id="boxContainer"> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> <div></div> </div> </body>
修改后出现异常的代码片段
原动画关键帧:
@keyframes cubeSpin { to { --animAngX : 1080deg; --animAngY : 360deg; --animAngZ : 720deg; } }
替换为:
@keyframes cubeSpin { to { transform: rotateX(calc(var(--Xangle) + 1080deg)) rotateY(calc(var(--Yangle) + 360deg)) rotateZ(calc(var(--Zangle) + 720deg)) } }
替换后,方块在动画过程中挤在一起,失去了原本的固定间距。
问题原因
问题出在transform属性的动画覆盖上:
- 原代码中,每个方块的
transform包含旋转(结合变量)+ 位移(translateX/Y/Z),动画仅修改旋转角度变量,位移部分始终保留。 - 修改后,动画的
to状态只定义了旋转部分,完全覆盖了原本的transform属性,丢失了translateX/Y/Z的位移设置——方块们都失去了各自的Z轴偏移,自然挤在一起。
解决办法
在动画关键帧里,必须保留完整的transform属性,包括所有位移和旋转计算:
@keyframes cubeSpin { to { transform: rotateX(calc(var(--Xangle) + 1080deg)) rotateY(calc(var(--Yangle) + 360deg)) rotateZ(calc(var(--Zangle) + 720deg)) translateX(var(--Xoffset)) translateY(var(--Yoffset)) translateZ(var(--Zoffset)) } }
这样动画过程中,每个方块的位移设置不会丢失,就能保持原本的固定间距,同时实现旋转动画,且兼容Firefox等不支持@property的浏览器。
补充说明:
- 底部网格的动画正常,因为它的动画完整保留了所有transform属性值,仅修改了
rotateZ的角度。 - 选择旋转子元素而非父容器,是为了避免子元素被投影到父平面,这个设计是合理的。
内容的提问来源于stack exchange,提问作者Jacob Ewing
相关产品推荐
相关产品推荐

