Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm developing a flip animation to show new numbers; it's much like an analog clock or calendar with the hinge in the middle.

The approach is straight forward: have a div with:

  • The bottom half of the first number on one side
  • The top half of the second number rotated 180 degrees so it's on the back

In order to show the new number, I rotate that whole div around the center of the container, revealing the back of the rotating div: Number flip animation in latest Firefox

However, in Chrome, the animation doesn't always work. Sometimes half disappears completely until the transition animation is complete and sometimes the old number doesn't render: Number flip animation in latest Chrome with the bottom of the number not appearing till after animation is complete

In Safari 12, it's worse, it doesn't seem to respect backface-visibility, even with the -webkit- prefix: Safari 12 Number animation, the bottom half of the first number is inverted after animation is complete

Pre-Chromium Edge handles this fine, but new (checked in v83) Edge has the same issue as Chrome.

I've tried messing around with the properties and have looked through other backface-visibility questions here.

Here's the code, hover over the numbers to see the flip:

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>

Is this a sound approach that can be easily fixed in Chromium browsers and Safari?

Would a different approach be better?

like image 223
Nicholas Weber Avatar asked Jun 23 '20 22:06

Nicholas Weber


People also ask

What happens when an element is styled as follows Backface-visibility hidden?

The back face is visible when turned towards the user. The back face is hidden, effectively making the element invisible when turned away from the user.

Can I use Backface-visibility hidden?

That's because the default for backface-visibility is visible . Instead of it being visible, you could hide it.

Do CSS animations work on all browsers?

Note: All browsers support the animation property without vendor prefixes. Only WebKit (Safari), and not Chromium, based browsers supports the -webkit-animation media feature.


1 Answers

I guess your code is a bit complex. I would simplify your logic like below where you no more need backface-visibility: hidden;

Note the usage of two important things:

  • the mask that allow me to cut the element and show only 50% of the height (top or bottom). This will make the animation more realistic since each number will have both top and bottom part seperated.
  • the z-index trick where I apply a transtion that change the z-index exactly at the middle of the animation (when the rotations are at 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>

The mask can be replaced with clip-path too:

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

Another optimization using counter and without setting an explicit 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 When using linear it's pretty easy but it's more trick with other ease functions. Here is a related question that can help you identify the middfle of ease functions: When exactly does an ease animation reach its midpoint?

like image 194
Temani Afif Avatar answered Oct 06 '22 23:10

Temani Afif