You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

切换@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 02:50:32