使用过渡时出现问题+不透明度变化+隐藏溢出

Issue while using transitions + opacity change + overflow hidden

如果您看到我分享的代码示例,您会看到叠加层超出了方框。我将问题追溯到 transition 属性。

我想删除 div 之外的内容。溢出无法正常工作。 (删除 transition 有效,但如果可能我想保留它)

感谢任何帮助

Codepen Link

代码

var timer = setInterval(function() {
  document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
  if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
    clearInterval(timer);
  }
}, 1000);
.qs-main-header .qs-timer {
  padding: 13px 10px;
  min-width: 130px;
  text-align: center;
  display: inline-block;
  background-color: #dd8b3a;
  color: #FFF;
  font-size: 20px;
  border-radius: 50px;
  text-transform: uppercase;
  float: right;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.qs-main-header .qs-timer-overlay {
  z-index: 1;
  width: 10%;
  max-width: 100%;
  position: absolute;
  height: 100%;
  top: 0;
  left: 0;
  background-color: #c7543e;
  opacity: 0.0;
  /* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
  z-index: 2;
  position: relative;
}
.scale-transition {
  -webkit-transition: all 1s;
  transition: all 1s;
}
<div class="qs-main-header">
  <div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
    <div class="scale-transition qs-timer-overlay"></div>
    <div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
    </div>
  </div>
</div>

是的,将 opacity: 0.99; 添加到 .qs-timer 问题将得到解决。

当不透明度:1 或未定义时:
在这种特殊情况下,不涉及透明度,因此 gfx 可以避免做昂贵的事情。

如果不透明度:0.99:
nsIFrame::HasOpacity() 决定不透明,所以 gfx 包括有价值的东西。 (喜欢 opacityborder-radius

如需更多帮助 Special case opacity:0.99 to treat it as opacity:1 for graphics ,这张票并没有提供我们实际目标的意见,而是提供了关于 CSS.

内部发生的事情的想法

实际上是 border-radius 在过渡发生时没有得到尊重。这是因为为加速渲染创建了合成层,可以通过查看以下文章来解释:


为什么禁用转换后问题没有发生?

  • 当样式发生变化但 none 需要创建合成层的条件得到满足时(即没有动画或过渡或 3D 变换等):
    • 没有合成层,因此整个区域似乎在每次更改时都会重新绘制。由于发生了完全重绘,因此没有问题。
  • 在开发工具中启用 "Show paint rects" 和 "Show composited layer borders" 后查看以下代码片段(在全屏模式下)并观察以下内容:
    • 没有创建带有橙色边框的区域(合成层)。
    • 每次通过将焦点设置在 a 标记之一上来修改样式时,整个区域都会重新绘制(红色或绿色闪烁区域)。

.outer {
  position: relative;
  height: 100px;
  width: 100px;
  margin-top: 50px;
  border: 1px solid red;
  overflow: hidden;
}
.border-radius {
  border-radius: 50px;
}
.inner {
  width: 50px;
  height: 50px;
  background-color: gray;
  opacity: 0.75;
}
a:focus + .outer.border-radius > .inner {
  transform: translateX(50px);
  height: 51px;
  opacity: 0.5;
}
<a href='#'>Test</a>
<div class='outer border-radius'>
  <div class='inner'>I am a strange root.
  </div>
</div>


为什么添加过渡会产生问题?

  • 初始渲染没有合成层,因为元素上还没有过渡。查看下面的代码片段并注意当代码片段是 运行 时如何绘制(红色或绿色闪烁区域)但没有创建合成层(带有橙色边框的区域)。
  • 当过渡开始时,Chrome 在过渡某些属性(例如不透明度、变换等)时将它们拆分为不同的合成层。请注意,一旦将焦点设置在其中一个锚标记上,就会显示两个带有橙色边框的区域。这些是已创建的合成层。
  • 正在为加速渲染进行图层拆分。正如 HTML5 Rocks 文章中提到的,不透明度和变换更改是通过更改合成层的属性应用的,不会发生重绘。
  • 在过渡结束时,重绘恰好将所有层合并回单个层,因为合成层不再适用(基于创建层的标准)。

.outer {
  position: relative;
  height: 100px;
  width: 100px;
  margin-top: 50px;
  border: 1px solid red;
  overflow: hidden;
}
.border-radius {
  border-radius: 50px;
}
.inner {
  width: 50px;
  height: 50px;
  background-color: gray;
  transition: all 1s 5s;
  /*transition: height 1s 5s; /* uncomment this to see how other properties don't create a compositing layer */
  opacity: 0.75;
}
a:focus + .outer.border-radius > .inner {
  transform: translateX(50px);
  opacity: 0.5;
  /*height: 60px; */
}
<a href='#'>Test</a>
<div class='outer border-radius'>
  <div class='inner'>I am a strange root.
  </div>
</div>

这说明当层被合并回来并发生完全重绘时,父级上的 border-radius 也会得到应用和尊重。然而,在过渡期间只有合成层的属性发生变化,因此该层似乎不知道其他层的属性,因此不尊重父级的边界半径。

我认为这是因为图层渲染的工作方式。每层都是一个软件位图,因此它相当于拥有一个圆形图像,然后在其顶部放置一个 div。这显然不会导致任何内容剪辑。

this bug thread 中的评论似乎也证实了当不再需要单独的层时会发生重绘。

We want to repaint if "gets own layer" is going to change

注意:虽然它们是 Chrome 特定的,但我认为其他人的行为也应该相似。


解决方法是什么?

解决方案似乎是为父 (.qs-timer) 元素创建一个单独的堆叠上下文。创建一个单独的堆叠上下文似乎会导致为父级创建一个单独的合成层,这解决了这个问题。

正如 BoltClock 在 this answer 中提到的,以下任何一个选项都会为父级创建一个单独的堆栈上下文,并且执行其中一个似乎可以解决问题。

  • 将父 .qs-timer 上的 z-index 设置为除自动以外的任何值。

    var timer = setInterval(function() {
      document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
      if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
        clearInterval(timer);
      }
    }, 1000);
    
    .qs-main-header .qs-timer {
      padding: 13px 10px;
      min-width: 130px;
      text-align: center;
      display: inline-block;
      background-color: #dd8b3a;
      color: #FFF;
      font-size: 20px;
      border-radius: 50px;
      text-transform: uppercase;
      float: right;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      z-index: 1; /* creates a separate stacking context */
    }
    .qs-main-header .qs-timer-overlay {
      z-index: 1;
      width: 10%;
      max-width: 100%;
      position: absolute;
      height: 100%;
      top: 0;
      left: 0;
      background-color: #c7543e;
      opacity: 0.0;
      /* border-radius: 50px 50px 0px 50px; */
    }
    .qs-main-header .qs-timer-content {
      z-index: 2;
      position: relative;
    }
    .scale-transition {
      -webkit-transition: all 1s;
      transition: all 1s;
    }
    
    <div class="qs-main-header">
      <div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
        <div class="scale-transition qs-timer-overlay"></div>
        <div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
        </div>
      </div>
    </div>
    

  • opacity 设置为小于 1 的值。我在下面的代码片段中使用了 0.99,因为它不会造成任何视觉差异。

    var timer = setInterval(function() {
      document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
      if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
        clearInterval(timer);
      }
    }, 1000);
    
    .qs-main-header .qs-timer {
      padding: 13px 10px;
      min-width: 130px;
      text-align: center;
      display: inline-block;
      background-color: #dd8b3a;
      color: #FFF;
      font-size: 20px;
      border-radius: 50px;
      text-transform: uppercase;
      float: right;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      opacity: 0.99; /* creates a separate stacking context */
    }
    .qs-main-header .qs-timer-overlay {
      z-index: 1;
      width: 10%;
      max-width: 100%;
      position: absolute;
      height: 100%;
      top: 0;
      left: 0;
      background-color: #c7543e;
      opacity: 0.0;
      /* border-radius: 50px 50px 0px 50px; */
    }
    .qs-main-header .qs-timer-content {
      z-index: 2;
      position: relative;
    }
    .scale-transition {
      -webkit-transition: all 1s;
      transition: all 1s;
    }
    
    <div class="qs-main-header">
      <div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
        <div class="scale-transition qs-timer-overlay"></div>
        <div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
        </div>
      </div>
    </div>
    

  • 向元素添加 transform。我在下面的代码片段中使用了 translateZ(0px),因为这也不会造成任何视觉差异。

    var timer = setInterval(function() {
      document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
      if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
        clearInterval(timer);
      }
    }, 1000);
    
    .qs-main-header .qs-timer {
      padding: 13px 10px;
      min-width: 130px;
      text-align: center;
      display: inline-block;
      background-color: #dd8b3a;
      color: #FFF;
      font-size: 20px;
      border-radius: 50px;
      text-transform: uppercase;
      float: right;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transform: translateZ(0px) /* creates a separate stacking context */
    }
    .qs-main-header .qs-timer-overlay {
      z-index: 1;
      width: 10%;
      max-width: 100%;
      position: absolute;
      height: 100%;
      top: 0;
      left: 0;
      background-color: #c7543e;
      opacity: 0.0;
      /* border-radius: 50px 50px 0px 50px; */
    }
    .qs-main-header .qs-timer-content {
      z-index: 2;
      position: relative;
    }
    .scale-transition {
      -webkit-transition: all 1s;
      transition: all 1s;
    }
    
    <div class="qs-main-header">
      <div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
        <div class="scale-transition qs-timer-overlay"></div>
        <div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
        </div>
      </div>
    </div>
    

前两种方法比第三种方法更可取,因为第三种方法仅适用于支持 CSS 转换的浏览器。