Search code examples
javascripthtmlcsscss-animations

How do different browsers calculate a CSS translate animation containing images?


I am building a small app with a horizontal infinite scroll animation containing a set of images. In essence, it looks like this:

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <link rel="stylesheet" href="main.css">
  <title>Example</title>
</head>

<body>
  <div id="container">
    <div id="photos_container">
      <div id="photos_scroller">
        <img src="https://picsum.photos/1000" alt="ID 111111">
        <img src="https://picsum.photos/1000" alt="ID 111112">
        <img src="https://picsum.photos/1000" alt="ID 111113">
        <img src="https://picsum.photos/1000" alt="ID 111114">

        <img src="https://picsum.photos/1000" alt="ID 111111">
        <img src="https://picsum.photos/1000" alt="ID 111112">
        <img src="https://picsum.photos/1000" alt="ID 111113">
        <img src="https://picsum.photos/1000" alt="ID 111114">
      </div>
    </div>
  </div>
</body>

</html>

main.css

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
  padding: 0;
  font: inherit;
}

html,
body {
  height: 100%;
}

body {
  display: grid;
  place-items: center;
  background-color: #1f1f1f;
}

#container {
  width: 95vw;
  padding-inline: 1rem;
}

#photos_container {
  overflow: hidden;
}

#photos_scroller {
  display: flex;
  gap: 1rem;
  width: max-content;
  animation: slide var(--move_duration, 10s) linear infinite;
}

#photos_scroller > img {
  max-height: 50vh;
  transform: translateZ(0); /* to prevent flickering images */
}

@keyframes slide {
  to {
    transform: translate(calc(-50% - 0.5rem));
  }
}

In Chrome on a MacBook, this works fine. However, on Safari, as well as in Safari and Chrome (?!?) on an iPad the images are hardly moving.

Adding a delay to the animation solves the issue, so my hypothesis is that the animation and the corresponding distance are calculated differently in different browsers. Specifically, in the mentioned cases, I suppose the animation is based on the layout at a point in time when the photos have not yet been downloaded.

Also, after tilting the iPad from landscape to portrait and back to landscape orientation the animation runs correctly, so I guess, after the repaint the animation is correctly calculated based on the then available images.

Can someone please explain to me the mechanics that are at play here? What is the recommended way to handle this issue, in particular when images are added or removed dynamically with JavaScript?


Solution

  • There was an answer, but it was removed for some reason. I will reiterate, so that it is available for others with a similar question:

    The hypothesis seems to be correct, Safari apparently calculates the animation before all images have been downloaded. As Siguza pointed out, Chrome on iOS is based on WebKit, not Blink, which is the reason for the differences happening on Chrome on MacOS and iOS.

    A possible way to address this issue is to attach load event listeners to all the images and only when all the images have finished loading, add the animation to the photos_scroller div (here with a CSS class), for example like this:

      images.forEach((image) => {
        image.addEventListener("load", () => {
          if (images.every((img) => img.complete)) {
            photosScrollerDiv.classList.add("move");
          }
        });
      });
    

    While this requires some JavaScript, it is more reliable than using a delay, which might or might not work depending on the time it takes to fully load the images.