本文篇幅比较长,涉及到的知识点也比较多,如3d,动画性能,动画js事件等,参考文献及demo展示也比较多,所以建议pc阅读效果更佳。
到现在来说css3动画也不是什么新技术,既然是要搞定它,好歹我们也得先看下别人做的一些东西吧,所以在此先向各位推荐几个比较好用的动画库:
看完上面那些动画库,心痒就不如行动了。
说起css3动画,有一个属性我们绝对避不开了,那就是transform
这个属性,而如果要搞点高级的3d特效,那还有两个比较容易混淆的东西perspective
和preserve-3d
,下面我们简单说明关于这些的一些疑难点。
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
属性指定了观察者与z=0平面的距离,使具有三维位置变换的元素产生透视效果。z大于0的时候三维元素比正常大,而z小于0时则比正常小,大小程度由该属性的值决定。
该属性不可继承。由于不可继承,所以只对一级子元素管用。
原理如下图(d表示perspective的值,Z表示translateZ的值):
当然观察者站在哪里也会影响效果,如下图改变perspective-origin
:
transform-style
属性指定了,该元素的子元素是(看起来)位于三维空间内,还是在该元素所在的平面内被扁平化。如值为preserve-3d
则创建一个3D渲染上下文,其直接子元素有一个共同的三维坐标系。
同样该属性不可继承,只应用于直接子元素。
前面的概念性解释太过笼统,好像都跟3d有关, 但是区别呢?区别呢?
简单来说,perspective为其直接的具有三维变换的子元素产生一个透视效果;而preserve-3d
则为其直接的子元素提供一个3d渲染空间。
具体效果,请查看 perspective和transform-style 的 demo演示
相信经过上的一些列demo演示,对这两个总有所区分了解了。如果你还不太明白的话,那就记住这个最佳实践吧。外面一层提供透视,然后再包裹一层提供3d渲染空间。
.perspective
.preserve-3d
.child*n
上面说了那么多疑难点,算是为我们的动画铺平了一些道路,现在正式进入我们的动画。
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: [[@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动画帧数计算器
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
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,从这张图上就可以清楚的看出为什么重排比重绘更耗性能,因为重排发在在重绘的前一步,它必然会导致下一步的重绘。
- | - |
---|---|
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 |
- | - |
---|---|
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 |
不是所有的属性动画消耗的性能都一样,消耗最低的是transform和opacity两个属性,其次是paint相关属性。各属性trigger参考见css triggers
这也就是为什么我们推荐使用transform的translate带替代margin或position定位的top/right/bottom/left值了,而用transform的scaleX/Y替代width/height的运动。
开启3d加速
transform: translate3d(0, 0, 0);
transform: translateZ(0);
提前告知浏览器我这里将会进行一些变动,请分配资源。
1、四大核心建议:
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';
}