CSS 动画:`backface-visibility` 导致跨浏览器问题?

CSS Animation: `backface-visibility` causing cross-browser problems?

我正在开发一个翻转动画来显示新数字;它很像中间有铰链的模拟时钟或日历。

方法很简单:有一个 div 和:

为了显示新数字,我将整个 div 围绕容器的中心旋转,露出旋转的背面 div: Number flip animation in latest Firefox

但是,在 Chrome 中,动画并不总是有效。有时一半会完全消失,直到过渡动画完成,有时旧数字不会呈现:Number flip animation in latest Chrome with the bottom of the number not appearing till after animation is complete

在 Safari 12 中,更糟糕的是,它似乎不尊重 backface-visibility,即使使用 -webkit- 前缀也是如此: Safari 12 Number animation, the bottom half of the first number is inverted after animation is complete

Chromium 之前的 Edge 可以很好地处理这个问题,但是新的(在 v83 中检查过)Edge 与 Chrome.

有同样的问题

我试过弄乱这些属性,并在这里查看了其他 backface-visibility 问题。

这是代码,将鼠标悬停在数字上可以看到翻转:

body {
  background: #2e517d;
}

.container {
  width: 175px;
  height: 192px;
  background: #4e9bfa;
  position: relative;
  left: 50%;
  transform: translate(-50%, 50%);
  perspective: 1000px;
}

.cover {
  width: 175px;
  height: 50%;
  position: absolute;
  top: 96px;
  background-color: #34b58c;
  transform: rotateX(0deg);
  transform-style: preserve-3d;
  transform-origin: top;
  transition: all 0.5s ease-out;
}

.container:hover .cover {
  transform: rotateX(180deg);
}

.flip {
  margin: 0;
  display: block;
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
}

.container p {
  font-size: 1000%;
  margin: 0;
}

.container>p {
  height: 96px;
  overflow: hidden;
}

.front-number-bottom {
  position: relative;
  height: 96px;
  overflow: hidden;
  background-color: red;
}

.front-number-bottom p {
  margin: 0;
  position: relative;
  top: -96px;
}

.back-number-top {
  position: relative;
  height: 96px;
  overflow: hidden;
}

.back-number-bottom {
  height: 96px;
  overflow: hidden;
  position: relative;
  z-index: -1;
}

.back-number-bottom p {
  margin: 0;
  position: relative;
  top: -96px;
}

div.front {
  background: red;
}

div.back {
  background: green;
  transform: rotateX(180deg);
}
<body>
  <div class="container">
    <p>76</p>
    <div id="cover" class="cover">
      <div class="flip front">
        <div class="front-number-bottom">
          <p>76</p>
        </div>
      </div>
      <div class="flip back">
        <div class="back-number-top">
          <p>77</p>
        </div>
      </div>
    </div>
    <div class="back-number-bottom">
      <p>77</p>
    </div>
  </div>
  </div>
</body>

这种方法是否可以在 Chromium 浏览器和 Safari 中轻松修复?

换一种方法会更好吗?

我猜你的代码有点复杂。我会像下面这样简化您的逻辑,您不再需要 backface-visibility: hidden;

注意两个重要的用法:

  • 允许我剪切元素并仅显示 50% 高度(顶部或底部)的蒙版。这将使动画更加逼真,因为每个数字的顶部和底部都会分开。
  • z-index 技巧,我在动画中间应用了一个改变 z-index 的转换(当旋转位于 90deg 时)1.

.card {
  width: 175px;
  height: 192px;
  position: relative;
  z-index:0;
  left: 50%;
  transform: translate(-50%, 50%);
  font-size: 160px;
}
.card span,
.card span::before,
.card span::after {
  position:absolute;
  top:0;
  left:0;
  right:0;
  bottom:0;
}
.card span {
  position:absolute;
  z-index:2;
  perspective: 1000px;
}
.card span:first-child {
  z-index:3;
  transition:0s 0.25s all linear;
}
.card span::before,
.card span::after{
  content:attr(data-number);
  -webkit-mask:linear-gradient(#fff,#fff) top/100% 50% no-repeat;
          mask:linear-gradient(#fff,#fff) top/100% 50% no-repeat;
  background:red;
  transition:0.5s all linear;
  transform-style: preserve-3d;
}
.card span::after {
  -webkit-mask-position:bottom;
          mask-position:bottom;
  background:green;
}

.card span:first-child::after {
    transform: rotateX(0deg);
}
.card span:last-child::before {
    transform: rotateX(-180deg);
}

/* Hover */
.card:hover span:first-child {
  z-index:1;
}
.card:hover span:first-child::after {
    transform: rotateX(180deg);
}
.card:hover span:last-child::before {
    transform: rotateX(0deg);
}
<div class="card">
  <span data-number="76"></span>
  <span data-number="77"></span>
</div>

遮罩也可以用 clip-path 代替:

.card {
  width: 175px;
  height: 192px;
  position: relative;
  z-index:0;
  left: 50%;
  transform: translate(-50%, 50%);
  font-size: 160px;
}
.card span,
.card span::before,
.card span::after {
  position:absolute;
  top:0;
  left:0;
  right:0;
  bottom:0;
}
.card span {
  z-index:2;
  perspective: 1000px;
}
.card span:first-child {
  z-index:3;
  transition:0s 0.25s all linear;
}
.card span::before,
.card span::after{
  content:attr(data-number);
  clip-path:polygon(0 0,100% 0,100% 50%,0 50%);
  background:red;
  transition:0.5s all linear;
  transform-style: preserve-3d;
}
.card span::after {
  clip-path:polygon(0 50%,100% 50%,100% 100%,0 100%);
  background:green;
}

.card span:first-child::after {
    transform: rotateX(0deg);
}
.card span:last-child::before {
    transform: rotateX(-180deg);
}

/* Hover */
.card:hover span:first-child {
  z-index:1;
}
.card:hover span:first-child::after {
    transform: rotateX(180deg);
}
.card:hover span:last-child::before {
    transform: rotateX(0deg);
}
<div class="card">
  <span data-number="76"></span>
  <span data-number="77"></span>
</div>


使用 counter 且未设置显式 width/height

的另一项优化

.card {
  margin:0 5px;
  font-family:monospace;
  display:inline-block;
  text-align:center;
  position: relative;
  z-index:0;
  font-size: 150px;
  counter-reset:num calc(var(--n,1) - 1);
}
/* this will defined the height/width*/
.card::after {
  content:counter(num);
  visibility:hidden;
}
/**/
.card span,
.card span::before,
.card span::after {
  position:absolute;
  top:0;
  left:0;
  right:0;
  bottom:0;
}
.card span {
  z-index:2;
  perspective: 1000px;
  counter-increment:num;
}
.card span:first-child {
  z-index:3;
  transition:0s 0.25s all linear;
}
.card span::before,
.card span::after{
  content:counter(num);
  clip-path:polygon(0 0,100% 0,100% 50%,0 50%);
  background:red;
  transition:0.5s all linear;
  transform-style: preserve-3d;
}
.card span::after {
  clip-path:polygon(0 50%,100% 50%,100% 100%,0 100%);
  background:green;
}

.card span:first-child::after,
.card:hover span:last-child::before{
    transform: rotateX(0deg);
}
.card span:last-child::before {
    transform: rotateX(-180deg);
}
.card:hover span:first-child::after {
    transform: rotateX(180deg);
}
.card:hover span:first-child {
  z-index:1;
}
<div class="card" style="--n:75">
  <span></span><span></span>
</div>

<div class="card" style="--n:5">
  <span></span><span></span>
</div>

<div class="card" style="--n:100">
  <span></span><span></span>
</div>


1 当使用 linear 时,它很容易,但它与其他 ease 函数一起使用时更有技巧。这是一个相关问题,可以帮助您确定缓动函数的中间值: