Search code examples
csscss-animationscss-variables

Why, when I switch from @property values to var values, does my CSS animation misbehave?


I've been working on expanding my CSS knowledge, and made a simple demo of 3d transformations, showing a set of squares rotating in 3d on a plane.

I initially wrote it using @property definitions of the rotation angles, and later realised I could do it with simple var definitions. When I change it though, the squares, which should stay a consistent distance apart, start flattening together in the animation cycle. I don't see why it does this and am hoping someone can point me in the right direction.

Here it is working. Note that Firefox doesn't handle @property functions (hence the need to change it)

: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);
        display: inline-block;
        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% );
}
<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>

If I replace this part:

@keyframes cubeSpin {
        to {
                --animAngX : 1080deg;
                --animAngY : 360deg;
                --animAngZ : 720deg;
        }
}  

With this:

@keyframes cubeSpin {
        to {
                transform:
                        rotateX(
                                calc(var(--Xangle) + 1080deg)
                        )
                        rotateY(
                                calc(var(--Yangle) + 360deg)
                        )
                        rotateZ(
                                calc(var(--Zangle) + 720deg)
                        )
                        
        }
}      

Then this is the outcome

: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);
        display: inline-block;
        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 {
            transform:
                    rotateX(
                            calc(var(--Xangle) + 1080deg)
                    )
                    rotateY(
                            calc(var(--Yangle) + 360deg)
                    )
                    rotateZ(
                            calc(var(--Zangle) + 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% );
}
<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>

As you can see in those demos, the first one works as I expect, and in the second, the rotating planes collapse together throughout the entire animation

Edit:

To expand a bit on what it does:

  • The bottom grid is just a div that's spinning, it works fine and is indeed using the more widely supported CSS changes in the animation.

  • In the middle of the screen is a single div with no width or height, it contains the nine divs that are the rotating layers

  • The layers themselves are being rotated, not the parent div as - if I understand correctly - rotating the parent div will render those children as being projected on to the parent's plane.

  • All of the layers are being transformed by the same animation "cubeSpin", as that is tied to all childen of the div #boxContainer.


Solution

  • You are missing the translate part of the transform

    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)
       ) 
    

    :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);
            display: inline-block;
            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 {
                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)
                    ) 
            }
    }
    
    #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% );
    }
    <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>