'Problem with animation-fill-mode on iOS when changing orientation

I've created an animation to move an element into view when the user clicks somewhere. The element is styled to be offscreen. When the animation is run, it moves the element onscreen, and uses animation-fill-mode: forwards to keep the element positioned after the animation ends. With a second click, the element is sent back offscreen again with another animation.

The problem is on iOS (tested on 14.6), when the user rotates the device - for some reason, the end values are not recalculated, so the element ends up wrongly positioned on the screen. You can see this explicitly when the "send offscreen" animation is triggered - the element jumps to the supposed animation start position, which ought to have been the same as the previous animation's end point. There's no jump when the animations are cycled

The following image shows this in action; after the device rotations, notice how the position of the red box is in the wrong position: when going from portrait to landscape, the box is too far to the right, when going from landscape to portrait, the box is too far to the left. screencap of iphone

The problem does not occur on Android with Chrome - the box is repositioned correctly.

Below is the code used to generate the image above.

Is this just a bug in iOS, or is there something I'm doing wrong?

Update: Seems this problem is actually not just limited to mobile: on desktop Safari, when you resize the browser window the same problem occurs - rather than readjusting the element's position due to the vw units, it somehow shifts the element. Chrome and Firefox behave as expected.

<html>

<head>
    <title>animation test</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        label {
            font-size: 24px;
        }
        .elem {
            position: fixed;
            bottom: -40vw;
            left: -40vw;
            transform: rotate3d(0, 0, 1, 45deg);
            transform-origin: 50% 100%;
            width: 30vw;
            height: 43vw;
            background-color: red;
        }

        .anim--in {
            animation: anim-in 1s;
            animation-fill-mode: forwards;
        }

        .anim--out {
            animation: anim-out 1s;
            animation-fill-mode: backwards;
        }

        @keyframes anim-in {
            0% {
                bottom: -40vw;
                left: -40vw;
            }
            100% {
                bottom: -2vw;
                left: -15vw;
            }
        }

        @keyframes anim-out {
            0% {
                bottom: -2vw;
                left: -15vw;
            }
            100% {
                bottom: -40vw;
                left: -40vw;
            }
        }
    </style>
</head>
<body>
    <label><input id="trigger" type="checkbox">Tap me to animate</label>

    <div class="elem"></div>

    <script>
        const trigger = document.getElementById("trigger");
        trigger.addEventListener("click", () => {
            const elem = document.getElementsByClassName("elem")[0];
            if (elem.classList.contains("anim--in")) {
                elem.classList.remove("anim--in");
                elem.classList.add("anim--out");
            } else {
                elem.classList.remove("anim--out");
                elem.classList.add("anim--in");
            }
            console.log("click!");
        });
    </script>
</body>
</html>


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source