You probably have performed round with view transition a bunch, you could have observed that 3D transitions between two pages (i.e., cross-document view transitions) don’t appear to work. That’s, at the very least not with out the browsers flattening issues first.
Picture components are one of the best instance to display this as a result of, just like the snapshots a browser takes of the before-after states in a view transition, pictures are changed components so, in idea, we must always be capable to use them as a form of decreased check case for 3D animations. For instance, flipping one picture to disclose one other on click on seems like this:
It’s essential to notice that, for the animation to work correctly, we have to set the perspective property on the picture’s guardian container (in our case, it’s the .scene factor). In any other case, the 3D transformation is merely flat. It form of angles the factor’s look:
In CSS, the guardian’s persepective is utilized to all its kids, excluding itself:
.scene {
perspective: 1200px;
.card { /* will get perspective */ }
}
What’s essential right here is the HTML construction. Particularly how the .scene container sits on prime of the kid .card components, making the 3D impact come to life so the flip seems the way it ought to:
Maybe our keyframe animation to flip the .playing cards is one thing like this:
@keyframes flipOut {
from {
remodel: rotateY(0deg);
}
to {
remodel: rotateY(-90deg);
}
}
Which we apply to the .playing cards like this:
.card.flip-out {
animation: flipOut 5.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
.card.flip-in {
animation: flipOut 5.2s cubic-bezier(0.4, 0, 0.2, 1) forwards reverse;
}
…the place the animates runs forwards when the .flip-out class is appended to the .card (courtesy of JavaScript looking forward to a click on) and runs in reverse when the .flip-in class is appended.
That’s the setup for the way a cross-document view transition must work, too, proper? If a picture helps a 3D animation, then a view transitions snapshot ought to do the identical. Let’s poke at that.
Establishing the view transition
First issues first, we've got to decide into view transitions on each pages with the @view-transition at-rule by setting the navigation descriptor to auto:
@view-transition {
navigation: auto;
}
If we had been to do nothing else, then one web page fades into one other when navigating between the 2. It’s probably the most primary of all cross-document view transitions.
How can we customise issues? We use the ::view-transition-old() and ::view-transition-new() pseudo-classes, the place the previous is the “outdated” snapshot and the latter is the “new” one. Just like the .card components we used within the final instance, that’s the place we set the keyframe animation:
::view-transition-old(root) {
/* animation goes right here */
}
::view-transition-new(root) {
/* animation goes right here */
}
The root parameter tells the view transition to focus on the entire web page and all the weather created (and never created) by the view transition’s default snapshot group.
Right here’s the issue
Let’s say we wish to apply that very same 3D flip to the whole webpage, the place the snapshot of the “outdated” web page flips into the “new” web page. Once more, a 3D animation asks us for 2 issues:
- TheÂ
perspective property on the guardian factor so its kids get that 3D impact - An animation on the web page for when the view transition occurs
However: What precisely can we set the angle on, as in, what's the guardian factor right here?
Since view transitions take snapshots of the whole webpage, we would assume (logically) it could be the factor (or the :root), proper? I imply, the DOM tree seems like this when a view transition is current:
html
├─ ::view-transition
│ ├─ ::view-transition-group(card)
│ │ └─ ::view-transition-image-pair(card)
│ │ ├─ ::view-transition-old(card)
│ │ └─ ::view-transition-new(card)
│ └─ ::view-transition-group(identify)
│ └─ ::view-transition-image-pair(identify)
│ ├─ ::view-transition-old(identify)
│ └─ ::view-transition-new(identify)
├─ head
└─ physique
└─ …
So, the whole snapshot needs to be the place we put the perspective. Proper? Seems, no.
In truth, does nothing in any respect! You’re left with this as a substitute of the gorgeous 3D flip we had been ready to make use of on the playing cards earlier:
Right here’s the code I used to be working with:
/* Cross-document View Transition opt-in */
@view-transition {
navigation: auto;
}
/* 3D flip: Outdated web page flips away, new web page flips in */
@keyframes flip-out {
0% {
remodel: rotateY(0deg);
opacity: 1;
}
100% {
remodel: rotateY(-90deg);
opacity: 0;
}
}
@keyframes flip-in {
0% {
remodel: rotateY(90deg);
opacity: 0;
}
100% {
remodel: rotateY(0deg);
opacity: 1;
}
}
::view-transition-old(root) {
animation: flip-out 0.3s cubic-bezier(0.4, 0, 1, 1) forwards;
transform-origin: heart heart;
}
::view-transition-new(root) {
animation: flip-in 0.3s cubic-bezier(0, 0, 0.6, 1) 0.3s backwards;
transform-origin: heart heart;
}
Observe: I didn’t reverse the animation right here since we flip to -90deg after which from 90deg. Not precisely the identical!
And it doesn’t work, irrespective of if perspective is on html or :root:
/* 👎 */
html {
perspective: 1100px;
}
/* 👎 */
:root {
perspective: 1100px;
}
I did some digging and found that perspective (and 3D transformations basically) is considered one of a number of CSS properties that might produce an uncommon impact. (Depart it to Bramus to have the reply!)
So… What can we do? Some concepts got here to thoughts, however sadly failed:
- I attempted setting theÂ
perspective property on theÂphysique. - I attempted settingÂ
perspectiveinsideÂ::view-transition-group(root). - I attempted settingÂ
perspectivecontained in theÂ::view-transition pseudo.
There’s really a brilliant easy workaround to this, and I can’t imagine it took me this lengthy to determine it out — don’t use perspective in any respect!
The answer
Brief story: we've got to make use of the perspective() perform as a substitute of the perspective property. And never inside any of the ::view-transition-* pseudos as you would possibly anticipate, however contained in the @keyframes animation:
@keyframes flip-out {
0% {
remodel: perspective(1100px) rotateY(0deg);
opacity: 1;
}
100% {
remodel: perspective(1100px) rotateY(-90deg);
opacity: 0;
}
}
@keyframes flip-in {
0% {
remodel: perspective(1100px) rotateY(90deg);
opacity: 0;
}
100% {
remodel: perspective(1100px) rotateY(0deg);
opacity: 1;
}
}
This straightforward, however massive change strikes the scene from a flat meh to a phenomenal ah yeah:
Right here’s why, apparently. The view transition pseudo-element tree is rendered exterior the traditional HTML move. Extra particularly, the whole view transition tree is rendered above the DOM in its personal layer. Nevertheless, significantly for ::view-transition, I’m not too positive why that is the case, however my finest guess could be that every view transition group robotically has its place and remodel values overridden by the browser; therefore, interfering with the perspective.
The distinction between perspective and perspective()? The perspective property is utilized to the guardian factor, whereas perspective() is a remodel property perform utilized on to the factor itself. And because the view transition pseudo tree doesn't have a real guardian, we’ve gotta use perspective() because it doesn’t require a guardian. Phew.
To recap…
Setting perspective on the html, :root, or any of the view transition pseudo-class gained’t work. And in case you have been struggling to search out the answer, like I used to be, I believe this little, however massive perspective() change will remedy that subject when you ever come throughout it. Take it from me, I battled with this for weeks until I got here again right now to rant about it and found an answer to it. A perk of writing!
