forked from w3c/csswg-drafts
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathOverview.bs
2039 lines (1487 loc) · 90 KB
/
Overview.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
Title: CSS View Transitions Module Level 1
Shortname: css-view-transitions
Level: 1
Status: ED
Prepare for TR: no
Group: csswg
ED: https://drafts.csswg.org/css-view-transitions-1/
TR: https://www.w3.org/TR/css-view-transitions-1/
Work Status: exploring
Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
Editor: Jake Archibald, Google, w3cid 76394
Editor: Khushal Sagar, Google, w3cid 122787
Implementation Report: https://wpt.fyi/results/css/css-view-transitions
Abstract: This module defines the View Transition API, along with associated properties and pseudo-elements,
which allows developers to create animated visual transitions representing changes in the document state.
Markup Shorthands: css yes, markdown yes
</pre>
<pre class=link-defaults>
spec:webidl; type:dfn; text:resolve
spec:css-position-3; type:property
text: inset-block-start
text: inset-inline-start
spec:css-shapes-3; type:function; text:rect()
spec:webidl; type:interface; text:Promise
spec:css-images-4; type:function; text:element()
spec:dom; type:dfn; text:document
spec:css-2022; type:dfn; text:style sheet
spec:selectors-4; type:dfn;
text:selector
text:type selector
spec:css-box-4; type:dfn; text:border box
spec:css-display-3; type:dfn;
text:containing block
text:replaced element
spec:css-cascade-5; type:dfn; text:computed value
spec:css22; type:dfn; text:element
spec:css-break-4; type:dfn; text:fragment
spec:css-viewport; type:dfn; text:interactive-widget;
spec:css-display-4; type: dfn; text:invisible;
spec:css2; type:dfn; text:viewport
</pre>
<pre class=anchors>
urlPrefix: https://wicg.github.io/navigation-api/; type: interface;
text: NavigateEvent
text: signal; for: NavigateEvent; url: #ref-for-dom-navigateevent-signal①
</pre>
<script async type="module" src="diagrams/resources/scaler.js"></script>
<style>
spec-scaler {
display: block;
}
spec-scaler:not(:defined) > * {
display: none;
}
.spec-slides {
width: 100%;
height: 100%;
border: none;
display: block;
}
.spec-slide-controls {
text-align: center;
}
.main-example-video {
display: block;
width: 100%;
max-width: 702px;
height: auto;
margin: 0 auto;
}
/* Put nice boxes around each algorithm. */
[data-algorithm]:not(.heading) {
padding: .5em;
border: thin solid #ddd; border-radius: .5em;
margin: .5em calc(-0.5em - 1px);
}
[data-algorithm]:not(.heading) > :first-child {
margin-top: 0;
}
[data-algorithm]:not(.heading) > :last-child {
margin-bottom: 0;
}
[data-algorithm] [data-algorithm] {
margin: 1em 0;
}
pre {
tab-size: 2;
}
.domintro {
position: relative;
color: green;
background: #DDFFDD;
margin: 2.5em 0 2em 0;
padding: 1.5em 1em 0.5em 2em;
}
.domintro dt, .domintro dt * {
color: black;
font-size: inherit;
}
.domintro dd {
margin: 0.5em 0 1em 2em; padding: 0;
}
.domintro dd p {
margin: 0.5em 0;
}
.domintro::before {
content: 'For web developers (non-normative)';
background: green;
color: white;
padding: 0.15em 0.25em;
font-style: normal;
position: absolute;
top: -0.8em;
left: -0.8em;
}
</style>
# Introduction # {#intro}
*This section is non-normative.*
This specification introduces a DOM API and associated CSS features
that allow developers to create animated visual transitions,
called <dfn export>view transitions</dfn>
between different states of a [=/document=].
## Separating Visual Transitions from DOM Updates ## {#separating-transitions}
Traditionally, creating a visual transition between two document states
required a period where both states were present in the DOM at the same time.
In fact, it usually involved creating a specific DOM structure
that could represent both states.
For example, if one element was “moving” between containers,
that element often needed to exist outside of either container for the period of the transition,
to avoid clipping from either container or their ancestor elements.
This extra in-between state often resulted in UX and accessibility issues,
as the structure of the DOM was compromised for a purely-visual effect.
[=View Transitions=] avoid this troublesome in-between state
by allowing the DOM to switch between states instantaneously,
then performing a customizable visual transition between the two states in another layer,
using a static visual capture of the old state, and a live capture of the new state.
These captures are represented as a tree of [=pseudo-elements=]
(detailed in [[#view-transition-pseudos]]),
where the old visual state co-exists with the new state,
allowing effects such as cross-fading
while animating from the old to new size and position.
## View Transition Customization ## {#customizing}
By default, <code>document.{{Document/startViewTransition()}}</code>
creates a [=view transition=] consisting of
a page-wide cross-fade between the two DOM states.
Developers can also choose which elements are captured independently
using the 'view-transition-name' CSS property,
allowing these to be animated independently of the rest of the page.
Since the transitional state (where both old and new visual captures exist)
is represented as pseudo-elements,
developers can customize each transition using familiar features
such as <a href="https://www.w3.org/TR/css-animations/">CSS Animations</a>
and <a href="https://www.w3.org/TR/web-animations/">Web Animations</a>.
## View Transition Lifecycle ## {#lifecycle}
A successful [=view transition=] goes through the following phases:
1. Developer calls <code>document.{{Document/startViewTransition}}({{UpdateCallback|updateCallback}})</code>,
which returns a {{ViewTransition}}, <var>viewTransition</var>.
1. Current state captured as the “old” state.
1. Rendering paused.
1. Developer's {{UpdateCallback|updateCallback}} function, if provided, is called,
which updates the document state.
1. <code><var>viewTransition</var>.{{ViewTransition/updateCallbackDone}}</code> fulfills.
1. Current state captured as the “new” state.
1. Transition pseudo-elements created.
See [[#view-transition-pseudos]] for an overview of this structure.
1. Rendering unpaused, revealing the transition pseudo-elements.
1. <code><var>viewTransition</var>.{{ViewTransition/ready}}</code> fulfills.
1. Pseudo-elements animate until finished.
1. Transition pseudo-elements removed.
1. <code><var>viewTransition</var>.{{ViewTransition/finished}}</code> fulfills.
<div id="phases-diagram">
<spec-scaler canvaswidth="1920" canvasheight="1080" style="aspect-ratio: 1920/1080">
<iframe class="spec-slides" src="diagrams/phases/phases.html"></iframe>
</spec-scaler>
<p class="spec-slide-controls">
<button disabled>Previous</button>
<button disabled>Next</button>
</p>
<script type="module">
const root = document.querySelector('#phases-diagram');
const [previous, next] = root.querySelectorAll('.spec-slide-controls button');
const iframe = root.querySelector('iframe');
next.disabled = false;
const updateButtons = (slide) => {
next.disabled = !slide.hasNext;
previous.disabled = !slide.hasPrevious;
};
next.addEventListener('click', async () => {
const slide = iframe.contentDocument.querySelector('spec-slide');
await slide.next();
updateButtons(slide);
});
previous.addEventListener('click', async () => {
const slide = iframe.contentDocument.querySelector('spec-slide');
await slide.previous();
updateButtons(slide);
});
</script>
</div>
## Transitions as an enhancement ## {#transitions-as-enhancements}
A key part of the View Transition API design
is that an animated transition is a visual <em>enhancement</em>
to an underlying document state change.
That means a failure to create a visual transition,
which can happen due to misconfiguration or device constraints,
will not prevent the developer's {{UpdateCallback}} being called,
even if it's known in advance that the transition animations cannot happen.
For example, if the developer calls {{ViewTransition/skipTransition()}} at the start of the [[#lifecycle|view transition lifecycle]],
the steps relating to the animated transition,
such as creating the [=view transition tree=],
will not happen.
However, the {{UpdateCallback}} will still be called.
It's only the visual transition that's skipped,
not the underlying state change.
Note: If the DOM change should also be skipped,
then that needs to be handled by another feature.
<code>{{NavigateEvent|navigateEvent}}.{{NavigateEvent/signal}}</code> is an example of a feature developers could use to handle this.
Although the View Transition API allows DOM changes to be asynchronous via the {{UpdateCallback}},
the API is not responsible for queuing or otherwise scheduling DOM changes
beyond any scheduling needed for the transition itself.
Some asynchronous DOM changes can happen concurrently (e.g if they're happening within independent components),
whereas others need to queue, or abort an earlier change.
This is best left to a feature or framework that has a more holistic view of the application.
## Rendering Model ## {#rendering-model}
View Transition works by replicating an element's rendered state using UA generated pseudo-elements.
Aspects of the element's rendering which apply to the element itself or its descendants,
for example visual effects like 'filter' or 'opacity' and clipping from 'overflow' or 'clip-path',
are applied when generating its image in [=Capture the image=].
However, properties like 'mix-blend-mode' which define how the element draws when it is embedded can't be applied to its image.
Such properties are applied to the element's corresponding ''::view-transition-group()'' pseudo-element,
which is meant to generate a box equivalent to the element.
If the ''::view-transition-group()'' has a corresponding element in the "new" states,
the browser keeps the properties copied over to the ''::view-transition-group()'' in sync with the DOM element in the "new" state.
If the ''::view-transition-group()'' has a corresponding both in the "old" and "new" state,
and the property being copied is interpolatable,
the browser also sets up a default animation to animate the property smoothly.
## Examples ## {#examples}
<div class=example>
Taking a page that already updates its content using a pattern like this:
```js
function spaNavigate(data) {
updateTheDOMSomehow(data);
}
```
A [=view transition=] could be added like this:
```js
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// With a transition:
document.startViewTransition(() => updateTheDOMSomehow(data));
}
```
This results in the default transition of a quick cross-fade:
<figure>
<video src="diagrams/videos/default.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
</figure>
The cross-fade is achieved using CSS animations on a [[#view-transition-pseudos|tree of pseudo-elements]],
so customizations can be made using CSS. For example:
```css
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
```
This results in a slower transition:
<figure>
<video src="diagrams/videos/slow.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
</figure>
</div>
<div class=example>
Building on the previous example, motion can be added:
```css
@keyframes fade-in {
from { opacity: 0; }
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(30px); }
}
@keyframes slide-to-left {
to { transform: translateX(-30px); }
}
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
```
Here's the result:
<figure>
<video src="diagrams/videos/slide.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
</figure>
</div>
<div class=example>
Building on the previous example,
the header and text within the header can be given their own ''::view-transition-group()''s for the transition:
```css
.main-header {
view-transition-name: main-header;
}
.main-header-text {
view-transition-name: main-header-text;
/* Give the element a consistent size, assuming identical text: */
width: fit-content;
}
```
By default, these groups will transition size and position from their “old” to “new” state,
while their visual states cross-fade:
<figure>
<video src="diagrams/videos/header.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
</figure>
</div>
<div class=example>
Building on the previous example, let's say some pages have a sidebar:
<figure>
<video src="diagrams/videos/bad-sidebar.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
</figure>
In this case, things would look better if the sidebar was static if it was in both the “old” and “new” states.
Otherwise, it should animate in or out.
The '':only-child'' pseudo-class can be used to create animations specifically for these states:
```css
.sidebar {
view-transition-name: sidebar;
}
@keyframes slide-to-right {
to { transform: translateX(30px); }
}
/* Entry transition */
::view-transition-new(sidebar):only-child {
animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Exit transition */
::view-transition-old(sidebar):only-child {
animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}
```
For cases where the sidebar has both an “old” and “new” state, the default animation is correct.
<figure>
<video src="diagrams/videos/good-sidebar.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
</figure>
</div>
<div class=example>
Not building from previous examples this time,
let's say we wanted to create a circular reveal from the user's cursor.
This can't be done with CSS alone.
Firstly, in the CSS, allow the “old” and “new” states to layer on top of one another without the default blending,
and prevent the default cross-fade animation:
```css
::view-transition-image-pair(root) {
isolation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
```
Then, the JavaScript:
```js
// Store the last click event
let lastClick;
addEventListener('click', event => (lastClick = event));
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// Get the click position, or fallback to the middle of the screen
const x = lastClick?.clientX ?? innerWidth / 2;
const y = lastClick?.clientY ?? innerHeight / 2;
// Get the distance to the furthest corner
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
);
// Create a transition:
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
});
// Wait for the pseudo-elements to be created:
transition.ready.then(() => {
// Animate the root's new view
document.documentElement.animate(
{
clipPath: [
\`circle(0 at ${x}px ${y}px)\`,
\`circle(${endRadius}px at ${x}px ${y}px)\`,
],
},
{
duration: 500,
easing: 'ease-in',
// Specify which pseudo-element to animate
pseudoElement: '::view-transition-new(root)',
}
);
});
}
```
And here's the result:
<figure>
<video src="diagrams/videos/circle.mp4" style="aspect-ratio: 1404/738" class="main-example-video" controls muted loop playsinline></video>
</figure>
</div>
# CSS properties # {#css-properties}
## Tagging Individually Transitioning Subtrees: the 'view-transition-name' property ## {#view-transition-name-prop}
<pre class=propdef>
Name: view-transition-name
Value: none | <<custom-ident>>
Initial: none
Inherited: no
Percentages: n/a
Computed Value: as specified
Animation type: discrete
</pre>
Note: though 'view-transition-name' is [=discrete|discretely animatable=], animating it doesn't
affect the running view transition. Rather, it's a way to set its value in a way that can change
over time or based on a [=timeline=]. An example for using this would be to change the 'view-transition-name'
based on [=scroll-driven animations=].
The 'view-transition-name' property “tags” an element
for [=capture in a view transition=],
tracking it independently in the [=view transition tree=]
under the specified <dfn>view transition name</dfn>.
An element so captured is animated independently of the rest of the page.
<dl dfn-type=value dfn-for=view-transition-name>
: <dfn>none</dfn>
:: The [=/element=] will not participate independently in a view transition.
: <dfn><<custom-ident>></dfn>
:: The [=/element=] participates independently in a view transition--
as either an old or new [=/element=]--
with the specified [=view transition name=].
Each [=view transition name=] is a [=tree-scoped name=].
Note: Since currently only document-scoped view transitions are supported, only view transition names that are associated with the document are respected.
The values <css>none</css> and <css>auto</css> are excluded from <<custom-ident>> here.
Note: If this name is not unique
(i.e. if two elements simultaneously specify the same [=view transition name=])
then the [=view transition=] will abort.
</dl>
Note: For the purposes of this API,
if one element has [=view transition name=] ''foo'' in the old state,
and another element has [=view transition name=] ''foo'' in the new state,
they are treated as representing different visual state of the same element,
and will be paired in the [=view transition tree=].
This may be confusing, since the elements themselves are not necessarily referring to the same object,
but it is a useful model to consider them to be visual states of the same conceptual page entity.
If the element’s [=principal box=] is [=fragmented=],
[=skips its contents|skipped=],
or [=element-not-rendered|not rendered=],
this property has no effect.
See [[#algorithms]] for exact details.
<div algorithm>
To get the <dfn>document-scoped view transition name</dfn> for an {{Element}} |element|:
1. Let |scopedViewTransitionName| be the [=computed value=] of 'view-transition-name' for |element|.
1. If |scopedViewTransitionName| is associated with |element|'s [=node document=], then return |scopedViewTransitionName|.
1. Otherwise, return ''view-transition-name/none''.
</div>
### Rendering Consolidation ### {#named-and-transitioning}
[=/Elements=] [=captured in a view transition=] during a [=view transition=]
or whose 'view-transition-name' [=computed value=] is not ''view-transition-name/none'' (at any time):
- Form a [=stacking context=].
- Are [[css-transforms-2#grouping-property-values|flattened in 3D transforms]].
- Form a [=backdrop root=].
# Pseudo-elements # {#pseudo}
## Pseudo-element Trees ## {#pseudo-root}
Note: This is a general definition for trees of pseudo-elements. If other features need this behavior, these definitions will be moved to [[css-pseudo-4]].
A <dfn>pseudo-element root</dfn> is a type of [=tree-abiding pseudo-element=] that is the [=tree/root=] in a [=tree=] of [=tree-abiding pseudo-elements=],
known as the <dfn>pseudo-element tree</dfn>.
The [=pseudo-element tree=] defines the document order of its [=tree/descendant=] [=tree-abiding pseudo-elements=].
When a [=pseudo-element=] [=tree/participates=] in a [=pseudo-element tree=],
its [=originating pseudo-element=] is its [=tree/parent=].
If a [=tree/descendant=] |pseudo| of a [=pseudo-element root=] has no other [=tree/siblings=],
then '':only-child'' matches that |pseudo|.
Note: This means that `::view-transition-new(ident):only-child` will only select `::view-transition-new(ident)` if the parent `::view-transition-image-pair(ident)` contains a single [=tree/child=].
As in, there is no [=tree/sibling=] `::view-transition-old(ident)`.
## View Transition Pseudo-elements ## {#view-transition-pseudos}
The visualization of a [=view transition=]
is represented as a [=pseudo-element tree=]
called the <dfn>view transition tree</dfn>
composed of the <dfn>view transition pseudo-elements</dfn> defined below.
This tree is built during the [=setup transition pseudo-elements=] step,
and is rooted under a ''::view-transition'' pseudo-element
[=originating element|originating=] from the [=root element=].
All of the [=view transition pseudo-elements=] are selected
from their [=ultimate originating element=], the [=document element=].
The [=view transition tree=] is not exposed to the accessibility tree.
<div class="example">
For example,
the ''::view-transition-group()'' pseudo-element is attached to the root element selector directly,
as in '':root::view-transition-group()'';
it is not attached to its parent, the ''::view-transition'' pseudo-element.
</div>
<div class=note>
Once the user-agent has captured both the “old” and “new” states of the document,
it creates a structure of pseudo-elements like the following:
```
::view-transition
├─ ::view-transition-group(name)
│ └─ ::view-transition-image-pair(name)
│ ├─ ::view-transition-old(name)
│ └─ ::view-transition-new(name)
└─ …other groups…
```
Each element with a 'view-transition-name' is captured separately,
and a ''::view-transition-group()'' is created for each unique 'view-transition-name'.
For convenience, the [=document element=] is given the 'view-transition-name' "root" in the [[#ua-styles|user-agent style sheet]].
Either ''::view-transition-old()'' or ''::view-transition-new()'' are absent in cases where the capture does not have an “old” or “new” state.
Each of the pseudo-elements generated can be targeted by CSS in order to customize its appearance,
behavior and/or add animations.
This enables full customization of the transition.
</div>
### Named View Transition Pseudo-elements ### {#named-view-transition-pseudo}
Several of the [=view transition pseudo-elements=]
are <dfn>named view transition pseudo-elements</dfn>,
which are [=functional pseudo-element|functional=] [=tree-abiding pseudo-element|tree-abiding=] [=view transition pseudo-elements=]
associated with a [=view transition name=].
These pseudo-elements take a <<pt-name-selector>> as their argument,
and their syntax follows the pattern:
<pre class=prod>
::view-transition-<var>pseudo</var>(<<pt-name-selector>>)
</pre>
where <<pt-name-selector>> selects a [=view transition name=],
and has the following syntax definition:
<pre class=prod>
<dfn><pt-name-selector></dfn> = '*' | <<custom-ident>>
</pre>
A [=named view transition pseudo-element=] [=selector=] only matches
a corresponding [=pseudo-element=]
if its <<pt-name-selector>> matches that [=pseudo-element=]’s [=view transition name=],
i.e. if it is either ''*'' or a matching <<custom-ident>>.
Note: The [=view transition name=] of a [=view transition pseudo-element=]
is set to the 'view-transition-name' that triggered its creation.
The specificity of a [=named view transition pseudo-element=] [=selector=]
with a <<custom-ident>> argument
is equivalent to a [=type selector=].
The specificity of a [=named view transition pseudo-element=] [=selector=]
with a ''*'' argument
is zero.
### View Transition Tree Root: the ''::view-transition'' pseudo-element ### {#view-transition-pseudo}
The <dfn>::view-transition</dfn> [=pseudo-element=]
is a [=tree-abiding pseudo-element=] that is also a [=pseudo-element root=].
Its [=originating element=] is the document's [=document element=],
and its [=containing block=] is the [=snapshot containing block=].
Note: This element serves as the [=tree/parent=] of all ''::view-transition-group()'' pseudo-elements.
### View Transition Named Subtree Root: the ''::view-transition-group()'' pseudo-element ### {#::view-transition-group}
The <dfn>::view-transition-group()</dfn> [=pseudo-element=]
is a [=named view transition pseudo-element=]
that represents a matching named [=view transition=] capture.
A ''::view-transition-group()'' [=pseudo-element=]
is generated for each [=view transition name=]
as a [=tree/child=] of the ''::view-transition'' [=pseudo-element=],
and contains a corresponding ''::view-transition-image-pair()''.
<div class=note>
This element initially mirrors the size and position of the “old” element,
or the “new” element if there isn't an “old” element.
If there's both an “old” and “new” state,
styles in the [=document/dynamic view transition style sheet=] animate this pseudo-element's 'width' and 'height'
from the size of the old element's [=border box=] to that of the new element's [=border box=].
Also the element's 'transform' is animated from the old element's screen space transform to the new element's screen space transform.
This style is generated dynamically since the values of animated properties are determined at the time that the transition begins.
</div>
### View Transition Image Pair Isolation: the ''::view-transition-image-pair()'' pseudo-element ### {#::view-transition-image-pair}
The <dfn>::view-transition-image-pair()</dfn> [=pseudo-element=]
is a [=named view transition pseudo-element=]
that represents a pair of corresponding old/new [=view transition=] captures.
This pseudo-element is a [=tree/child=] of the corresponding ''::view-transition-group()'' pseudo-element
and contains
a corresponding ''::view-transition-old()'' pseudo-element
and/or
a corresponding ''::view-transition-new()'' pseudo-element
(in that order).
<div class=note>
This element exists to provide ''isolation: isolate'' for its children,
and is always present as a [=tree/child=] of each ''::view-transition-group()''.
This isolation allows the image pair to be blended with non-normal blend modes
without affecting other visual outputs.
</div>
### View Transition Old State Image: the ''::view-transition-old()'' pseudo-element ### {#::view-transition-old}
The <dfn>::view-transition-old()</dfn> [=pseudo-element=]
is an empty [=named view transition pseudo-element=]
that represents a visual snapshot of the “old” state as a [=replaced element=];
it is omitted if there's no “old” state to represent.
Each ''::view-transition-old()'' pseudo-element is a [=tree/child=]
of the corresponding ''::view-transition-image-pair()'' pseudo-element.
<div class=note>
'':only-child'' can be used to match cases where this element is the only element in the ''::view-transition-image-pair()''.
The appearance of this element can be manipulated with `object-*` properties in the same way that other replaced elements can be.
</div>
Note: The content and [=natural dimensions=] of the image are captured in [=capture the image=],
and set in [=setup transition pseudo-elements=].
Note: Additional styles in the [=document/dynamic view transition style sheet=] added to animate these pseudo-elements
are detailed in [=setup transition pseudo-elements=] and [=update pseudo-element styles=].
### View Transition New State Image: the ''::view-transition-new()'' pseudo-element ### {#::view-transition-new}
The <dfn>::view-transition-new()</dfn> [=pseudo-element=]
(like the analogous ''::view-transition-old()'' pseudo-element)
is an empty [=named view transition pseudo-element=]
that represents a visual snapshot of the “new” state as a [=replaced element=];
it is omitted if there's no “new” state to represent.
Each ''::view-transition-new()'' pseudo-element is a [=tree/child=]
of the corresponding ''::view-transition-image-pair()'' pseudo-element.
Note: The content and [=natural dimensions=] of the image are captured in [=capture the image=],
then set and updated in [=setup transition pseudo-elements=] and [=update pseudo-element styles=].
# View Transition Layout # {#view-transition-rendering}
The [=view transition pseudo-elements=] are styled, laid out, and rendered like normal elements,
except that they originate in the [=snapshot containing block=] rather than the [=initial containing block=]
and are painted in the [=view transition layer=] above the rest of the document.
## The Snapshot Containing Block ## {#snapshot-containing-block-concept}
The <dfn>snapshot containing block</dfn> is a rectangle
that covers all areas of the window that could potentially display page content
(and is therefore consistent regardless of root scrollbars or [=interactive-widget|interactive widgets=]).
This makes it likely to be consistent for the [=document element=]'s [=captured element/old image=] and [=captured element/new element=].
Within a [=child navigable=], the [=snapshot containing block=] is the union of the navigable's [=viewport=] with any [=scrollbar gutters=].
<figure>
<img src="diagrams/phone-browser.svg" width="200" height="335" alt="A diagram of a phone screen, including a top status bar, a browser URL bar, web-content area with a floating scrollbar, a virtual keyboard, and a bottom bar with an OS back button">
<img src="diagrams/phone-browser-snapshot-root.svg" width="200" height="335" alt="The previous diagram, but highlights the area that's the 'snapshot containing block', which includes everything except the top status bar and the bottom bar with the OS back button">
<figcaption>
An example of the [=snapshot containing block=] on a mobile OS.
The snapshot includes the URL bar, as this can be scrolled away.
The keyboard is included as this appears and disappears.
The top and bottom bars are part of the OS rather than the browser, so they're not included in the snapshot containing block.
</figcaption>
</figure>
<figure>
<img src="diagrams/desktop-browser.svg" width="132" height="79" alt="A diagram of a desktop browser window, including a tab bar, a URL bar, and a web-content area featuring both horizontal and vertical scrollbars" style="height:auto; width: 600px">
<img src="diagrams/desktop-browser-snapshot-root.svg" width="132" height="79" alt="The previous diagram, but highlights the area that's the 'snapshot containing block', which includes the web content area and the scrollbars" style="height:auto; width: 600px">
<figcaption>
An example of the [=snapshot containing block=] on a desktop OS.
This includes the scrollbars, but does not include the URL bar, as web content never appears in that area.
</figcaption>
</figure>
The <dfn>snapshot containing block origin</dfn> refers to the top-left corner of the [=snapshot containing block=].
The <dfn>snapshot containing block size</dfn> refers to the width and height of the [=snapshot containing block=] as a [=/tuple=] of two numbers.
The [=snapshot containing block=] is considered to be an [=absolute positioning containing block=]
and a [=fixed positioning containing block=] for ''::view-transition'' and its descendants.
## View Transition Painting Order ## {#view-transition-stacking-layer}
This specification introduces a new stacking layer, the [=view transition layer=],
to the end of the painting order established in
<a href="https://www.w3.org/TR/CSS22/zindex.html">CSS2§E Elaborate Description of Stacking Contexts</a>. [[!CSS2]]
The ''::view-transition'' pseudo-element generates a new stacking context,
called the <dfn>view transition layer</dfn>,
which paints after all other content of the document
(including any content rendered in the [=Document/top layer=]),
after any filters and effects that are applied to such content.
(It is not subject to such filters or effects,
except insofar as they affect the rendered contents
of the ''::view-transition-old()'' and ''::view-transition-new()'' pseudo-elements.)
Note: The intent of the feature is to be able to capture the contents of the page, which includes the top layer elements.
In order to accomplish that, the [=view transition layer=] cannot be a part of the captured stacking contexts,
since that results in a circular dependency.
Therefore, the [=view transition layer=] is a sibling of all other content.
When a {{Document}}'s [=document/active view transition=]'s [=ViewTransition/phase=] is "`animating`",
the boxes generated by any element in that {{Document}} with [=captured in a view transition=]
and its [=element contents=],
except [=ViewTransition/transition root pseudo-element=]'s [=tree/inclusive descendants=],
are not painted (as if they had ''visibility: hidden'') and
do not respond to hit-testing (as if they had ''pointer-events: none'').
Note: Elements participating in a transition need to skip painting in their DOM location because
their image is painted in the corresponding ''::view-transition-new()'' pseudo-element instead.
Similarly, hit-testing is skipped because the element's DOM location does not correspond to where its contents are rendered.
However, there is no change in how these elements are accessed by assistive technologies or the accessibility tree.
# User Agent Stylesheet # {#ua-styles}
The <dfn>global view transition user agent style sheet</dfn> is
a [=user-agent origin=] style sheet containing the following rules:
```css
:root {
view-transition-name: root;
}
:root::view-transition {
position: fixed;
inset: 0;
}
:root::view-transition-group(*) {
position: absolute;
top: 0;
left: 0;
animation-duration: 0.25s;
animation-fill-mode: both;
}
:root::view-transition-image-pair(*) {
position: absolute;
inset: 0;
animation-duration: inherit;
animation-fill-mode: inherit;
animation-delay: inherit;
}
:root::view-transition-old(*),
:root::view-transition-new(*) {
position: absolute;
inset-block-start: 0;
inline-size: 100%;
block-size: auto;
animation-duration: inherit;
animation-fill-mode: inherit;
animation-delay: inherit;
}
/* Default cross-fade transition */
@keyframes -ua-view-transition-fade-out {
to { opacity: 0; }
}
@keyframes -ua-view-transition-fade-in {
from { opacity: 0; }
}
/* Keyframes for blending when there are 2 images */
@keyframes -ua-mix-blend-mode-plus-lighter {
from { mix-blend-mode: plus-lighter }
to { mix-blend-mode: plus-lighter }
}
```
<details class="note">
<summary>Explanatory Summary</summary>
This UA style sheet does several things:
* Lay out ''::view-transition'' to cover the entire [=snapshot containing block=]
so that each '':view-transition-group()'' child can lay out relative to it.
* Give the [=root element=] a default [=view transition name=],
to allow it to be independently selected.
* Reduce layout interference from the ''::view-transition-image-pair()'' pseudo-element
so that authors can essentially treat ''::view-transition-old()'' and ''::view-transition-new()''
as direct children of ''::view-transition-group()'' for most purposes.
* Inherit animation timing through the tree so that by default,
the animation timing set on a ''::view-transition-group()''
will dictate the animation timing of all its descendants.
* Style the element captures ''::view-transition-old()'' and ''::view-transition-new()''
to match the size and position set on ''::view-transition-group()''
(insofar as possible without breaking their aspect ratios)
as it interpolates between them.
Since the sizing of these elements depends on the mapping between logical and physical coordinates,
[=dynamic view transition style sheet=] copies relevant styles from the DOM elements.
* Set up a default quarter-second cross-fade animation
for each ''::view-transition-group()''.
</details>
Additional styles are dynamically added to the [=user-agent origin=] during a [=view transition=]
through the [=document/dynamic view transition style sheet=].
# API # {#api}
## Additions to {{Document}} ## {#additions-to-document-api}
<xmp class=idl>
partial interface Document {
ViewTransition startViewTransition(optional UpdateCallback updateCallback);
};
callback UpdateCallback = Promise<any> ();
</xmp>
<dl class="domintro non-normative">
: <code>{{ViewTransition|viewTransition}} = {{Document|document}}.{{startViewTransition}}({{UpdateCallback|updateCallback}})</code>
:: Starts a new [=view transition=]
(canceling the {{Document|document}}’s existing [=active view transition=], if any).
{{UpdateCallback|updateCallback}}, if provided, is called asynchronously, once the current state of the document is captured.
Then, when the promise returned by {{UpdateCallback|updateCallback}} fulfills,
the new state of the document is captured
and the transition is initiated.
Note that {{UpdateCallback|updateCallback}}, if provided, is *always* called,
even if the transition cannot happen
(e.g. due to duplicate `view-transition-name` values).
The transition is an enhancement around the state change, so a failure to create a transition never prevents the state change.
See [[#transitions-as-enhancements]] for more details on this principle.
If the promise returned by {{UpdateCallback|updateCallback}} rejects, the transition is skipped.
</dl>
### {{Document/startViewTransition()}} Method Steps ### {#ViewTransition-prepare}
<div algorithm>
The [=method steps=] for <dfn method for=Document>startViewTransition(|updateCallback|)</dfn> are as follows:
1. Let |transition| be a new {{ViewTransition}} object in [=this's=] [=relevant Realm=].
1. If |updateCallback| is provided, set |transition|'s [=ViewTransition/update callback=] to |updateCallback|.
1. Let |document| be [=this's=] [=relevant global object's=] [=associated document=].
1. If |document|'s [=Document/visibility state=] is "<code>hidden</code>",
then [=skip the view transition|skip=] |transition| with an "{{InvalidStateError}}" {{DOMException}},
and return |transition|.
1. If |document|'s [=active view transition=] is not null,
then [=skip the view transition|skip that view transition=]
with an "{{AbortError}}" {{DOMException}} in [=this's=] [=relevant Realm=].
Note: This can result in two asynchronous [=ViewTransition/update callbacks=] running concurrently
(and therefore possibly out of sequence):
one for the |document|'s current [=active view transition=], and another for this |transition|.
As per the [design of this feature](#transitions-as-enhancements), it's assumed that the developer is using another feature or framework to correctly schedule these DOM changes.
1. Set |document|'s [=active view transition=] to |transition|.
Note: The [=view transition=] process continues in [=setup view transition=],
via [=perform pending transition operations=].
1. Return |transition|.
</div>
## The {{ViewTransition}} interface ## {#the-domtransition-interface}
<xmp class=idl>
[Exposed=Window]
interface ViewTransition {
readonly attribute Promise<undefined> updateCallbackDone;