防止CSS动画期间背景图片调整大小并消除闪烁

8
我有一个
,用作幻灯片图片容器。将在容器中显示的图像是不同大小的,并且应用为背景图像,而不是容器内部的元素,以便:
  • 我可以通过媒体查询随时更换图像集
  • 背景可以动画制作幻灯片效果
  • 我使用background-size:cover和background-clip:content-box,这些都正确地保持了图像在容器所需区域内,确保过量被正确剪切。
    第一个问题是,在CSS动画期间,当一个图像转换到另一个图像时,新图像会在该关键帧的时间内调整大小。我想要的是下一张图像只是过渡到(已经适当大小),而不是看到它重新调整大小。
    实际上,我甚至不确定为什么首先会出现交叉淡化可视化,因为我没有指示这样做(也许是内置的-webkit-动画?)。我本来认为你只会看到从一个图像立即更改为下一个图像。我实际上喜欢交叉淡化,但不知道它来自哪里,让我认为它与调整大小的问题有关。
    第二个问题是,在幻灯片秀的第一次迭代(仅限)中,图像会闪烁并短暂显示容器元素的白色背景颜色。因为这只发生在第一次迭代中,所以我认为可能是图像的初始下载时间的问题,并且在随后的迭代中会消失。然而,当我添加了一些JavaScript来预加载幻灯片展示前的图片时,发现了相同的问题。此外,一旦您运行幻灯片展示,您就会将图像保存在缓存中,当您刷新页面(它将仅从缓存中获取图像,是的,我正在通过从自己的测试服务器获取文件进行测试,以便它们将正确缓存)时,闪烁会再次发生。
    我看到过一些关于使用preserve3d等各种CSS属性设置消除闪烁的帖子,但没有任何帮助。
    我在Windows 10上使用Chrome 62.0.3202.94桌面版。

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

    我意识到我可以使用其他各种技术来规避这些问题(例如JavaScript,堆叠<img>元素并管理透明度等),但我真的想了解是什么原因导致了这两个问题。


    1
    这真的很有趣:D 白色背景明显是由于图片未能及时加载,但调整大小时的闪烁对我来说是新的事情...指定确切的背景大小可以解决闪烁问题。请查看codepen https://codepen.io/mnikolaus/pen/YEexNX 我知道它不符合您的响应式想法,但您可以尝试解决一下。我猜测给定时间内图像大小的计算量对浏览器来说太大了... - Mario Nikolaus
    您需要为显示图像拥有单个元素吗?解决方案需要多具有可扩展性? - Mario Nikolaus
    我并不需要一个单一元素,但是我真的不想把HTML搞得一团糟,作为解决实际根本问题的补丁。我需要幻灯片从移动端到桌面端视口都能自适应。 - Scott Marcus
    你可以尝试使用伪类来实现循环效果,但这种方法并不可扩展。最好的方法是在Sass中使用多个元素,并动态创建动画。如果图像能够在这种情况下表现出期望的行为就更好了,但不幸的是这并不是事实。我非常期待你的解决方案,所以请在你找到时发布它。 - Mario Nikolaus
    @MarioNikolaus 再次感谢。我知道我可以重新设计来解决这些问题(JavaScript 解决方案会非常简单),但我更感兴趣的是找到它们的根本原因。 - Scott Marcus
    显示剩余4条评论
    1个回答

    3

    1. 交叉淡化效果:

    这种情况发生是因为浏览器试图在关键帧之间进行动画。如果我们看一下你的@keyframes循环,我们会发现你每隔25%就设置了一个关键帧。这意味着浏览器将从0%到25%进行动画。它将要动画什么?被更改的背景。如何进行动画?交叉淡化。此外,背景再次调整大小以覆盖div(使用的图片格式不同-如果使用相同的格式,则可能不会发生这种情况)。

    @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");   }
    }
    

    方案 A(无交叉淡入淡出和白色背景)

    您可以通过使动画几乎瞬间发生来消除调整大小:

    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");   }
    

    关键帧0%的背景与关键帧24.99%相同,因此不会发生动画效果。然后在25%时发生变化(这看起来像是瞬间变化)。请参见下面的工作示例:

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

    请注意!最后一个关键帧(100%)应设置为与第一个(0%)相同的值,因为我们希望循环从停止的位置开始。实际上,这意味着在此示例中只有4个不同的背景图像(如果您想要5个-请采取20%的步骤)。
    我们已修复更改大小的背景问题。副作用是我们也“修复”了交叉淡入淡出。真正的缺点是播放动画的第一次仍然有白色背景。
    2. 白色背景
    我认为白色背景出现是因为需要首先加载图像。它们只会在动画运行时加载。这就是为什么第一次运行时会有白色背景的原因。
    解决方案B(带有交叉淡入淡出和无白色背景)
    我们可以使用辅助div来恢复交叉淡入淡出效果。但是我们必须在关键帧中设置不透明度。当显示其他div时,可以在关键帧中设置背景图像-因此它有更多时间加载,而不会出现白色背景。请参见下面的代码片段:

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

    解决方案B说明:
    该方案的想法是在显示背景图像之前让它们加载。因此,有两个div和两个@keyframe动画,从而实现平滑交替的背景。
    #innermostFrame2的宽度和高度与#innermostFrame相同,并且绝对定位(正好在其上方)。在动画持续时间的25%内,将显示和隐藏#innermostFrame2。以下内容可见:
             25%                25%               25%                25%
    [ #innermostFrame2 - #innermostFrame - #innermostFrame2 - #innermostFrame ]
    

    百分比:

    背景图片已经设置好了#innermostFrame2本身——为了使它尽可能快地加载。在@keyframes cycle2中,0%是不需要的,因为背景已经设置并可见。

    我们希望图片在动画的25%(4张图片)处显示。现在我决定在这个例子中使用10%的淡入淡出效果看起来很好(8秒动画的10%是0.8秒)。您可以做得更少或更多,因为这是一个品味问题。这就是为什么动画从15%的不透明度为1开始,到25%的不透明度为0。在div不可见之后,我们立即在26%更改背景图片,以便在CSS对象模型中加载。

    #innermostFrame的背景现在可见(因为#innermostFrame2被隐藏了)。我们想在动画持续时间的25%中显示它。因此,在50%时,我们希望#innermostFrame2再次显示新背景。因为我决定在动画的10%中进行淡入淡出,所以我们从40%开始,opacity: 0;。在#innermostFrame2完全显示(50%)之后,我们立即更改#innermostFrame的背景,以让它加载。这发生在@keyframes cycle的51%。


    好的。你能再详细解释一下第二个动画吗?具体来说,你为什么选择了这些百分比,而且交替的图像是来自于交替的动画吗? - Scott Marcus
    @ScottMarcus - 我更新了一些关于百分比的具体信息。 - J.T. Houtenbos

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接