使用转换+不透明度更改+溢出隐藏时出现问题

8
如果您查看我分享的代码示例,您会发现覆盖层超出了框。我将问题追溯到transition属性。
我想要删除div外部的内容。溢出不像预期的那样工作。(删除transition可以解决问题,但如果可能的话,我想保留它)
感谢任何帮助 Codepen链接 代码

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>


1
这是已知的行为。将 opacity: 0.99; 添加到 .qs-timer 可以解决它,但我没有清楚的解释为什么会起作用。 - Harry
谢谢,伙计。我之前看到过这个解决方案。但是我把它应用在了错误的 div 上。另外,如果你将其作为答案添加进去,我可以将其标记为所选答案。 - user1496463
2个回答

15
实际上,在发生过渡时,无法遵守border-radius。这是由于创建了用于加速渲染的合成层所导致的,并且可以通过查看以下文章来解释:

为什么在禁用过渡时不会出现此问题?

  • 当样式更改但没有满足必须创建合成层的条件(即没有动画、过渡或3D变换等)时:
    • 没有合成层,因此整个区域似乎在每次更改时都会被重新绘制。由于进行了全面的重新绘制,因此没有问题。
  • 启用“显示绘制矩形”和“显示合成层边框”,然后在 Dev 工具中查看下面的代码片段(全屏模式),并观察以下内容:
    • 没有创建橙色边框(合成层)的区域。
    • 每次通过将焦点设置在其中一个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。这显然不会导致任何内容被剪切。 此错误线程中的评论似乎也证实了当不再需要单独的图层时会发生重绘。

如果“获得自己的图层”将要更改,我们想要重绘

注意:虽然它们是Chrome特定的,但我认为行为在其他浏览器中应该是类似的。
解决方案是什么? 解决方案似乎是为父元素(.qs-timer)创建一个单独的堆叠上下文。创建单独的堆叠上下文似乎会为父元素创建一个单独的合成图层,从而解决了这个问题。
正如BoltClock在这个答案中提到的,以下任何一个选项都将为父元素创建一个单独的堆叠上下文,并执行其中之一似乎可以解决该问题。
  • Setting a z-index on the parent .qs-timer to anything other than 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>

  • Setting opacity to anything less than 1. I have used 0.99 in the below snippet as it doesn't cause any visual difference.

    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>

  • Adding a transform to the element. I have used translateZ(0px) in the below snippet as this also doesn't create any visual difference.

    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,而父元素由于缺乏堆叠上下文而无法捕获它。我理解得对吗? - user1496463
@MrBones:实际上,回答中提到的当前“原因”是错误的(解决方案是正确的)。我继续进行分析,并验证了此fiddle 的JS部分中提到的细节,发现它们是正确的。您还可以通过在Chrome Dev工具中激活“show paint rect”来检查它(请参阅HTML5 Rocks文章以获取有关如何激活它的信息)。我正在尝试找出添加堆叠上下文如何解决它,并在完成后更新答案 :) - Harry
1
谢谢,伙计。我现在正在尝试理解堆叠上下文。你的解释让我更好地理解了这个主题,所以暂时将其标记为答案。 - user1496463

1

是的,在.qs-timer中添加opacity: 0.99;可以解决问题。

当opacity:1或未定义时:
在这种特殊情况下,没有涉及到透明度,因此gfx可以避免进行昂贵的操作。

如果opacity:0.99:
nsIFrame :: HasOpacity()决定存在透明度,因此gfx包含有价值的东西(例如带有border-radiusopacity)。

欲了解更多帮助特殊情况下的opacity:0.99如何处理为图形的opacity:1,本票证并未提供我们实际目标的意见,但说明了CSS内部发生的事情。


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