Reputation: 73
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:
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?
Upvotes: 7
Views: 720
Reputation: 273086
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:
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?
Upvotes: 1