【问题标题】:Issue while using transitions + opacity change + overflow hidden使用过渡+不透明度更改+溢出隐藏时出现问题
【发布时间】:2015-07-29 06:52:20
【问题描述】:

如果您看到我分享的代码示例,您可以看到覆盖在框外。我将问题追溯到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 可以解决此问题,但我没有明确解释为什么会这样。
  • 谢谢伙计。我已经看到了前面提到的这个解决方案。但我将它应用到了错误的 div 中。另外,如果您将其添加为答案,我可以将其标记为选定答案。

标签: javascript html css css-animations


【解决方案1】:

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


为什么禁用转换时不会出现此问题?

  • 当样式发生变化但没有满足需要创建合成层的条件时(即没有动画或过渡或 3D 变换等):
    • 没有合成层,因此整个区域似乎在每次更改时都会重新绘制。由于发生了完全重绘,因此没有问题。
  • 在开发工具中启用“显示绘制矩形”和“显示合成图层边框”后查看下面的 sn-p(全屏模式)并观察以下内容:
    • 不会创建带有橙色边框的区域(合成层)。
    • 每次通过将焦点设置在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>

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

  • 初始渲染没有合成层,因为元素上还没有过渡。查看下面的 sn-p 并注意当 sn-p 运行时,绘制(红色或绿色闪烁区域)发生但没有创建合成层(橙色边框区域)。
  • 当过渡开始时,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 中的评论似乎也证实了当不再需要单独的图层时会发生重绘。

如果“获取自己的图层”发生变化,我们想重新绘制

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


解决办法是什么?

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

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

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

    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 的任何值。我在下面的 sn-p 中使用了 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。我在下面的 sn-p 中使用了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 转换的浏览器。

【讨论】:

  • 嗯,所以将qs-timer 添加到堆叠上下文中可以有效地解决这个问题。原因是 - opacity + transition 重绘了父元素没有捕获的 qs-overlay(因为缺少堆叠上下文)。我做对了吗?
  • @MrBones:实际上答案中提到的当前 reason 是不正确的(虽然解决方案是正确的)。我继续我的分析,这个fiddle 的 JS 部分中提到的细节已经过验证并发现是正确的。您还可以通过在 Chrome 开发工具中激活“显示绘制矩形”来检查它(有关如何激活它的信息,请查看 HTML5 Rocks 文章)。我试图找到添加堆叠上下文如何解决它并在完成后更新答案:)
  • 谢谢伙计。我现在正在尝试了解堆叠上下文。你的解释让我对这个话题有了更好的理解,所以暂时标记为答案。
【解决方案2】:

是的,将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 内部发生了什么的想法。

【讨论】:

    猜你喜欢
    • 2017-07-06
    • 2017-06-27
    • 2012-01-20
    • 2012-05-01
    • 1970-01-01
    • 2017-03-20
    • 2017-03-14
    • 2021-05-11
    • 2020-06-20
    相关资源
    最近更新 更多