Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep Background Image from Resizing During CSS Animation and Remove Flicker

I have a <div> that I am using as a slide show image container. The images that will be displayed in the container are different sizes and applied as background images, rather than <img> elements inside the container so that:

  • I can swap the set of images as needed via media queries
  • The background can be animated to produce the slideshow effect

I have background-size:cover and background-clip:content-box and these are correctly keeping the image within the desired area of the container, ensuring that excess is clipped off properly.

The first issue is that during the CSS animation, when one image transitions to another, the new image is resized over the course of that keyframe's timing. What I would like is for the next image to just be transitioned to (at the proper size already), without seeing it resize.

Actually, I'm not even sure why there is a cross-fade visualization in the first place, since I have no instructions to do that (perhaps a built-in -webkit- animation?). I would have thought that you would just see an immediate change from one image to the next. I actually like the cross-fade, but not knowing where it's coming from makes me think it's related to the resizing issue.

The second issue is that during the first iteration (only) of the slide show, there is a flickering of the images and the white background color of the container element is shown briefly. Because this only happens on the first iteration, I thought that perhaps it was an issue with the initial download times for the images and that's why it goes away on subsequent iterations. However, that proved not to be the case when I added some JavaScript to preload the images before the slide show starts and found the same issue. Also, once you run the show, you have the images in your cache and when you refresh the page (which will just get the images from your cache and yes, I am testing this by getting the file from my own test server so they will cache properly) the flicker happens again.

I've seen some posts about eliminating the flicker with various CSS property settings using preserve3d and such, but none of that helped.

I'm using Chrome 62.0.3202.94 desktop on Windows 10.

#outerFrame {
  background-color: #22130c;
  box-shadow: inset 0 1px 1px 1px hsla(0,0%,100%,.2), 
              inset 0 -2px 1px hsla(0,0%,0%,.4), 
              0 5px 50px hsla(0,0%,0%,.25), 
              0 20px 20px -15px hsla(0,0%,0%,.2), 
              0 30px 20px -15px hsla(0,0%,0%,.15), 
              0 40px 20px -15px hsla(0,0%,0%,.1);
  padding: 1.5em;
  overflow: auto;
  float: left;
  margin: 0 1em 1em 0;
}

#innerFrame {
  background-color: #fff5e5;
  box-shadow: 0 2px 1px hsla(0,0%,100%,.2), 
              0 -1px 1px 1px hsla(0,0%,0%,.4), 
              inset 0 2px 3px 1px hsla(0,0%,0%,.2), 
              inset 0 4px 3px 1px hsla(0,0%,0%,.2), 
              inset 0 6px 3px 1px hsla(0,0%,0%,.1);
  padding: 1.5em;
}

/* This is the relevant style: */
#innermostFrame {
  padding: .75em;
  background-color: #fff;
  box-shadow: 0 -1px 0 2px hsla(0, 0%, 0%,.03);
  background-position: 50% 50%;
  background-repeat: no-repeat;
  background-size:cover;  
  background-clip: content-box;
  animation: cycle 8s ease-in-out infinite;
  
  width: 30vw;
  height:40vh;
  min-width: 200px;
  max-width: 900px;
}

@keyframes cycle {
  0%   { background-image: url("https://picsum.photos/200/300/?random");   }
  25%  { background-image: url("https://picsum.photos/640/480/?random");   }
  50%  { background-image: url("https://picsum.photos/1900/1200/?random"); }
  75%  { background-image: url("https://picsum.photos/480/200/?random");   }
  100% { background-image: url("https://picsum.photos/600/300/?random");   }
}
<div id="outerFrame">
  <div id="innerFrame">
    <div id="innermostFrame"></div>
  </div>
</div>

I realize I could side-step these issues using other various techniques (i.e. JavaScript, stacking <img> elements and managing opacity, etc.), but I am really trying to understand what is causing these two issues.

like image 702
Scott Marcus Avatar asked Nov 20 '17 15:11

Scott Marcus


People also ask

How do I stop my background from flickering to change?

Try to preload the image resource to the device storage by including the image in DOM like in the following HTML-Code. Maybe the error comes up because the image resource need to be loaded which takes some time (flickering).

How do I stop my background image from stretching in HTML?

You can use background-size: cover to scale the image, while preserving its intrinsic aspect ratio (if any), to the smallest size such that both its width and its height can completely cover the background positioning area. Using this option will ensure that the image doesn't get stretched out of proportion.

Can you animate a background image in CSS?

You can use CSS to create trendy animations and visual effects. You don't need to know JavaScript or even HTML and create different kinds of animations and environments on your website. Our team at Slider Revolution has researched CSS animated background examples that can help you create fun websites.


1 Answers

1. Crossfade:

this occurs because the browser tries to animate between keyframes. If we take a look at your @keyframes cycle, we see you make a keyframe every 25%. This means the browser will animate from 0% to 25%. What will it animate? The background that is changed. How will it animate it? Crossfade. Also the background is sized again to cover the div ( the pictures used are of different formats - if you use the same format - this would probably not happen ).

@keyframes cycle {
  0%   { background-image: url("https://picsum.photos/200/300/?random");   }
  25%  { background-image: url("https://picsum.photos/640/480/?random");   }
  50%  { background-image: url("https://picsum.photos/1900/1200/?random"); }
  75%  { background-image: url("https://picsum.photos/480/200/?random");   }
  100% { background-image: url("https://picsum.photos/600/300/?random");   }
}

Solution A ( no crossfade and white background )

You can get rid of the resizing by letting the animation happen almost instantaneous:

0%   { background-image: url("https://picsum.photos/200/300/?random");   }
24.99%   { background-image: url("https://picsum.photos/200/300/?random");   }
25%  { background-image: url("https://picsum.photos/640/480/?random");   }

The background in keyframe 0% is the same at keyframe 24.99% so it does not animate. Then it changes at 25% (this will look like an instant change) See working snippet below:

#outerFrame {
  background-color: #22130c;
  box-shadow: inset 0 1px 1px 1px hsla(0,0%,100%,.2), 
              inset 0 -2px 1px hsla(0,0%,0%,.4), 
              0 5px 50px hsla(0,0%,0%,.25), 
              0 20px 20px -15px hsla(0,0%,0%,.2), 
              0 30px 20px -15px hsla(0,0%,0%,.15), 
              0 40px 20px -15px hsla(0,0%,0%,.1);
  padding: 1.5em;
  overflow: auto;
  float: left;
  margin: 0 1em 1em 0;
}


#innerFrame {
  position: relative;
  background-color: #fff5e5;
  box-shadow: 0 2px 1px hsla(0,0%,100%,.2), 
              0 -1px 1px 1px hsla(0,0%,0%,.4), 
              inset 0 2px 3px 1px hsla(0,0%,0%,.2), 
              inset 0 4px 3px 1px hsla(0,0%,0%,.2), 
              inset 0 6px 3px 1px hsla(0,0%,0%,.1);
  padding: 1.5em;
}

/* This is the relevant style: */
#innermostFrame{
  padding: .75em;
  background-color: #fff;
  box-shadow: 0 -1px 0 2px hsla(0, 0%, 0%,.03);
  background-position: 50% 50%;
  background-repeat: no-repeat;
  background-size:cover;  
  background-clip: content-box;
  animation: cycle 8s ease-in-out infinite;
  width: 30vw;
  height:40vh;
  min-width: 200px;
  max-width: 900px;
}

@keyframes cycle {
      0%   { background-image: url("https://picsum.photos/200/300/?random");   }
      24.99%   { background-image: url("https://picsum.photos/200/300/?random");   }
      
      25%  { background-image: url("https://picsum.photos/640/480/?random");   }
      49.99%  { background-image: url("https://picsum.photos/640/480/?random");   }
      
      50%  { background-image: url("https://picsum.photos/1900/1200/?random"); }
      74.99%  { background-image: url("https://picsum.photos/1900/1200/?random"); }
      
      75%  { background-image: url("https://picsum.photos/480/200/?random");   }
      99.99%  { background-image: url("https://picsum.photos/480/200/?random");   }
      
      100% { background-image: url("https://picsum.photos/200/300/?random");    }
}
<div id="outerFrame">
  <div id="innerFrame">
    <div id="innermostFrame"></div>
  </div>
</div>

Pay attention! the last keyframe (100%) should be set to the same values as the first (0%) because we want the loop to start where it left off. In effect this means you have only 4 different background images in this example ( if you want to have 5 - take steps of 20% instead ).

We fixed the problem with the resizing backgrounds. As a side effect we also 'fixed' the crossfade. The real downside is we still have the white backgrounds the first time the animation plays.

2. White backgrounds

I presume the white backgrounds appear because the images need to be loaded first. They only get loaded when the animation runs. That is why there are white backgrounds the first run trough.

Solution B ( with crossfade and no white background )

We can use a secondary div to get the crossfade effect back. But we have to set the opacity in the keframes. We can set the background image in a keyframe while showing the other div - so it has more time to load and the white backgrounds do not appear. See snippet below:

#outerFrame {
  background-color: #22130c;
  box-shadow: inset 0 1px 1px 1px hsla(0,0%,100%,.2), 
              inset 0 -2px 1px hsla(0,0%,0%,.4), 
              0 5px 50px hsla(0,0%,0%,.25), 
              0 20px 20px -15px hsla(0,0%,0%,.2), 
              0 30px 20px -15px hsla(0,0%,0%,.15), 
              0 40px 20px -15px hsla(0,0%,0%,.1);
  padding: 1.5em;
  overflow: auto;
  float: left;
  margin: 0 1em 1em 0;
}


#innerFrame {
  position: relative;
  background-color: #fff5e5;
  box-shadow: 0 2px 1px hsla(0,0%,100%,.2), 
              0 -1px 1px 1px hsla(0,0%,0%,.4), 
              inset 0 2px 3px 1px hsla(0,0%,0%,.2), 
              inset 0 4px 3px 1px hsla(0,0%,0%,.2), 
              inset 0 6px 3px 1px hsla(0,0%,0%,.1);
  padding: 1.5em;
}

/* This is the relevant style: */
#innermostFrame,
#innermostFrame2{
  padding: .75em;
  background-color: #fff;
  box-shadow: 0 -1px 0 2px hsla(0, 0%, 0%,.03);
  background-position: 50% 50%;
  background-repeat: no-repeat;
  background-size:cover;  
  background-clip: content-box;
  animation: cycle 8s ease-in-out infinite;
  width: 30vw;
  height:40vh;
  min-width: 200px;
  max-width: 900px;
}
#innermostFrame2{
  position: absolute;
  top: 1.5em; /* padding of innerFrame */
  animation: cycle2 8s infinite;
  background-image: url("https://picsum.photos/200/300/?random"); 
}
@keyframes cycle {
  0%   { background-image: url("https://picsum.photos/640/480/?random"); }
  50%  { background-image: url("https://picsum.photos/640/480/?random"); }
  51%  { background-image: url("https://picsum.photos/480/200/?random"); }
  100% { background-image: url("https://picsum.photos/480/200/?random"); }
}
@keyframes cycle2 {
  15%  { opacity: 1;}
  25%  { opacity: 0; background-image: url("https://picsum.photos/200/300/?random"); }
  
  26%  { background-image: url("https://picsum.photos/1900/1200/?random");}
  40%  { opacity: 0;}
  50%  { opacity: 1;}
  65%  { opacity: 1;}
  75%  { opacity: 0; background-image: url("https://picsum.photos/1900/1200/?random");}
  
  76%  { background-image: url("https://picsum.photos/200/300/?random");}
  90%  { opacity: 0;}
  100% { opacity: 1;}
}
<div id="outerFrame">
  <div id="innerFrame">
    <div id="innermostFrame"></div>
    <div id="innermostFrame2"></div>
  </div>
</div>

Solution B explained:

The idea is to let the background images load before they are shown. Therefor there are 2 divs with 2 @keyframe animations resulting in smoothly alternating backgrounds.

#innermostFrame2 gets the same width and height as #innermostFrame and is positioned absolutely ( straight above it ). #innermostFrame2's is shown and hidden for 25% of the animation duration. The following is visible:

         25%                25%               25%                25%
[ #innermostFrame2 - #innermostFrame - #innermostFrame2 - #innermostFrame ]

Percentages:

The background image is already set for #innermostFrame2 itself - to make it load as fast as possible. In the @keyframes cycle2 there is no need for 0% because the background is already set and visible.

We want the image to be shown for 25% of the animation ( 4 images). Now I decided that a crossfade of 10% looked nice in this example ( 10% of 8 seconds animation is 0.8 seconds ). You could do less or more, as this is a matter of taste. This is why the animation starts with an opacity of 1 at 15% and 0 at 25%. Immediately after the div is invisible we change the background image at 26%, so it can be loaded in the CSS Object Model.

#innermostFrame's background is visible now ( because #innermostFrame2 is hidden ). We want to show this for 25% of the duration of the animation. So at 50% we want #innermostFrame2 to be visible again with the new background. And because I decided to crossfade for 10% of the duration of the animation we start at 40% with opacity: 0;. Immediately after the #innermostFrame2 is fully shown (50%) we change #innermostFrame's background to let it load. This happens at 51% in @keyframes cycle

like image 56
J.T. Houtenbos Avatar answered Oct 23 '22 02:10

J.T. Houtenbos