欢送关注我的公众号:前端侦探
在原生 APP 中,咱们常常会看到那种丝滑又舒服的页面切换动画,比方这样的
Android 里个别称之为共享元素(shareElement)动画,也就是动画前后有一个(或多个)雷同的元素,起到前后过渡的成果,能够很分明的看到元素的变动过程,而并不是简略的隐没和呈现。
当初,web 中(chrome 111+)也迎来了这样一个个性,叫做视图转换动画 View Transitions,又称“转场动画”,也能很轻松的实现这类成果,一起理解一下吧
一、疾速意识 View Transitions
先从一个简略的例子来认识一下。
比方,上面有一个网格列表
<div class="list" id="list">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
<div class="item">10</div>
</div>
简略润饰后如下
而后咱们实现一个简略交互,点击每个元素,元素就会被删除
list.addEventListener('click', function(ev){if (ev.target.className === 'item') {ev.target.remove()
}
})
能够失去这样的成果
性能失常,就是有点太过僵硬了
当初轮到 View Transitions
出场了!咱们只须要在扭转状态的中央增加document.startViewTransition
,如下
list.addEventListener('click', function(ev){if (ev.target.className === 'item') {document.startViewTransition(() => { // 开始视图变换
ev.target.remove()});
}
})
当然为了兼容不反对的浏览器,能够做一下判断
list.addEventListener('click', function(ev){if (document.startViewTransition) { // 如果反对就视图变换
document.startViewTransition(() => { // 开始视图变换
ev.target.remove()});
} else { // 不反对就执行原来的逻辑
ev.target.remove()}
})
当初成果如下
删除前后当初有一个淡入淡出的成果了,也就是默认的动画成果,咱们能够把这个动画时长设置大一点,如下
::view-transition-old(root), /* 旧视图 */
::view-transition-new(root) { /* 新视图 */
animation-duration: 2s;
}
这两个伪元素咱们前面再做介绍,先看成果
是不是显著感觉过渡变慢了许多?
然而这种动画还是不够难受,是一种整体的变动,看不出删除前后元素的地位变动。
接下来咱们给每个元素指定一个标识,用来标记变动前后的状态,为了不便管制,能够借助 CSS 变量
<div class="list" id="list">
<div class="item" style="--i: a1">1</div>
<div class="item" style="--i: a2">2</div>
<div class="item" style="--i: a3">3</div>
<div class="item" style="--i: a4">4</div>
<div class="item" style="--i: a5">5</div>
<div class="item" style="--i: a6">6</div>
<div class="item" style="--i: a7">7</div>
<div class="item" style="--i: a8">8</div>
<div class="item" style="--i: a9">9</div>
<div class="item" style="--i: a10">10</div>
</div>
这里通过 view-transition-name
来设置名称
.item{view-transition-name: var(--i);
}
而后能够失去这样的成果,每个元素在变动前后会主动找到之前的地位,并且平滑的挪动过来,如下
残缺代码能够查看
- view-transition sort (codepen.io)’)
是不是十分丝滑?这就是 View Transitions
的魅力!
二、View Transitions 的外围概念
为啥仅仅加了一点点代码就是实现了如此顺畅的动画呢?为啥浏览器能够晓得前后的元素地位关系呢?这里简略介绍一下变动原理。
整个 JS
局部只有一行外围代码,也就是document.startViewTransition
,示意开始视图变换
document.startViewTransition(() => {// 变动操作});
整个过程包含 3 局部
- 调用
document.startViewTransition
,浏览器会捕获以后页面的状态,相似于实时截图,或者“快照” - 执行理论的 dom 变动,再次记录变动后的页面状态(截图)
- 触发两者的过渡动画,包含透明度、位移等变动,也能够自定义 CSS 动画
上面是一个示意图
在动画执行的过程中,还会在页面根节点主动创立以下伪元素
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
上面是控制台的截图
其中,::view-transition-old
示意 旧视图 的状态,也就是变动之前的截图,::view-transition-new
示意 新视图 的状态,也就是变动之后的截图。
默认状况下,整个页面 root
都会作为一个状态,也就是下面的::view-transition-group(root)
,在切换前后会执行淡入淡出动画,如下
:root::view-transition-new(root) {animation-name: -ua-view-transition-fade-in; /* 淡入动画 */}
:root::view-transition-old(root) {animation-name: -ua-view-transition-fade-out; /* 淡出动画 */}
这也是为什么在应用了 document.startViewTransition
后整个页面会有淡入淡出的成果了
为了让每个元素都有本人的过渡状态,这里须要给每个元素都指定名称
.item{view-transition-name: item-1;}
这样指定名称后,每个名称都会创立一个::view-transition-group
,示意独立的分组
这样在变动前后 view-transition-name
雷同的局部就会一一主动执行过渡动画了,以第 4、5 个元素为例(在 3 删除当前)
最初就会失去这样的成果
外围概念就这些了,上面再来看几个例子
三、不同元素之间的过渡
视图变动其实和元素是否雷同没有关联,有关联的只有 view-transition-name
,浏览器是依据view-transition-name
寻找的,也就是雷同名称的元素在前后会有一个过渡动画。
比方上面这样一个例子
每个按钮在关上弹窗时,都能够分明的看到弹窗是从哪里呈现的,如何实现这样的成果呢?
从实质上看,其实就是 按钮到弹窗的视图变换 ,依照后面讲到的,可能会想到给前后加上雷同的view-transition-name
,上面是HTML
构造
<div class="bnt-group" id="group">
<button> 按钮 1 </button>
<button> 按钮 2 </button>
<button> 按钮 3 </button>
</div>
<dialog id="dialog">
<form method="dialog">
我是弹窗
<button> 敞开 </button>
</form>
</dialog>
尝试一下
button,dialog{view-transition-name: dialog;}
而后增加点击关上弹窗事件
group.addEventListener('click', function(ev){if (ev.target.tagName === 'BUTTON') {if (document.startViewTransition) {document.startViewTransition(() => {dialog.showModal()
});
} else {dialog.showModal()
}
}
})
这样会有什么问题吗?运行如下
很显著报错了,意思就是一个页面中不能有雷同的 view-transition-name
。严格来讲,是 不能同时呈现,如果其余元素都是暗藏的,只有一个是显示的,也没有问题。其实认真想一下,也很好了解,如果同时有两个雷同的名称,并且都可见,最初变换的时候该以哪一个为准呢?
所以,在这种状况下,正确的做法应该是动静设置view-transition-name
,比方默认不给按钮增加名称,只有在点击的时候才增加,而后在变换完结之后再移除按钮的view-transition-name
,实现如下
group.addEventListener('click', function(ev){if (ev.target.tagName === 'BUTTON') {
ev.target.style.viewTransitionName = 'dialog' // 动静增加 viewTransitionName
if (document.startViewTransition) {document.startViewTransition(() => {
ev.target.style.viewTransitionName = '' // 完结后移除 viewTransitionName
dialog.showModal()});
} else {dialog.showModal()
}
}
})
这样就实现了动静缩放的成果
大抵曾经实现想要的成果,不过还有一个小问题,咱们把速度加快一点(把动画时长设置长一点)
能够分明的看到,本来的按钮先放大到了弹窗大小,而后逐步隐没。这个过程是咱们不须要的,有没有方法去掉呢?
当然也是能够的!本来的按钮其实就是旧视图,也就是点击之前的截图,咱们只须要将这个视图暗藏起来就行了
::view-transition-old(dialog) {display: none;}
这样就完满实现了从哪里点击就从哪里关上的成果
残缺代码能够查看:
- view-transition-dialog (codepen.io)’)
四、自定义过渡动画
通过后面的例子能够看出,默认状况下,视图转换动画是一种淡入淡出的动画,而后还有如果地位、大小不同,也会平滑过渡。
除此之外,咱们还能够手动指定过渡动画。比方上面这个例子
这是一个光明模式的繁难模型,实现也非常简单,筹备两套主题,这里用 color-scheme
实现
无关 color-scheme 的更多详情能够参考这篇文章:CSS color-scheme 和夜间模式
.dark{color-scheme: dark;}
而后通过点击动静给 html
切换 dark
类名
btn.addEventListener('click', function(ev){document.documentElement.classList.toggle('dark')
})
这样就失去了主题切换成果
接着,咱们增加视图转换动画
btn.addEventListener('click', function(ev){if (document.startViewTransition) {document.startViewTransition(() => {document.documentElement.classList.toggle('dark')
});
} else {document.documentElement.classList.toggle('dark')
}
})
这样就失去了一个默认的淡入淡出的切换成果(为了不便察看,将动画时长缩短了)
你能够把前后变动设想成是两张截图的变动,如果要实现点击呈现圆形裁剪动画,其实就是在新视图上执行一个裁剪动画,因为是齐全重叠的,所以看着像是一种穿透扩散的成果
动画很简略,就是一个 clip-path
动画
@keyframes clip {
from {clip-path: circle(0%);
}
to{clip-path: circle(100%);
}
}
咱们把这个动画放在 ::view-transition-new
中
::view-transition-new(root) {
/* mix-blend-mode: normal; */
animation: clip .5s ease-in;
/* animation-duration: 2s; */
}
成果如下
是不是还有点奇怪?这是因为默认的一些款式导致,包含原有的淡出成果,还有混合模式
所以还须要去除这些影响
::view-transition-old(root) {animation: none;}
::view-transition-new(root) {
mix-blend-mode: normal;
animation: clip .5s ease-in;
}
当然你能够把鼠标点击的地位传递到页面根节点
btn.addEventListener('click', function(ev){document.documentElement.style.setProperty('--x', ev.clientX + 'px')
document.documentElement.style.setProperty('--y', ev.clientY + 'px')
if (document.startViewTransition) {document.startViewTransition(() => {document.documentElement.classList.toggle('dark')
});
} else {document.documentElement.classList.toggle('dark')
}
})
动画里间接通过 CSS 变量获取
@keyframes clip {
from {clip-path: circle(0% at var(--x) var(--y));
}
to{clip-path: circle(100% at var(--x) var(--y));
}
}
这样就实现了完满的扩散切换成果
残缺代码能够查看:
- view transition theme change (codepen.io)’)
五、其余案例
找了几个乏味的例子
只有波及到前后过渡变动的,都能够思考用这个个性,例如上面的拖拽排序
https://codepen.io/argyleink/pen/rNQZbLr
再比方这样一个数字过渡动画
https://codepen.io/argyleink/pen/jOQKdeW
还有相似于 APP 的转场动画(多页面跳转)
https://simple-set-demos.glitch.me/6-expanding-image/
五、总结和阐明
总的来说,原生的视图转换动画能够很轻松的实现两种状态的过渡,让 web 也能实现媲美原生 APP 的动画体验,上面再来回顾一下整个变动过程:
- 调用
document.startViewTransition
,浏览器会捕获以后页面的状态,相似于实时截图,或者“快照” - 执行理论的 dom 变动,再次记录变动后的页面状态(截图)
- 触发两者的过渡动画,包含透明度、位移等变动,也能够自定义 CSS 动画
- 默认状况下是整个页面的淡入淡出变动
::view-transition-old
示意 旧视图 的状态,也就是变动之前的截图,::view-transition-new
示意 新视图 的状态,也就是变动之后的截图。- 如果须要指定具体元素的变动,能够给该元素指定
view-transition-name
- 前后变动不肯定要同一元素,浏览器是依据
view-transition-name
寻找的 - 同一时间页面上不能呈现两个雷同
view-transition-name
的元素,不然视图变动会生效
另外,视图转换动画应该作为一种 体验加强 的性能,而非必要性能,在应用动画时其实拖慢了页面关上或者更新的速度,并且在动画过程中,页面是齐全“解冻”的,做不了任何事件,因而须要掂量好动画的工夫,如果页面自身就很慢就更不要应用这些动画了。最初,如果感觉还不错,对你有帮忙的话,欢送点赞、珍藏、转发❤❤❤
欢送关注我的公众号:前端侦探