1. 介绍
本节为非规范性内容。
视图转换,如 [css-view-transitions-1] 中所规定的,是一个允许开发者 在文档的视觉状态之间创建动画转换的功能。
第2级扩展了该规范,通过添加必要的 API 和生命周期来启用 跨同源跨文档导航的转换,以及一些让编写具有更丰富视图转换的页面变得更容易的附加功能。
第2级定义了以下内容:
-
跨文档视图转换,包括 @view-transition 规则和 启用跨文档视图转换生命周期的算法。
-
在视图转换伪元素之间共享样式,一种声明一次样式, 并将其用于多个视图转换伪元素的方法。这包括 view-transition-class 属性,以及对命名伪元素的添加
2. 跨文档视图转换
2.1. 概述
本节为非规范性内容。
2.1.1. 激活
对于同文档视图转换,作者通过调用startViewTransition
使用 JavaScript 来启动视图转换。
在跨文档视图转换中,触发视图转换的是两个文档之间的导航,只要满足以下条件:
-
两个文档都是同源的;
-
页面在整个导航过程中保持可见;
-
用户通过与页面交互来发起导航,例如点击链接或提交表单;或者通过与浏览器 UI 交互来进行
traverse
导航(前进/后退)。这排除了例如通过 URL 栏发起的导航; -
导航没有包含跨源重定向;并且
-
两个文档都使用 @view-transition 规则选择了跨文档视图转换。
详细信息请参见生命周期部分。
2.1.2. 等待新状态稳定
在同文档视图转换中,作者可以使用传递给startViewTransition
的回调来指示转换的新状态何时处于稳定状态。
由于跨文档视图转换是声明性的,因此没有这样的显式承诺。相反,用户代理依赖渲染阻塞机制来决定文档何时达到稳定状态。
通过这种方式,作者可以使用 blocking
属性来延迟转换,直到:
-
所有预期的脚本都执行完毕,通过在必需的脚本上使用脚本的
blocking
属性。 -
所有预期的样式都应用完毕,通过在必需的样式上使用样式或链接的
blocking
属性。 -
所有预期的 HTML 元素都被解析器看到,使用
expect
HTMLLinkElement
元素。
注意:过度使用渲染阻塞机制可能会使 旧状态长时间保持冻结,导致用户体验不佳。 为了避免这种情况,建议确保渲染阻塞元素能及时可用。
在这个例子中,旧文档的最后一帧将会显示,动画将被延迟, 直到满足以下所有条件:
-
style.css
被应用,以确保新状态具有正确的样式 -
fixup.js
被运行,以确保演示已通过基于脚本的修复更新到最新状态。 -
main-article
部分被看到和解析,以确保在允许转换继续之前加载了足够的内容。
<!DOCTYPE html> < html > < head > < !-- 这将默认阻塞渲染 -->< link rel = "stylesheet" href = "style.css" > < !-- 由于这个脚本修复了布局,将其标记为渲染阻塞将 确保在视图转换激活之前运行它 -->< script async href = "fixup.js" blocking = "render" ></ script > < !-- 等待 main-article 元素被看到并完全解析后 再激活转换 -->< link rel = "expect" href = "#main-article" blocking = "render" > </ head > < body > < header > ...</ header > < main > < article id = "main-article" > ...</ article > </ main > < article id = "secondary-article" > ...</ article > </ body > </ html >
2.1.3. 自定义
ViewTransition
对象允许在脚本中自定义转换。
同文档视图转换使用从 startViewTransition
调用返回的单个 ViewTransition
对象来处理整个生命周期。
跨文档视图转换有两个 ViewTransition
对象,一个在旧文档中,一个在新文档中。
2.1.3.1. 在旧文档中处理视图转换
pageswap
事件在文档即将被卸载并被另一个文档替换之前的最后时刻触发。
它可以用来了解视图转换是否即将发生,使用 types
自定义它,
对捕获的元素做最后的更改,或在必要时跳过它。
PageSwapEvent
接口有一个 viewTransition
对象,当导航符合视图转换条件时,该对象将为非 null,
以及一个 activation
对象,提供有关导航的便用信息,如重定向后的 URL。
转换的 finished
承诺可以用于在转换后进行清理,以防文档稍后从 BFCache 中恢复。
2.1.3.2. 在新文档中处理视图转换
pagereveal
事件在呈现新文档的第一帧之前触发。
它可以用来通过查询 viewTransition
属性来了解视图转换是否仍然有效。
类似于同文档视图转换,作者现在可以选择不同的 types
,
对捕获的元素做最后的更改,等待转换ready
以便为其添加动画,或完全跳过它。
2.1.4. 生命周期
本节为非规范性内容。
成功的跨文档视图转换会经过以下阶段:
-
在旧的
Document
中:-
用户发起导航,例如点击链接、提交表单、按浏览器的后退按钮等。
注意:某些导航不会触发视图转换,例如在 URL 栏中输入新地址。
-
如果导航是同源的,没有跨源重定向, 并且旧的
Document
已选择了跨文档视图转换,事件的viewTransition
属性将是 一个ViewTransition
对象。 -
如果
ViewTransition
没有被跳过,旧文档的状态将被捕获。
-
-
然后,在新的
Document
中:-
当新的
Document
准备好进行第一次渲染机会时,一个名为pagereveal
的事件在新的Document
上触发, 带有一个viewTransition
属性。 -
这个
ViewTransition
的
承诺已经解决, 其捕获的元素从旧的updateCallbackDone
Document
中填充。 -
新文档的状态被捕获作为转换的"新"状态。
-
从这一点开始,转换以类似于同文档转换的方式继续,按照激活视图转换。
-
2.2. 示例
相反,我们在页面1和页面2中都选择在导航时触发视图转换:
// 在两个文档中:@view-transition { navigation : auto; }
从页面1到页面2或从页面2的链接将生成示例1的交叉淡入淡出转换。 要实现示例2、3和4的效果,只需将伪元素的 CSS 放在两个文档中。
@view-transition { navigation : auto; } @media ( max-width:600 px ) { navigation : none; }
-
在两个页面中都选择导航触发的视图转换。
-
将点击位置传递给新文档,例如通过
sessionStorage
。 -
使用
pagereveal
事件拦截新文档中的ViewTransition
对象。
在两个页面中:
在旧页面中:@view-transition { navigation : auto; }
addEventListener( 'click' , event=> { sessionStorage. setItem( "lastClickX" , event. clientX); sessionStorage. setItem( "lastClickY" , event. clientY); });
在新页面中:
// 这将在初始加载和从 BFCache 重新激活时运行。 addEventListener( "pagereveal" , async event=> { if ( ! event. viewTransition) return ; const x= sessionStorage. getItem( "lastClickX" ) ?? innerWidth/ 2 ; const y= sessionStorage. getItem( "lastClickY" ) ?? innerHeight/ 2 ; const endRadius= Math. hypot( Math. max( x, innerWidth- x), Math. max( y, innerHeight- y) ); await event. viewTransition. ready; // 为新文档的视图添加动画 document. documentElement. animate( { clipPath: [ < code data- opaque bs- autolink- syntax= '`circle(0 at ${x}px ${y}px)`' > circle( 0 at ${ x} px ${ y} px) < /code>,< code data- opaque bs- autolink- syntax= '`circle(${endRadius}px at ${x}px ${y}px)`' > circle( ${ endRadius} px at ${ x} px ${ y} px) < /code>,], }, { duration: 500 , easing: 'ease-in' , pseudoElement: '::view-transition-new(root)' } ); })
在旧页面中:
window. addEventListener( "pageswap" , event=> { // 例如,页面被隐藏,或导航是跨文档的。 if ( ! event. viewTransition) return ; // 如果您不希望前进/后退导航使用视图转换... if ( event. activation. navigationType=== "traverse" ) { event. viewTransition. skipTransition(); } const newURL= new URL( event. activation. entry. url); if ( newURL. pathname=== "/details" && thumbnail. complete) { thumbnail. classList. add( "transition-to-hero" ); // 如果页面从 BFCache 恢复,这将清理状态。 event. viewTransition. finished. then(() => { thumbnail. classList. remove( "transition-to-hero" ); }); } });
在新页面中:
window. addEventListener( "pagereveal" , event=> { // 例如,页面被隐藏,导航是跨文档的,或转换在旧文档中被跳过。 if ( ! event. viewTransition) return ; const oldURL= new URL( navigation. activation. from . url); if ( newURL. pathname=== "/list" ) { event. viewTransition. types. add( "coming-from-list" ); // 这将显示缩略图直到视图转换完成。 if ( ! hero. complete) { setToThumbnail( hero); event. viewTransition. finished. then(() => { setToFullResolution( hero); }) } } });
2.3. 选择跨文档视图转换
2.3.1. @view-transition 规则
@view-transition 规则被文档用来指示跨文档导航
应该设置并激活一个 ViewTransition
。
@view-transition 规则具有以下语法:
@view-transition { <declaration-list> }
@view-transition 规则接受 navigation 和 types 描述符。
注意:按照默认行为,@view-transition 规则可以嵌套在条件组规则内,如 @media 或 @supports。
当 Document
document 的 @view-transition 规则发生变化时,用户代理必须更新出站转换的选择状态给定 document。
注意:这需要在布尔值中缓存,因为在导航时需要并行读取结果。
2.3.2. navigation 描述符
名称: | navigation |
---|---|
适用于: | @view-transition |
值: | auto | none |
初始值: | none |
'navigation' 描述符选择在执行某种类型的导航时自动启动视图转换。 必须在新旧文档中都存在。
- none
-
不会有转换。
- auto
-
如果导航是同源的,没有跨源重定向,并且其
NavigationType
是以下之一,转换将被启用
此 at-rule 符合 CSS 的向前兼容解析要求; 不理解这些规则的符合标准的解析器将忽略它们而不出错。 任何给定用户代理不识别或未实现的描述符, 或其值不匹配此处或本规范未来版本中给出的语法的描述符, 必须完全忽略; 它们不会使 @view-transition 规则无效。
2.3.3. 使用 CSSOM 访问 @view-transition 规则
CSSViewTransitionRule
表示一个 @view-transition 规则。
[Exposed =Window ]interface :
CSSViewTransitionRule CSSRule {readonly attribute CSSOMString ; [
navigation SameObject ]readonly attribute FrozenArray <CSSOMString >; };
types
navigation
getter 步骤是如果存在相应的 navigation 描述符则返回其值,否则返回空字符串。
types
getter 步骤是如果存在相应的 types 描述符则返回其值,否则返回空列表。
3. 选择性视图转换
3.1. 概述
本节为非规范性内容。
对于简单页面和单一视图转换,在参与元素上设置 view-transition-name 属性应该就足够了。 然而,在更复杂的场景中,作者可能希望声明各种视图转换,并只同时运行其中一个。 例如,在点击导航栏时滑动整个页面,在拖拽列表项时对列表进行排序。
为了确保每个视图转换只捕获其所需的内容,并且不同的转换不会相互干扰, 本规范引入了活动类型的概念,以及 :active-view-transition 和 :active-view-transition-type() 伪类。
:active-view-transition 在文档元素具有活动视图转换时匹配,而 :active-view-transition-type() 在选择器中的类型与文档元素的活动视图转换的活动类型匹配时匹配。
ViewTransition
的
活动
类型通过以下方式之一填充:
-
作为
startViewTransition(callbackOptions)
的参数传递 -
随时使用转换的
types
进行修改 -
使用 types 描述符为跨文档视图转换声明。
3.2. 示例
document. startViewTransition({ update: updateTheDOMSomehow, types: [ "slide-in" , "reverse" ]});
这将激活以下任何选择器:
:root:active-view-transition-type ( slide-in) {} :root:active-view-transition-type ( reverse) {} :root:active-view-transition-type ( slide-in, reverse) {} :root:active-view-transition-type ( slide-in, something-else) {} :root:active-view-transition{}
而不提供转换类型启动转换时,只会激活 ':active-view-transition':
document. startViewTransition( updateTheDOMSomehow); // 或 document. startViewTransition({ update: updateTheDOMSomehow});
/* 这将被激活 */ :root{ } :root:active-view-transition{} /* 这将不会被激活 */ :root:active-view-transition-type ( slide-in) {} :root:active-view-transition-type ( any-type-at-all-except-star) {}
3.3. 基于活动视图转换的选择
3.3.1. :active-view-transition 伪类
:active-view-transition 伪类应用于文档的根元素,如果它具有活动视图转换。
:active-view-transition 的特异性是一个伪类选择器。
:active-view-transition 伪类在其节点 文档具有非空活动视图转换时匹配文档元素。
3.3.2. :active-view-transition-type() 伪类
:active-view-transition-type() 伪类应用于文档的根元素,如果它具有匹配的活动视图转换。 它具有以下语法定义:
:active-view-transition-type(<custom-ident>#)
:active-view-transition-type() 的特异性是一个伪类选择器。
:active-view-transition-type() 伪类在其节点 文档具有非空活动视图转换, 且其活动类型 包含至少一个 <custom-ident> 参数时匹配文档元素。
3.4. 更改正在进行的视图转换的类型
ViewTransition
接口扩展如下:
[Exposed =Window ]interface {
ViewTransitionTypeSet setlike <DOMString >; }; [Exposed =Window ]partial interface ViewTransition {attribute ViewTransitionTypeSet ; };
types
ViewTransitionTypeSet
对象表示一个字符串集合,没有特殊语义。
注意:ViewTransitionTypeSet
可以包含对 :active-view-transition-type 无效的字符串,例如
不是 <custom-ident> 的字符串。
types
getter
步骤是返回 this
的 活动类型。
3.5. 为跨文档视图转换激活转换类型
types 描述符
名称: | types |
---|---|
适用于: | @view-transition |
值: | none | <custom-ident>+ |
初始值: | none |
'types' 描述符为转换设置活动类型,
在捕获或执行转换时,等同于使用该 types
调用 startViewTransition(callbackOptions)
。
注意:types 描述符仅应用于
定义它的 Document
。
作者有责任在两个文档中使用其选择的类型集。
4. 在视图转换伪元素之间共享样式
4.1. 概述
本节为非规范性内容。
在以类似方式设置 DOM 中多个元素的样式时,通常使用 类 属性: 设置一个在多个元素之间共享的名称,然后使用 类选择器 来声明共享样式。
视图转换伪元素(例如 view-transition-group())不是在 DOM 中定义的,而是通过使用 view-transition-name 属性定义的。 为此,view-transition-class CSS 属性为视图转换提供了等同于 HTML 类 的功能。 当具有 view-transition-name 的元素也具有 view-transition-class 值时,该类可以通过伪元素选择, 如 示例 所示。
4.2. 示例
< div class = "box" id = "red-box" ></ div > < div class = "box" id = "green-box" ></ div > < div class = "box" id = "yellow-box" ></ div >
div.box{ view-transition-class : any-box; width : 100 px ; height : 100 px ; } #red-box{ view-transition-name : red-box; background : red; } #green-box{ view-transition-name : green-box; background : green; } #yellow-box{ view-transition-name : yellow-box; background : yellow; } /* 以下样式将应用于所有框,感谢 'view-transition-class' */ ::view-transition-group ( *.any-box) { animation-duration : 1 s ; }
4.3. view-transition-class 属性
名称: | view-transition-class |
---|---|
值: | none | <custom-ident>+ |
初始值: | none |
适用于: | 所有元素 |
继承性: | 否 |
百分比: | 不适用 |
计算值: | 按指定值 |
规范顺序: | 按语法 |
动画类型: | 离散 |
view-transition-class 可用于将相同的样式规则应用于多个 命名视图转换伪元素,这些元素可能具有不同的 view-transition-name。 虽然 view-transition-name 用于匹配旧状态中的元素与其在新状态中对应的元素, view-transition-class 仅用于使用视图转换伪元素应用样式 (::view-transition-group()、::view-transition-image-pair()、::view-transition-old()、::view-transition-new())。
请注意,view-transition-class 本身不会将元素标记为捕获,它仅用作 为已经具有 view-transition-name 的元素设置样式的附加方式。
- none
-
不会有类应用于为此元素生成的 命名视图转换伪元素。
- <custom-ident>+
-
所有指定的 <custom-ident> 值(除了 none)在用于 命名视图转换伪元素选择器时会被应用。 none 对于 view-transition-class 是无效的 <custom-ident>,即使与另一个 <custom-ident> 组合使用也是如此。
每个"视图转换类"都是一个 树作用域名称。
注意:如果在转换的旧状态和新状态中都为元素指定了相同的 view-transition-name, 则仅应用来自新状态的 view-transition-class 值。这也适用于跨文档视图转换: 来自旧文档的类仅在其对应的 view-transition-name 未在新文档中指定时才会应用。
4.4. 命名视图转换伪元素的扩展
命名视图转换伪元素(view-transition-group()、view-transition-image-pair()、view-transition-old()、view-transition-new()) 被扩展以支持以下语法:
::view-transition-group(<pt-name-and-class-selector>) ::view-transition-image-pair(<pt-name-and-class-selector>) ::view-transition-old(<pt-name-and-class-selector>) ::view-transition-new(<pt-name-and-class-selector>)
其中 <pt-name-selector> 的工作方式如之前定义的,而 <pt-name-and-class-selector> 具有以下语法定义:
<pt-name-and-class-selector> = <pt-name-selector> <pt-class-selector>? | <pt-class-selector> <pt-class-selector> = ['.' <custom-ident>]+
在解释上述语法时,禁止使用空白:
-
在 <pt-class-selector> 的任何组件之间。
在其 <pt-class-selector> 中具有一个或多个 <custom-ident> 值的 命名视图转换伪元素 选择器 仅在伪元素的 view-transition-name 在 命名元素 中的 类列表 值 包含 所有这些值时才匹配元素。
具有以下任一情况的 命名视图转换伪元素 选择器 的 特异性:
-
具有 <custom-ident> 的 <pt-name-selector>;或
-
具有至少一个 <custom-ident> 的 <pt-class-selector>,
等同于 类型选择器。
具有 * 参数且具有空 <pt-class-selector> 的 命名视图转换伪元素 选择器 的 特异性 为零。
5. 扩展 document.startViewTransition()
dictionary {
StartViewTransitionOptions ViewTransitionUpdateCallback ?=
update null ;sequence <DOMString >?=
types null ; };partial interface Document {ViewTransition startViewTransition (optional (ViewTransitionUpdateCallback or StartViewTransitionOptions )= {}); };
callbackOptions
startViewTransition(callbackOptions)
的方法步骤如下:
-
让 updateCallback 为 null。
-
如果 callbackOptions 是一个
ViewTransitionUpdateCallback
, 将 updateCallback 设置为 callbackOptions。 -
否则,如果 callbackOptions 是一个
StartViewTransitionOptions
, 则将 updateCallback 设置为 callbackOptions 的update
。 -
如果 this 的 活动视图转换 不为 null 且其 出站后捕获步骤 不为 null, 则:
-
让 preSkippedTransition 为 this 的 相关域 中的一个新
ViewTransition
, 其 更新回调 为 updateCallback。注意:此处忽略 preSkippedTransition 的
types
, 因为转换从未被激活。 -
使用 "
InvalidStateError
"DOMException
跳过 preSkippedTransition。 -
返回 preSkippedTransition。
注意:这确保在触发
pageswap
后启动的同文档转换被跳过。 -
-
让 viewTransition 为运行
startViewTransition(updateCallback)
的方法 步骤,传入 updateCallback 的结果。 -
如果 callbackOptions 是一个
StartViewTransitionOptions
, 将 viewTransition 的 活动类型 设置为types
作为集合的克隆。 -
返回 viewTransition。
6. 自动确定 view-transition-name
6.1. 概述
本节为非规范性内容。
要让元素参与视图转换,需要为其分配一个唯一的 view-transition-name。 当多个元素涉及同一个视图转换时,这可能变得繁琐和冗长,特别是在许多元素都是相同元素转换的情况下, 即元素在旧状态和新状态中具有相同的 view-transition-name。
为了简化这一过程,将 view-transition-name 设置为 auto 将为元素生成一个 view-transition-name,或者如果元素存在 id 属性则使用该值。
6.2. 示例
< ul > < li > 项目 1</ li > < li > 项目 2</ li > < li > 项目 3</ li > < li > 项目 4</ li > ...</ ul >
通常情况下,每个项目都必须接收一个唯一的 view-transition-name:
li : nth-child ( 1 ) { view-transition-name : item1; } li:nth-child ( 2 ) { view-transition-name : item2; } li:nth-child ( 3 ) { view-transition-name : item3; } li:nth-child ( 4 ) { view-transition-name : item4; } ...
使用 auto,这段 CSS 就能够工作:
li{ view-transition-name : auto; }
6.3. view-transition-name 的扩展
除了现有值之外,view-transition-name 还接受一个 auto 关键字。 要解析 element 的 view-transition-name 的 已用值:
-
让 computed 为 view-transition-name 的 计算值。
-
如果 computed 是 none,返回 null。
-
如果 computed 是一个 <custom-ident>,返回 computed。
-
断言:computed 是 auto。
-
如果 element 具有关联的 id,且 computed 与 element 的 根 关联的根相同,则返回 element 的 id 值。
-
返回一个唯一字符串。该字符串应该对此元素和
Document
保持一致且唯一,至少在 element 的 节点文档 的 活动视图转换 的生命周期内。注意:此字符串不是 Web 可观察的,用于在内部算法中寻址元素。
注意:当在跨文档视图转换中使用时,生成的 auto 值永远不会匹配,从而产生独立的 ::view-transition-group() 伪元素, 一个退出,一个进入。
由 auto 生成的 view-transition-name 是一个 树作用域名称。
7. 嵌套视图转换
7.1. 概述
本节为非规范性内容。
默认情况下,在多个元素上设置 view-transition-name
会生成一个扁平的 视图转换树,其中所有的 ::view-transition-group() 伪元素都是 ::view-transition 伪元素的子元素。
这对许多简单的用例来说是足够的,但有一些样式用例无法通过扁平树实现。
最突出的用例是裁剪:在扁平树中,所有伪元素都被裁剪到 快照包含块,因此在正常树中裁剪元素会在视图转换期间失去裁剪效果,导致视觉效果错误。
在扁平树中可能产生意外视觉效果的效果有:
-
裁剪(overflow、clip-path、border-radius):裁剪会影响元素的子元素。
-
opacity、mask-image 和 filter:这些效果被设计为作用于完全栅格化的树图像,而不是单独作用于每个项目。
-
3D 变换(transform-style、transform、perspective):要显示完整的 3D 变换动画范围,需要保持某些层次结构。
为了启用这些用例,本规范引入了嵌套视图转换伪元素的概念。通过使用 view-transition-group CSS 属性, 作者可以为生成的 ::view-transition-group() 伪元素分配一个"父组",在 视图转换树中创建层次结构。
7.2. 示例
< section class = "container" > < article > 内容</ article > </ section >
.container{ view-transition-name : container; } .container, ::view-transition-group ( container) { clip-path : circle (); } article{ view-transition-name : article; view-transition-group : container; }
通过将 clip-path 同时应用于包含元素及其生成的伪元素,我们在转换期间保持了裁剪效果, 通过将 view-transition-group 应用于引用容器的内部元素,我们使树以能够应用此裁剪的方式"嵌套"。