浏览器渲染原理
在实际开始学习CSS动画之前,需要先对浏览器渲染有个简单的了解
浏览器渲染过程
- 根据HTML构建HTML树(DOM)
- 根据CSS构建CSS树(CSSOM)
- 将两棵树合并成一棵渲染树(render tree)
- Layout布局(文档流、盒模型、计算大小和位置)
- Paint绘制(把边框颜色、文字颜色、阴影等画出来)
- Compose合成(根据层叠关系展示画面)
三种更新方式
- JS / CSS > 样式 > 布局 > 绘制 > 合成
如果修改了元素的“layout”属性,也就是改变了元素的几何属性(例如宽度、高度、左侧或顶部位置等),那么浏览器将必须检查所有其他元素,然后“自动重排”页面。任何受影响的部分都需要重新绘制,而且最终绘制的元素需进行合成。
- JS / CSS > 样式 > 绘制 > 合成
如果修改了“paint only”属性(例如背景图片、文字颜色或阴影等),即不会影响页面布局的属性,则浏览器会跳过布局,但仍将执行绘制。
- JS / CSS > 样式 > 合成
如果更改了一个既不要布局也不要绘制的属性,则浏览器将跳到只执行合成。这个最后的版本开销最小,最适合于应用生命周期中的高压力点,例如动画或滚动。
注意:如果想知道更改任何指定 CSS 属性将触发上述三个版本中的哪一个,可以查看CSStriggers
以上图文,来自渲染树构建、布局及绘制
一个简单的例子
怎么实现div从左往右移动,有三种方法:
left
left实现示例 JS代码
var n = 1
var id = setInterval(() => {
console.log(n)
if (n <= 100) {
demo.style.left = n / 100 * 300 + 'px'
n = n + 1
}else{
clearInterval(id) //n到200取消动画
}
}, 1000 / 30) //30帧
原理:每过一段时间(用setInterval实现)将div移动一小段距离,直到移动到目标地点
但我们一般不会使用left做动画,如图
绿色表示重新绘制(repaint),此过程便是上述三种更新方式的第一种,它需要走完布局,绘制,合成,导致动画的性能不好。
注意:查看渲染的方式
- 打开开发者工具
- 键盘"ESC"键
- 新窗口左边三个点,点击Rendering
- 勾选Paint flashing
- 刷新动画查看
transform + translate
#demo{
width: 100px;
height: 100px;
border: 1px solid red;
transition: all 1s linear; /* 过渡属性,可以自动补充中见帧 */
}
#demo.end{
transform: translateX(300px);
}
setTimeout(()=>{
demo.classList.add('end')
},0)
原理:通过 transform:translateX(0==>300),运用transition过渡属性自动补齐中间帧
如图所示:
此方法没有repaint(重新绘制),而是直接合成,所以它的性能较好
animation
animation实现示例 此方法可以仅用CSS实现动画
#demo {
width: 100px;
height: 100px;
border: 1px solid red;
animation:run 4s forwards;
}
@keyframes run {
0% {transform:none;}
100% {transform:translateX(300px);}
}
原理:定义动画的名字run,并写明运动过程0%与100%方框的位置,便可通过animation来实现方框运动
此方法与transform一样,拥有较好的性能
transform总结
完整指南->transform-MDN
translateZ
- transform: translateZ()
- 我们把X作为横轴,Y作为纵轴,那么Z便是从屏幕方向指向我们的轴
- 此用法直接写看不出效果
- 举个例子:
#demo:hover{
transform: translateZ(-200px);/* 靠近原点 */
}
.wrapper{
perspective: 1000px; /* 透视图原点所在位置 */
border: 1px solid black;
}
我们需要给demo添加一个父容器wrapper,并给出perspective: Xpx;,它表示我们构建的一个坐标轴原点距离屏幕有X个px,这时我们便可以看到translateZ的效果:
鼠标经过,它在以Z轴方向往原点靠拢 Z轴示例
transition总结
完整指南->transition-MDN
- 作用:补充中间帧
- 语法:transition:属性名 时长 过渡方式 延迟
- 可以用all代表所有属性
并不是所有属性都能用transition
如display:block->none 没法过渡 如何实现动画消失的效果?
- opacity:1->0
#demo {
width: 100px;
height: 100px;
border:1px solid red;
transition: all 1s;
opacity: 1;
}
#demo.end {
width: 200px;
height: 200px;
opacity: 0;
}
x.onclick = ()=> {
demo.classList.add('end');
}
demo.addEventListener('transitionend', function(){
console.log(1)
demo.remove()
}) //删掉原有位置
注意:opacity从0变为1,只是看不见,但它原有的位置还在,我们需要使用上述JS代码删除其原有位置
- visibility:hidden->visible 将上述代码opacity部分替换为visibility即可
animation总结
完整指南->animation-MDN
缩写语法:
animation:时长 过渡方式 延迟 次数 方向 填充方式 是否暂停 动画名;
以上所有属性都有对应的单独属性
如何让动画停在最后一帧?
在animation加上一个forwards即可
@keyframes完整语法
第一种
@keyframes xxx {
from {transform:none;}
to {transform:translateX(100px);}
}
第二种
@keyframes xxx {
0% {transform:none;}
50%{transform:translateX(50px)}
100% {transform:translateX(100px);}
}
中间点
有了以上总结,我们可以实现一个稍微复杂的例子:
使用两次transform
#demo.b{
transform: translateX(200px);
}
#demo.c{
transform: translateX(200px) translateY(100px);
}
button.onclick = ()=>{
demo.classList.add('b')
setTimeout(()=>{
demo.classList.remove('b')
demo.classList.add('c')
},1000)
}
我们通过JS和按钮,可以实现先将方框移动到b的位置,在以b的基础移动到c的位置
使用animation
animation中间点示例
使用animation,不仅可以实现中间点,还可以实现往复,暂停,继续的功能:
#demo.start{
animation: xxx 1.5s infinite alternate;
}
@keyframes xxx {
0% {
transform: none;/* 第1帧位置 */
}
66.66%{
transform: translateX(200px);/* 第2帧位置 */
}
100%{
transform: translateX(200px) translateY(100px);/* 第3帧位置 */
}
}
infinite表示动画无限次播放 alternate让动画往复运动
button.onclick = ()=>{
demo.classList.add('start')
}
paused.onclick = ()=>{
demo.style.animationPlayState = 'paused'
}
running.onclick = ()=>{
demo.style.animationPlayState = 'running'
}
我们通过JS运用animation的animationPlayState属性值,即可实现动画的暂停与继续