htmlcsssafaricss-animationsopacity

Safari ignoring opacity change in CSS animation


This always worked but I recently checked a website on iOS Safari and noticed a CSS effect now didn't work, I guess after an update. Confirmed this also didn't work on Safari on my laptop. It does work as it should in Chrome and Firefox.

Basically the animation should run then then opacity: 0; is set to essentially hide the div once complete as it's no longer needs to be visible (it's a loading effect).

I've included a simplified version using coloured boxes - as you can see it will not honour the opacity change in the final step of the animation in Safari.

The proper animate does scale, so I've left that in but aware it doesn't make sense out of context here - but at least it shows the rest of the declarations are honoured.

Does anyone have an idea what's causing this all of a sudden? Thanks in advance!

.pixel-load {
  background: #f00;
  display: block;
  height: 400px;
  width: 300px;
  }
  
.pixel-load__preload {
  background: #00f;
  display: block;
  height: 100%;
  width: 100%;
}


.pixel-load.loaded .pixel-load__preload {
    animation: loaded .32s .16s steps(1, end) both;
}

@keyframes loaded {
    0% {
        scale: 1.6;
    }

    33% {
        scale: 1.2;
    }

    66% {
        scale: 1;
    }

    100% {
        opacity: 0;
        scale: 1;
        z-index: 1;
    }
}
<div class="pixel-load loaded">
  <div class="pixel-load__preload"></div>
</div>


Solution

  • There does seem to have been a change in Safari as you suspected.

    On an iPad, IOS16.7, the behavior is like that on Edge//Chrome on Windows 11. That is, there is an almost instant change to show the (smaller) red rectangle. The opacity: 0 is being obeyed.

    On an iPhone, IOS18.5, the blue rectangle instantly becomes smaller, but the opacity does not seem to be being obeyed.

    The question is - which is the correct behavior? I ask this because of the both that is present in the animation. That should mean the animation running forwards then backwards - but it doesn't seem to (the rectangle does not expand after it has shrunk).

    It is instructive to slow the whole thing down to see exactly what is going on. Putting the time to 32s instead of .32s we can see the steps - on the iPhone as well as on the other systems.

    To force the take up and remembering of the opacity you could put an extra 99% setting into the keyframes. This seems to convince the Safari 18.5 that it should implement the opacity 0 setting.

    However, I am not convinced that we have yet got to a full explanation. For example, take out the steps and the iPhone behaves the same as the other systems. So we need to think a bit more deeply about what was expected when putting in that steps value - including what does it actually mean when the whole animation is so very quick at 0.32seconds? Do we expect intermediary frames to be drawn, and if they are do we expect to be able to see them as they flash by (it's noticable I can see the frames when running in a stack snippet on some systems, which will be a bit slower than running direct on say the laptop).

    <!document html>
    
      <head>
        <style>
          .pixel-load {
            background: #f00;
            display: block;
            height: 400px;
            width: 300px;
          }
    
          .pixel-load__preload {
            background: #00f;
            display: block;
            height: 100%;
            width: 100%;
          }
    
    
          .pixel-load.loaded .pixel-load__preload {
            animation: loaded .32s .16s steps(1, end) both;
          }
    
          @keyframes loaded {
            0% {
              scale: 3;
            }
    
            33% {
              scale: 2;
            }
    
            66% {
              scale: 1;
            }
    
            99%,
            100% {
              opacity: 0;
              scale: 1;
              z-index: 1;
            }
          }
        </style>
      </head>
    
      <body>
        <div class="pixel-load loaded">
          <div class="pixel-load__preload"></div>
        </div>
      </body>