-
Notifications
You must be signed in to change notification settings - Fork 785
Expand file tree
/
Copy pathOverview.bs
More file actions
3554 lines (2585 loc) · 157 KB
/
Overview.bs
File metadata and controls
3554 lines (2585 loc) · 157 KB
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 2
Shortname: css-view-transitions
Level: 2
Status: ED
Group: csswg
Prepare for TR: no
ED: https://un5n798jx6qx6j0rmf2verhh.julianrbryant.com/css-view-transitions-2/
TR: https://un5gmtkzgkj46tygt32g.julianrbryant.com/TR/css-view-transitions-2/
Work Status: exploring
Editor: Noam Rosenthal, Google, w3cid 121539
Editor: Khushal Sagar, Google, w3cid 122787
Editor: Vladimir Levin, Google, w3cid 75295
Editor: Tab Atkins-Bittner, Google, https://umnb2f9fwamm0.julianrbryant.com/contact/, w3cid 42199
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:css-view-transitions-1;
text: active view transition; type: dfn;
text: capture the new state; type: dfn;
text: capture the old state; type: dfn;
text: activate view transition; type: dfn;
text: skip the view transition; type: dfn;
text: ViewTransition; type: interface;
text: named elements; for: ViewTransition; type: dfn;
text: finished promise; for: ViewTransition; type: dfn;
text: ready promise; for: ViewTransition; type: dfn;
text: update callback; for: ViewTransition; type: dfn;
text: update callback done promise; for: ViewTransition; type: dfn;
text: initial snapshot containing block size; for: ViewTransition; type: dfn;
text: captured elements; type: dfn;
text: snapshot containing block; type: dfn;
text: old transform; for: captured element; type: dfn;
text: new element; for: captured element; type: dfn;
text: updateCallbackDone; type: property; for: ViewTransition;
text: phase; type: dfn; for: ViewTransition;
text: call the update callback; type: dfn;
text: perform pending transition operations; type: dfn;
text: setup view transition; type: dfn;
text: named view transition pseudo-element; type: dfn;
text: rendering suppression for view transitions; type: dfn;
text: view transition tree; type: dfn;
text: view transition name; type: dfn;
text: group styles rule; type: dfn;
text: update pseudo-element styles rule; type: dfn;
text: document-scoped view transition name; type: dfn;
text: global view transition user agent style sheet; type: dfn;
spec:dom; type:dfn; text:document; for:/
spec:css2; type:dfn; text:element
spec:html
text: latest entry; type: dfn;
text: has been revealed; type: dfn;
text: render-blocking mechanism; type: dfn;
spec:geometry-1
text:multiply; type:dfn;
text:matrix; type:dfn;
spec:infra; type:dfn; text:list
spec:css-borders-4; type: property;
text:border-radius;
text:border-width;
spec:css-break-4; type:dfn; text:fragment
spec:css2; type:dfn; text:viewport
</pre>
<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;
}
</style>
<script async type="module" src="diagrams/resources/scaler.js"></script>
# 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=] or an [=/element=],
or between distinct same-origin documents.
## Level 2 Updates ## {#intro-l2}
Level 2 defines the following,
extending the model in [[CSS-VIEW-TRANSITIONS-1]]:
* [[#cross-document-view-transitions|Cross-document view transitions]], including the ''@view-transition'' rule and the algorithms
that enable the cross-document view transition lifecycle.
* [[#selective-vt|Selective view transitions]], a way to match styles based on the existence of an [=active view transition=],
and more specifically based on the active view transition being of a certain type.
* [[#shared-style-with-vt-classes|Sharing styles between view transition pseudo-elements]], a way to declare a style once,
and use it for multiple view transition pseudo-elements. This includes the 'view-transition-class' property, and [[#pseudo-element-class-additions|additions to named pseudo-elements]]
* [[#view-transition-group-prop|Nested view transition groups]], a way to construct deeper hierarchies of view transition pseudo-elements,
which in turn allows relative transforms and clipping of participating elements.
* [[#scoped-vt|Scoped view transitions]], a way to perform view transitions within the scope of a DOM subtree.
## 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>element.{{Element/startViewTransition()}}</code>
and <code>document.{{Document/startViewTransition()}}</code>
create a [=view transition=] consisting of a cross-fade of the entire
[=ViewTransition/root element=] between the two DOM states.
Developers can instead choose which descendant 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://un5gmtkzgkj46tygt32g.julianrbryant.com/TR/css-animations/">CSS Animations</a>
and <a href="https://un5gmtkzgkj46tygt32g.julianrbryant.com/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}}({{ViewTransitionUpdateCallback|updateCallback}})</code>
or <code>element.{{Element/startViewTransition}}({{ViewTransitionUpdateCallback|updateCallback}})</code>,
which returns a {{ViewTransition}}, <var>viewTransition</var>.
1. Current state is captured as the “old” state.
1. Rendering is paused within the view transition [=ViewTransition/root element=].
1. Developer's {{ViewTransitionUpdateCallback|updateCallback}} function, if provided, is called,
which updates the document state.
1. <code><var>viewTransition</var>.{{ViewTransition/updateCallbackDone}}</code> fulfills.
1. Current state is captured as the “new” state.
1. Transition pseudo-elements are created.
See [[#view-transition-pseudos]] for an overview of this structure.
1. Rendering is 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">
The following set of diagrams demonstrate the transition phases that a view transition goes through while performing the above steps:
<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 {{ViewTransitionUpdateCallback}} 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 {{ViewTransitionUpdateCallback}} 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 {{ViewTransitionUpdateCallback}},
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. The callback execution order is only specified for
callbacks that result from view transitions on the same root element, or on elements related by DOM ancestors.
In general, the scheduling order 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 corresponding elements 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 simple "whole document" transition displayed a visible judder in the purple heading bar,
and had the heading text always slide to the left
even when it was "moving" to the right in the 1->2 transition.
To fix these issues,
the header and text within the header can be given their own ''view-transition-name'' for the transition,
which creates a separate ''::view-transition-group()'':
```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>
# API # {#api}
## Additions to {{Document}} ## {#additions-to-document-api}
<xmp class=idl>
partial interface Document {
ViewTransition startViewTransition(
optional (ViewTransitionUpdateCallback or StartViewTransitionOptions) callbackOptions = {}
);
readonly attribute ViewTransition? activeViewTransition;
};
callback ViewTransitionUpdateCallback = Promise<any> ();
dictionary StartViewTransitionOptions {
ViewTransitionUpdateCallback? update = null;
sequence<DOMString>? types = null;
};
</xmp>
<dl class="domintro non-normative">
: <code>{{ViewTransition|viewTransition}} = {{Document|document}}.{{Document/startViewTransition}}({{ViewTransitionUpdateCallback|updateCallback}})</code>
:: Starts a new [=view transition=]
(canceling the {{Document|document}}’s existing [=active view transition=], if any).
{{ViewTransitionUpdateCallback|updateCallback}}, if provided, is called asynchronously, once the current state of the document is captured.
Then, when the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} fulfills,
the new state of the document is captured
and the transition is initiated.
Note that {{ViewTransitionUpdateCallback|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 {{ViewTransitionUpdateCallback|updateCallback}} rejects, the transition is skipped.
</dl>
### {{Document/startViewTransition()}} Method Steps ### {#ViewTransition-prepare}
<div algorithm="start-vt-with-options">
The [=method steps=] for <dfn method for=Document>startViewTransition(|callbackOptions|)</dfn> are as follows:
1. Perform the {{Element}} {{Element/startViewTransition()}} [=method steps=]
given |callbackOptions|,
but for the [=/root element|document root element=],
and return the result.
</div>
### {{Document/activeViewTransition}} Property ### {#doc-activeviewtransition}
In order to provide ergonomic behavior,
the [=active view transition=] is exposed to script via a document property.
<dl dfn-type=attribute dfn-for=Document>
: <dfn>activeViewTransition</dfn>
:: Returns the [=active view transition=] for the [=document=],
or null if there is no active view transition.
</dl>
Note: An <code>element.startViewTransition()</code> call does not update
<code>document.activeViewTransition</code>, but will update
{{Element/activeViewTransition}} on the <code>element</code>.
## Additions to {{Element}} ## {#additions-to-element-api}
<xmp class=idl>
partial interface Element {
ViewTransition startViewTransition(
optional (ViewTransitionUpdateCallback or StartViewTransitionOptions) callbackOptions = {}
);
readonly attribute ViewTransition? activeViewTransition;
};
</xmp>
<dl class="domintro non-normative">
: <code>{{ViewTransition|viewTransition}} = {{Element|el}}.{{Element/startViewTransition}}({{ViewTransitionUpdateCallback|updateCallback}})</code>
:: Starts a new [=view transition=]
(potentially canceling a conflicting [=view transition=]).
{{ViewTransitionUpdateCallback|updateCallback}}, if provided, is called asynchronously, once the current state of the element and its subtree is captured.
Then, when the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} fulfills,
the new state of the element and its subtree is captured
and the transition is initiated.
Note that {{ViewTransitionUpdateCallback|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 {{ViewTransitionUpdateCallback|updateCallback}} rejects, the transition is skipped.
</dl>
### {{Element/startViewTransition()}} Method Steps ### {#Element-ViewTransition-prepare}
<div algorithm="start-el-vt-with-options">
The [=method steps=] for <dfn method for=Element>startViewTransition(|callbackOptions|)</dfn> are as follows:
1. Let |document| be [=this's=] [=relevant global object's=] [=associated document=].
1. Let |viewTransition| be a new {{ViewTransition}} object in |document|'s [=relevant Realm=],
with [=ViewTransition/root element=] set to [=this=].
1. If |callbackOptions| is a {{ViewTransitionUpdateCallback}},
set |viewTransition|'s [=update callback=] to |callbackOptions|.
Otherwise, if |callbackOptions| is a {{StartViewTransitionOptions}}, then set |viewTransition|'s [=update callback=]
to |callbackOptions|'s {{StartViewTransitionOptions/update}}.
1. If [=this=]'s [=principal box=] is [=element-not-rendered|not rendered=],
or is an <a spec="css-display-3">internal ruby box</a>
or a <a spec="css-display-3" lt="atomic inline">non-atomic</a> <a spec="css-display-3">inline-level</a> box,
[=Skip the view transition|skip=] |viewTransition| with an "{{InvalidStateError}}" {{DOMException}}.
1. If [=this=] is not the [=/root element|document root element=],
force [=this=] to have both [=layout containment=] and [=view transition scoping=] for the duration of the transition.
1. If |document|'s [=active view transition=] is not null and its [=outbound post-capture steps=] is not null,
then:
1. [=Skip the view transition|Skip=] |viewTransition| with an "{{InvalidStateError}}" {{DOMException}}.
Note: The |viewTransition|'s {{ViewTransition/types}} are ignored here because the transition is never activated.
1. Return |viewTransition|.
Note: This ensures that a same-document transition that started after firing {{Window/pageswap}} is skipped.
1. If [=this=]'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 [=this=]'s current [=active view transition=], and another for this |viewTransition|.
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 [=this=]'s [=active view transition=] to |viewTransition|.
Note: The [=view transition=] process continues in [=setup view transition=],
via [=perform pending transition operations=].
1. If |callbackOptions| is a {{StartViewTransitionOptions}}, set |viewTransition|'s [=ViewTransition/active types=] to a [=list/clone=] of {{StartViewTransitionOptions/types}} as a [=/set=].
1. Return |viewTransition|.
</div>
### {{Element/activeViewTransition}} Property ### {#element-activeviewtransition}
Similar to [Document.activeViewTransition](#doc-activeviewtransition),
the active view transition of an element is exposed to script via a property on the element.
<dl dfn-type=attribute dfn-for=Element>
: <dfn>activeViewTransition</dfn>
:: Returns the [=active view transition=] for the [=element=],
or null if there is no active view transition.
</dl>
## The {{ViewTransition}} interface ## {#the-domtransition-interface}
<xmp class=idl>
[Exposed=Window]
interface ViewTransition {
readonly attribute Promise<undefined> updateCallbackDone;
readonly attribute Promise<undefined> ready;
readonly attribute Promise<undefined> finished;
undefined skipTransition();
[SameObject] readonly attribute ViewTransitionTypeSet types;
readonly attribute Element transitionRoot;
undefined waitUntil(Promise<any> promise);
};
</xmp>
The {{ViewTransition}} interface represents and controls a single same-document [=view transition=],
i.e. a transition where the starting and ending document are the same,
possibly with changes to the document's DOM structure.
<dl class="domintro non-normative">
: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/updateCallbackDone}}</code>
:: A promise that fulfills when the promise returned by {{ViewTransitionUpdateCallback|updateCallback}} fulfills, or rejects when it rejects.
Note: The View Transition API wraps a DOM change and creates a visual transition.
However, sometimes you don't care about the success/failure of the transition animation,
you just want to know if and when the DOM change happens.
{{ViewTransition/updateCallbackDone}} is for that use-case.)
: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/ready}}</code>
:: A promise that fulfills once the pseudo-elements for the transition are created,
and the animation is about to start.
It rejects if the transition cannot begin.
This can be due to misconfiguration, such as duplicate 'view-transition-name's,
or if {{ViewTransition/updateCallbackDone}} returns a rejected promise.
The point that {{ViewTransition/ready}} fulfills
is the ideal opportunity to animate the [=view transition pseudo-elements=]
with the [[web-animations-1#extensions-to-the-element-interface|Web Animation API]].
: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/finished}}</code>
:: A promise that fulfills once the end state is fully visible and interactive to the user.
It only rejects if {{ViewTransitionUpdateCallback|updateCallback}} returns a rejected promise,
as this indicates the end state wasn't created.
Otherwise, if a transition fails to begin,
or is skipped (by {{ViewTransition/skipTransition()}}),
the end state is still reached,
so {{ViewTransition/finished}} fulfills.
: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/skipTransition}}()</code>
:: Immediately finish the transition, or prevent it starting.
This never prevents {{ViewTransitionUpdateCallback|updateCallback}} being called,
as the DOM change is independent of the transition.
See [[#transitions-as-enhancements]] for more details on this principle.
If this is called before {{ViewTransition/ready}} resolves, {{ViewTransition/ready}} will reject.
If {{ViewTransition/finished}} hasn't resolved, it will fulfill or reject along with {{ViewTransition/updateCallbackDone}}.
: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/transitionRoot}}</code>
:: The {{ViewTransition}}'s [=ViewTransition/root element=].
For [=view transitions=] started on the {{Document}},
this is the [=document element=].
: <code>{{ViewTransition|viewTransition}}.{{ViewTransition/waitUntil}}()</code>
:: Delays view transition finish until the given promise is settled
When invoked with Promise |p|, run [=delay finish for promise=] steps with |p|.
</dl>
A {{ViewTransition}} has the following:
<dl dfn-for="ViewTransition">
: <dfn>named elements</dfn>
:: a [=/map=], whose keys are [=view transition names=] and whose values are [=captured elements=].
Initially a new [=map=].
Note: Since this is associated to the {{ViewTransition}}, it will be cleaned up when [=Clear view transition=] is called.
: <dfn>phase</dfn>
:: One of the following ordered phases, initially "`pending-capture`":
1. "`pending-capture`".
1. "`update-callback-called`".
1. "`animating`".
1. "`done`".
Note: For the most part, a developer using this API does not need to worry about the different phases, since they progress automatically.
It is, however, important to understand what steps happen in each of the phases: when the snapshots are captured, when pseudo-element DOM is created, etc.
The description of the phases below tries to be as precise as possible, with an intent to provide an unambiguous set of steps for implementors to follow in order to produce a spec-compliant implementation.
: <dfn>update callback</dfn>
:: a {{ViewTransitionUpdateCallback}} or null. Initially null.
: <dfn>ready promise</dfn>
:: a {{Promise}}.
Initially [=a new promise=] in [=this's=] [=relevant Realm=].
: <dfn>update callback done promise</dfn>
:: a {{Promise}}.
Initially [=a new promise=] in [=this's=] [=relevant Realm=].
Note: The [=ready promise=] and [=update callback done promise=] are immediately created,
so rejections will cause {{unhandledrejection}}s unless they're [=mark as handled|handled=],
even if the getters such as {{updateCallbackDone}} are not accessed.
: <dfn>finished promise</dfn>
:: a {{Promise}}.
Initially [=a new promise=] in [=this's=] [=relevant Realm=],
[=marked as handled=].
Note: This is [=marked as handled=] to prevent duplicate {{unhandledrejection}}s,
as this promise only ever rejects along with the [=update callback done promise=].
: <dfn>transition root pseudo-element</dfn>
:: a ''::view-transition''.
Initially a new ''::view-transition''.
: <dfn>initial snapshot containing block size</dfn>
:: a [=tuple=] of two numbers (width and height), or null.
Initially null.
Note: This is used to detect changes in the [=snapshot containing block size=],
which causes the transition to [=skip the view transition|skip=].
[Discussion of this behavior](https://github.com/w3c/csswg-drafts/issues/8045).
: <dfn>active types</dfn>
:: A {{ViewTransitionTypeSet}}, initially empty.
: <dfn>outbound post-capture steps</dfn>
:: Null or a set of steps, initially null.
: <dfn>root element</dfn>
:: An {{Element}},
indicating which element is hosting the {{ViewTransition}}.
</dl>
The {{ViewTransition/finished}} [=getter steps=] are to return [=this's=] [=ViewTransition/finished promise=].
The {{ViewTransition/ready}} [=getter steps=] are to return [=this's=] [=ViewTransition/ready promise=].
The {{ViewTransition/updateCallbackDone}} [=getter steps=] are to return [=this's=] [=ViewTransition/update callback done promise=].
The {{ViewTransition/types}} [=getter steps=] are to return [=this=]'s [=ViewTransition/active types=].
The {{ViewTransition/transitionRoot}} [=getter steps=] are to return [=this=]'s [=ViewTransition/root element=].
### The {{ViewTransitionTypeSet}} Interface ### {#the-viewtransitiontypeset-interface}
<xmp class=idl>
[Exposed=Window]
interface ViewTransitionTypeSet {
setlike<DOMString>;
};
</xmp>
The {{ViewTransitionTypeSet}} object represents a [=/set=] of strings, without special semantics.
Note: a {{ViewTransitionTypeSet}} can contain strings that are invalid for '':active-view-transition-type'', e.g.
strings that are not a <<custom-ident>>.
### {{ViewTransition/skipTransition()}} Method Steps ### {#ViewTransition-skipTransition}
<div algorithm="dom-viewtransition-skipTransition">
The [=method steps=] for <dfn method for="ViewTransition">skipTransition()</dfn> are:
1. If [=this=]'s [=ViewTransition/phase=] is not "`done`",
then [=skip the view transition=] for [=this=]
with an "{{AbortError}}" {{DOMException}}.
</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=].
The values <css>none</css>, <css>auto</css>, and <css>match-element</css> are excluded from <<custom-ident>> here.
Note: If this name is not unique within a single transition
(i.e. if two elements simultaneously specify the same [=view transition name=])
then the [=view transition=] will abort. It is, however, possible to have non-unique
[=view transition name=]s as long as the elements with these names are never part
of the same transition.
</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 states 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=].
## Isolating Scoped View Transition Names: the 'view-transition-scope' property ## {#view-transition-scope-prop}
<pre class="propdef">
Name: view-transition-scope
Value: none | all
Initial: none
Inherited: no
Percentages: n/a
Computed Value: as specified
Animation type: discrete
</pre>
Note: Although 'view-transition-scope' is [=discrete|discretely animatable=],
it only has an effect during 'view-transition-name' discovery
<dl dfn=type=value dfn-for=view-transition-scope>
: <dfn>none</dfn>
:: This property has no effect on 'view-transition-name' discoverability.
: <dfn>all</dfn>
:: Discoverability of 'view-transition-name' is limited to the subtree of this element.
This is done by applying <dfn>view transition scoping</dfn>.
Such scoping means that any transitions that originate outside of the element
will not discover a non-none 'view-transition-name' either on this element
or in its flat-tree descedants.
<div class=example>
```html
<style>
.scope { view-transition-scope: all; }
#a { view-transition-name: a; }
#b { view-transition-name: b; }
#c { view-transition-name: c; }
#d { view-transition-name: d; }
</style>
<div id=a>
<div id=b class=scope>
<div id=c></div>
</div>
<div id=d></div>
</div>
<script>
...
a.startViewTransition(); // Will discover "a" and "d" view-transition-names
b.startViewTransition(); // Will discover "b" and "c" view-transition-names
...
</script>
```
</div>
Note: Starting a transition implies that [=this=] element has ''view-transition-scope: all''
for the duration of the transition.
<div class=example>
```html
<style>
#a { view-transition-name: a; }
#b { view-transition-name: b; }
#c { view-transition-name: c; }
#d { view-transition-name: d; }
</style>