搞定这些疑难杂症,向css3动画说yes
本文篇幅比较长,涉及到的知识点也比较多,如3d,动画性能,动画js事件等,参考文献及demo展示也比较多,所以建议pc阅读效果更佳。
动画库
到现在来说css3动画也不是什么新技术,既然是要搞定它,好歹我们也得先看下别人做的一些东西吧,所以在此先向各位推荐几个比较好用的动画库:
关于css3动画不得不说的几个属性
看完上面那些动画库,心痒就不如行动了。
说起css3动画,有一个属性我们绝对避不开了,那就是transform
这个属性,而如果要搞点高级的3d特效,那还有两个比较容易混淆的东西perspective
和preserve-3d
,下面我们简单说明关于这些的一些疑难点。
transform
1、任何非none值的transform会导致创建一个堆栈上下文和包含块。
所以如果父级元素设置了transform属性,position:relative/absolute/fixed
会基于此定位,详细请参考:transformed element creates a containing block for all its positioned descendants
demo如下:
.demo{
width: 200px;
height: 200px;
transform: translate(100px,100px);
background: #f00;
}
.child{
width: 100px;
height: 100px;
background: #000;
position: fixed;
top: 50px;
left: 20px;
}
2、transform属性值覆盖问题
我们知道transform可以有四个不同的变换,分别为scale
、translate
、skew
、rotate
。现在的问题是当有两个transform设置不同变换时,权重大的覆盖权重小的。
.demo{
transform: scale(2);
}
.demo{
// 第一条scale将会被覆盖,失效
transform: translateX(50px);
}
// 如果要包含第一条scale
.demo{
transform: scale(2) translateX(50px);
}
这个问题在平时使用中还好,但是在动画中那就相当麻烦了,因为你必须还得去拷贝之前设置的值。所以水平垂直居中的弹窗如果用了translate水平定位,然后再使用transform动画,那就毁了。
注: 听说谷歌正在拆分这四个值,这样就简单多了。
3、transform几个值的先后问题
对不起,那四个值的真的不是随便写的,它是有先后的。第一会改变中心点,第二会改变坐标系,所以请遵循先后顺序。以立方体为例:
<section class="container">
<div id="cube">
<figure class="front">1</figure>
<figure class="back">2</figure>
<figure class="right">3</figure>
<figure class="left">4</figure>
<figure class="top">5</figure>
<figure class="bottom">6</figure>
</div>
</section>
.front { transform: rotateY( 0deg ) translateZ( 100px ); }
.back { transform: rotateX( 180deg ) translateZ( 100px ); }
.right { transform: rotateY( 90deg ) translateZ( 100px ); }
.left { transform: rotateY( -90deg ) translateZ( 100px ); }
.top { transform: rotateX( 90deg ) translateZ( 100px ); }
.bottom { transform: rotateX( -90deg ) translateZ( 100px ); }
可以看到在设置六个面的时候,我们是先旋转(改变了坐标系),然后通过translateZ定位即可。这就跟我们军训的时候站军姿一样,每转动一次,我们的坐标随即改变,如向左转,转完之后就不再是左而是我们的正前方了。
perspective
perspective
属性指定了观察者与z=0平面的距离,使具有三维位置变换的元素产生透视效果。z大于0的时候三维元素比正常大,而z小于0时则比正常小,大小程度由该属性的值决定。
该属性不可继承。由于不可继承,所以只对一级子元素管用。
原理如下图(d表示perspective的值,Z表示translateZ的值):
当然观察者站在哪里也会影响效果,如下图改变perspective-origin
:
transform-style
transform-style
属性指定了,该元素的子元素是(看起来)位于三维空间内,还是在该元素所在的平面内被扁平化。如值为preserve-3d
则创建一个3D渲染上下文,其直接子元素有一个共同的三维坐标系。
同样该属性不可继承,只应用于直接子元素。
perspective 与 preserve-3d 差别
前面的概念性解释太过笼统,好像都跟3d有关, 但是区别呢?区别呢?
简单来说,perspective为其直接的具有三维变换的子元素产生一个透视效果;而preserve-3d
则为其直接的子元素提供一个3d渲染空间。
具体效果,请查看 perspective和transform-style 的 demo演示
最佳实践
相信经过上的一些列demo演示,对这两个总有所区分了解了。如果你还不太明白的话,那就记住这个最佳实践吧。外面一层提供透视,然后再包裹一层提供3d渲染空间。
.perspective
.preserve-3d
.child*n
动画
上面说了那么多疑难点,算是为我们的动画铺平了一些道路,现在正式进入我们的动画。
transition
transition: [property] [duration] [timing-function] [delay];
1、不可自动触发,可以通过改变class,改变状态(:hover, :active, :checked等)触发
2、display的none与其他值的切换不行, 通过delay设置也不行,除非通过回调函数或setTimeout先切换display,再设置动画样式改变。
.demo{
display: none;
width: 100px;
transition: width .3s; // 在none和其他值之间切换,动画无效
}
.demo:hover{
display: block;
width: 200px;
}
3、可设置delay为负值,表示动画已经运行到了该时间,前面的动画效果忽略,见demo
4、可对自己或子元素进行动画动画: combined transitions 1
5、或对同级下面的元素及其子元素进行动画控制: combined transitions 2
6、可在状态内添加transition,覆盖默认的transition,见demo
// 鼠标滑过会采用hover的transition即1s(覆盖了默认的transition),滑出会采用默认的transition即5s
.demo{
width: 200px;
height: 200px;
background: #f00;
transition: all 5s;
}
.demo:hover{
transition: all 1s;
background: #000;
}
animation
语法:
animation: [[@keyframes](/user/keyframes) name] [duration] [timing-function] [delay]
[iteration-count] [direction] [fill-mode] [play-state];
animation: slidein 3s ease-in 1s 2 reverse both paused;
1、可自动触发,也可以通过状态或增加class触发
2、安卓低端机不支持伪元素(::before
和::after
)动画
3、animation-fill-mode 可设置动画结束及开始的状态。如为backwards,则元素默认应用第一关键帧的样式,忽略delay,可通过一开始就暂停观察(animation-play-state: paused;);如为forwards,则在动画结束后,元素将应用动画结束后的属性值;如果animation-fill-mode的值为both,则动画会遵循backwards和forwards的规则。也就是说,它会设置开始和结束的状态。
4、animation-timing-function默认应用在每个关键帧之间的变化,而不是开始到结束整个流程。所以@keyframes 中的每个关键帧可以重新定义animation-timing-function
[@keyframes](/user/keyframes) square {
50% {
top: 200px;
left: 400px;
animation-timing-function: ease-in-out;
}
}
5、可以用于none到block的动画切换
查看demo,主要代码如下:
// child一开始为none,demo hover的时候使用动画显示
.demo .child{
display: none;
}
.demo:hover .child{
display: block;
animation: showChild .3s both;
}
[@keyframes](/user/keyframes) showChild {
from{
opacity: 0;
}
}
6、对于关键帧的安排是门技术活,所以这里推荐使用CSS3动画帧数计算器
动画js事件
1、transition
动画只有一个transitionend
事件,而webkit现在既支持webkitTransitionEnd,也支持标准的transitionend事件,所以只能绑定一个,不然会触发两次事件,见demo
2、如有多个属性参与动画,就会出现多个transitionend
事件(这个事件标准还是有不少bug的),所以请使用jquery的one事件,或者绑定事件调用函数中随即取消绑定事件
3、Detect the End of CSS Animations and Transitions with JavaScript
function whichTransitionEvent(){
var t,
el = document.createElement("div");
var transitions = {
"transition" : "transitionend",
"MozTransition" : "transitionend",
"WebkitTransition": "webkitTransitionEnd"
}
for (t in transitions){
if (el.style[t] !== undefined){
return transitions[t];
}
}
}
var transitionEvent = whichTransitionEvent();
// jquery 调用
$(".button").click(function(){
$(this).addClass("animate");
$(this).one(transitionEvent,
function(event) {
// Do something when the transition ends
});
});
// 原生js调用
var button = document.querySelector(".button"),
transitionEvent = whichTransitionEvent();
button.addEventListener("click", function() {
if (button.classList) {
button.classList.add("animate");
} else {
button.className += " " + "animate";
}
button.addEventListener(transitionEvent, customFunction);
});
function customFunction(event) {
button.removeEventListener(transitionEvent, customFunction);
// Do something when the transition ends
}
4、animation
动画有三个js事件,分别为animationstart
、animationiteration
、animationend
不支持的动画属性切换
- background-image
- float
- height/width/top/right/bottom/left 等auto值向具体值的变换
- display 在none和其他值之间切换
- position 在static和absolute之间切换
timing-function
1、cubic-bezier(x1, y1, x2, y2)
,标准四个数字的取值范围为[0,1],但是各个浏览器的范围有所不同,chrome浏览器就允许弹性效果,如cubic-bezier(.62,-0.32,.29,1.46)
2、 steps()
步骤动画,规定几步完成,每步完成时间(t)为总时间(T)/步骤(n),分为start和end两种。start表示每步完成时间的开始就已经运动了,end表示每步完成时间结束才运动。
demo见Twitter's "fave" animation
性能
各个属性的性能消耗
既然有这么多属性都可以运动,那么是不是所有的属性运动都一样消耗性能呢?下面我们大概看下css的渲染过程:
如上图:css最终表现大概分成主要的四步: recalculate style(查找计算样式),layout(排布),paint(绘制),composite layers(组合层级)
而我们经常说的重排和重绘,就是发生在重新layout和重新paint,从这张图上就可以清楚的看出为什么重排比重绘更耗性能,因为重排发在在重绘的前一步,它必然会导致下一步的重绘。
layout 相关属性
- |
- |
---|---|
width |
height |
padding |
margin |
display |
border-width |
border |
top |
position |
font-size |
float |
text-align |
overflow-y |
font-weight |
overflow |
left |
font-family |
line-height |
vertical-align |
right |
clear |
white-space |
bottom |
min-height |
paint 相关属性
- |
- |
---|---|
color |
border-style |
visibility |
background |
text-decoration |
background-image |
background-position |
background-repeat |
outline-color |
outline |
outline-style |
border-radius |
outline-width |
box-shadow |
background-size |
composite layers 相关属性
- transform
- opacity
总结
不是所有的属性动画消耗的性能都一样,消耗最低的是transform和opacity两个属性,其次是paint相关属性。各属性trigger参考见css triggers
这也就是为什么我们推荐使用transform的translate带替代margin或position定位的top/right/bottom/left值了,而用transform的scaleX/Y替代width/height的运动。
3D
开启3d加速
transform: translate3d(0, 0, 0);
transform: translateZ(0);
will-change
提前告知浏览器我这里将会进行一些变动,请分配资源。
1、四大核心建议:
- 不要将 will-change 应用到太多元素上:浏览器已经尽力尝试去优化一切可以优化的东西了。有一些更强力的优化,如果与 will-change 结合在一起的话,有可能会消耗很多机器资源,如果过度使用的话,可能导致页面响应缓慢或者消耗非常多的资源。
- 有节制地使用:通常,当元素恢复到初始状态时,浏览器会丢弃掉之前做的优化工作。但是如果直接在样式表中显式声明了 will-change 属性,则表示目标元素可能会经常变化,浏览器会将优化工作保存得比之前更久。所以最佳实践是当元素变化之前和之后通过脚本来切换 will-change 的值。
- 不要过早应用 will-change 优化:如果你的页面在性能方面没什么问题,则不要添加 will-change 属性来榨取一丁点的速度。 will-change 的设计初衷是作为最后的优化手段,用来尝试解决现有的性能问题。它不应该被用来预防性能问题。过度使用 will-change 会导致大量的内存占用,并会导致更复杂的渲染过程,因为浏览器会试图准备可能存在的变化过程。这会导致更严重的性能问题。
- 给它足够的工作时间:这个属性是用来让页面开发者告知浏览器哪些属性可能会变化的。然后浏览器可以选择在变化发生前提前去做一些优化工作。所以给浏览器一点时间去真正做这些优化工作是非常重要的。使用时需要尝试去找到一些方法提前一定时间获知元素可能发生的变化,然后为它加上 will-change 属性。
2、错误用法,直接应用在hover,没有提前告知浏览器分配资源
.element:hover {
will-change: transform;
transition: transform 2s;
transform: rotate(30deg) scale(1.5);
}
3、正确应用,在进入父元素的时候就告诉浏览器分配资源
.element {
transition: opacity .3s linear;
}
/* declare changes on the element when the mouse enters / hovers its ancestor */
.ancestor:hover .element {
will-change: opacity;
}
/* apply change when element is hovered */
.element:hover {
opacity: .5;
}
4、在应用变化之后,取消will-change的资源分配
var el = document.getElementById('demo');
el.addEventListener('animationEnd', removeHint);
function removeHint() {
this.style.willChange = 'auto';
}
其他动画
- How SVG Line Animation Works
- A Guide to SVG Animations (SMIL)(下面的英文版)
- 超级强大的SVG SMIL animation动画详解 (上面的中文版)
- gsap
- Velocity.js
参考资料
- Thank God We Have A Specification!
- perspective-origin
- Intro to CSS 3D transforms
- learning css3 transform
- Understanding CSS Timing Functions
- Animation Advice from a CSS Master
- Everything You Need to Know About the CSS will-change Property
- High Performance Animations
- Golang中container/list包中的坑
- 关于Golang语言数组索引的有趣现象
- Golang不定参数
- Go并发编程基础(译)
- go-concurrent-programming.md
- Go语言并发模型:以并行处理MD5为例
- golang 使用json 包 实现序列化
- 【远古文章】用 Go 语言来看 Android! 出发, Android, 出发!
- Leaf 游戏服务器框架简介
- MongoDB 存储过程的使用以及性能调优方案
- [go语言]利用缓冲信道来实现网游帐号验证消息的分发和等待
- 【Golang语言社区--投稿专区】简单,好玩,有趣的命令行版12306(golang)
- 网游内存数据库的设计(1)
- 网游内存数据库的设计(2)
- HTML 教程
- HTML 简介
- html div 标签介绍
- html span 标签介绍
- html a 超链接标签
- HTML Br换行标签介绍
- HTML P段落标签介绍
- HTML br与p标签区别
- Html H 标题标签
- html px em pt长度单位
- HTML form 标签
- HTML radio 单选框
- HTML B 加粗标签
- HTML strong加粗粗体标签
- HTML em 强调标签
- HTML i 斜体标签
- HTML u下划线标签
- HTML s 删除线标签
- Html img 图片标签
- Html上标注sup与下标注sub标签
- HTML nobr 禁止换行标签
- HTML hr 水平线标签
- HTML label 标签
- HTML input 标签
- HTML textarea 标签
- HTML select下拉列表标签
- HTML checkbox 多选框
- HTML font color 标签
- HTML iframe 框架标签
- HTML Table 表格
- HTML dl dt dd 标签
- HTML ol li有序列表标签
- HTML ul li 无序列表标签
- HTML 注释
- CSS 教程
- CSS 简介
- CSS 语法
- CSS Id 和 Class选择器
- CSS 样式的创建
- CSS background 背景介绍
- CSS 文本样式
- CSS font 字体
- CSS A 链接
- CSS ul ol列表样式
- CSS TABLE 样式
- CSS 框模型
- CSS border 边框
- CSS Outlines 轮廓
- CSS 外边距 Margin
- CSS Padding 内边距
- CSS 分组和嵌套选择器
- CSS 尺寸 (Dimension)
- CSS Display 属性
- CSS Position 定位
- CSS Float 浮动
- CSS 水平对齐(Horizontal Align)
- CSS 组合选择符
- CSS 伪类
- CSS 伪元素
- CSS 导航栏
- CSS 下拉菜单
- CSS 图片廊
- CSS 图像透明/不透明
- CSS sprite 图像拼合技术
- CSS 媒体类型
- CSS 属性选择器
- CSS 实例
- Three.js教程(7):材质
- Keep APP技术研究
- Canvas系列(15):实战-小球拖拽
- Canvas系列(16):实战-小球与斜面碰撞
- Three.js教程(3):场景
- Three.js教程(4):相机
- 使用GithubActions自动部署应用到自己的服务器(ECS)
- Nuxt项目给script标签添加crossorigin属性
- Canvas系列(12):动画高级
- Canvas系列(13):实战--星空连线图
- VSCode支持Vue自动保存格式化的配置
- 构造方法或new返回该对象
- 使用Node在浏览器打开某个网页
- 快应用初探--把个人博客封装成快应用
- 10分钟开发一个npm全局依赖包(上)