004 - CSS3动画和JS动画基础篇

272 阅读12分钟

1. CSS3动画

1.1 transition过渡

  • CSS过渡本质上是对用户界面效果的一种增强手段,是指在一段时间内将Css样式的初始值变成另外一个值。更确切的说,这种状态的改变是对元素某种操作的响应,通常是由用户交互触发的,但是也可以使用JS脚本进行触发。
  • css属性值的变化是在瞬间(毫秒级)完成的,多数情况下整个过程不会超过16毫秒(变换背景图由于需要解码并重新绘制页面可能需要大于16毫秒,而这种情况显然已经不属于过渡了)。
  • 另外需要说明的是过渡必须需要页面将当前元素渲染结束后才会产生效果。也就说由于元素的渲染比元素样式变换更加耗时,则当元素原本是隐藏的,原本目标是将元素重新渲染为显示时,进而立刻进行修改元素样式,但transition是在元素渲染结束后才会产生效果的,故此时不会出现transition动画效果。如果想此时transition也出现动画效果,那需要先更改浏览器渲染机制了。
  • 过渡是写在某种操作之前style中的,当触发了这个特定操作,元素样式的变换过程受过渡属性设置限制,但最终结果并与之前的直接样式变化没有发生改变。默认是没有过渡效果的,若使用了过渡,后又不需要,可以禁止。

1.1.1 过渡的属性

过渡有transition-propertytransition-durationtransition-timing-functiontransition-delay四个属性,当然过渡也提供了transition简写。

  • transition-property 用于指定想使用过渡效果的css属性,这里分为none,all和其他css属性。none是禁止所有属性使用过渡效果(默认),all是指对当前元素的所有css属性添加过渡效果,其他css属性是特指css属性,另外可以是变换transform。 需要注意的是,并不是所有的css属性都有机会享有这份殊荣的,这里暂时不具体详细列出属性列表,支出动画的属性列表本身也在不断的增加新成员,但是还有规矩可寻的。判断属性是否支持动画的关键是确定其取值是够能够在两个数据点之间插入一个数据点,简单来说就是如果属性值只能是关键字,而不能是计算为某种数值时,是不能使用动画效果的,相反能使用。关于属性值是如何实现在两个数据点之间插入一个数据点的详细叙述,有机会再写。 transiton-property:attr1,attr2,...,attrn;
  • transition-duration 用于指定从一个状态过渡到另一个状态需要经历多久,即过渡持续时间长度,单位可以是秒或毫秒,默认0S。 若两个状态生命的transition-duration不一致,在保证声明正确的情况下以目标状态声明为准。 值必须是大于等于零,过渡效果不起作用,即不触发transitionend事件。 transition-duration若只有一个值,是对声明过渡效果的所有属性添加同一个过渡时间,若添加多个值且与应用过渡效果的css属性个数相同时,则一一对应,否则根据个浏览器指定的规则处理,时间值多是,多的不起作用,时间值少时重复循环使用。 transition-duration:0s,100ms,...,200ms;
  • transition-timing-function 用于指定过渡效果的速度和加速度问题,控制过渡的步调,初始值ease。 可以取的值有ease、linear、ease-in、ease-out、ease-in-out、step-start、step-end、step(步进的次数n,start),当然也可以直接使用贝塞尔曲线函数cubic-bezier(x1,y1,x2,y2),四个参数描述了贝塞尔曲线的横轴和纵轴。 transition-timing-function:ease;
  • transition-delay 用于指定对过渡效果的延迟执行时间长度,默认为0s,单位为s或ms,数量和效果类似transition-duration。 transition-delay:0s,...,0s;
  • transition事件 无论何种css属性过渡效果都会最终触发transitionend事件,需要注意的是有时看起来单个属性的声明,却会触发多个transitionend事件,这是因为简写属性中的每个支持动画的属性有各自的transitionend事件,例如添加了border-radius过渡效果,却调用了四次transitionend事件,这是因为border-radius有四个单独属性border-bottom-left-radius/border-bottom-right-radius/border-top-left-radius/border-top-right-radius四个简写形式。 另外需要注意的是这个事件不能通过el.ontransitonend = (e)=>{};方式添加transitionend事件,而是需要使用el.addEventListener('transitioned',(e) => {});添加这一事件。 transitionend事件有三个与该事件有关的属性:结束过渡的css属性名称的e.propertyName、过渡持续时间(单位秒)的e.elapsedTime以及若应用效果的伪元素(普通DOM节点返回空字符串)的e.pseudoElement
  • transition简写 transition:attr1 0s 0s ease,...,attr2 0s 0s linear;

1.1.2 反向过渡:退回起点

当触发过渡事件结束后,各属性通过相同的过渡回到默认属性,延迟相同,但是transition-timing-function是相反的。

1.2 animation动画

如果想为元素添加animation动画,必须需要定义一个关键帧。

1.2.1 定义动画帧

@keyframes name{
	from{/*动画开始时的元素样式状态*/}
	to{}
	...
	to{/*最后一个表示动画结束时的元素样式状态*/}
}
// 当然,也可以使用百分比实现过程的精细化定义,
// 当没有定义from或0%的时候,默认从元素计算后属性开始
// 当没有定义to或100%的时候,默认动画结束时状态为元素计算后属性
// 当动画结束后,元素状态将会回到行间样式,即计算后属性,也就是元素渲染后的样式

1.2.2 将动画应用到元素上

一种是单独声明各个属性,一种是使用animation简写属性一次性声明全部属性,当然这两种方式可以混着使用。

  • animation-name 指定动画帧的名字,默认值为none,即没有任何动画效果 animation-name:name1,...,namen; -animation-duration 指定动画执行时间,默认0s,单位为秒或毫秒。 animation-duration:0s,..,0s;
  • animation-iteration-count 指定动画执行的次数,默认1。 animation-iteration-count:0,..,infinite;
  • animation-direction 指定动画是从0%关键帧向100%关键帧播放还是从100%关键帧向0%关键帧播放。也可以定义所有多次执行动画都按照相同的方向执行,或者是每隔一次循环变一次方向。默认normal,可填写reverse、alternate、alternate-reverse。 animation-direction:normal,...,normal;
  • animation-delay 动画延迟多长时间执行。默认是0s,单位是s或ms。 animation-delay:0s,...,0s;
  • 动画事件 与animation动画相关的事件有animationstart、animationiteration、animationend三个,每个事件都有animationName、elapsedTime和pseudoElement三个只读属性。
// 在动画开始播放是触发执行,如果动画有延迟,就在延迟之后触发执行,延迟为负值,立即执行。
el.addEventListener('animationstart',() =>{});
// 在动画执行完成后触发执行,若无限次执行且延迟大于零,此事件永远不触发。
el.addEventListener('animationend',() => {});
// 在动画两次迭代之间触发执行
el.addEventListener('animationiteration',() => {});
  • animation-timing-function 与transition-timing-function类似。
  • animation-play-state 如果想暂停或开始动画,默认是running。 animation-play-state:running|paused;
  • animation-fill-mode 定义动画播放结束后是否 应用原来的属性值。默认是none。 animation-fill-mode:none,forward,backward,both;
  • animation简写 animation:name1 duration timing-function delay iteration-count direction fill-mode play-state,...; 注意除了duration和delay之外顺序一定外,其他属性不必完全按照顺序;另外,不必把所有属性都罗列出来。

1.2.3 其他

animation还有特指度、!important、动画顺序、display:none对动画影响、UI线程等,有机会再做一个专题分享。

1.3 transform变换

元素自从创立以来就是矩形的,且只能在横轴和纵轴位置,而transform就是利用一些技巧将元素看起来 是倾斜的,但底层坐标并没有发生改变。 坐标系常用的就是二维坐标和三维坐标,transform也就有2D和3D两种版本。

1.3.1 基本使用

// 移动类变换
transform:translate(x,y); //2d
transform:translate3d(x,y,z); // 3d
transform:translateX() translateY() translateZ(); // 分方向移动

// 缩放类变换
transform:scale(x,y); // 2d
transform:scale3d(x,y,z); // 3d
transform:scaleX() scaleY() scaleZ(); // 分轴放缩

// 旋转类变形
transform:rotate(deg); // 相当于rotateZ()
transform:rotate3d(x,y,z, deg); // 不等价三个下边的三个合在一起,x,y,z均可取0或1
transform:rotateX() rotateY() rotateZ(); // 分轴旋转

// 斜切类变形
transform:skew(x,y); // 与下边两个合起来不等价
transform:skewX() skewY();

// 视域函数
transform:persitive(num);// 与后边讲的perspective属性类似,不能是负值,否则会被忽略。

// 变形默认是按照元素的中心原点进行变换的,但可更改
tranform-origin:x y; // 初始值为50% 50%。可以写计算值,百分数或类似top的单词

// 增强3D效果
transform-style:flat|perserve-3d;// 默认flat,在元素进行3D变换时,是否保留子元素的3d变换。

// 景深
perspective:0; // 默认值是none,
perspectiveorigin:x y ; // 默认是50% 50%

// 处理背面
backface-visibility:hidden;// 隐藏和父级角度相对的背面,默认visible

1.3.2 变换函数原理

变换函数是W3C为方便使用封装的函数,2d操作底层是3x3的矩阵,3d操作的是4x4矩阵,js中被不能获取到变换的过程值,能获取到的只是matrix(a,b,c,e,f),且变换具有不可逆性,因为多种类型变换更改matrix参数,举证无法逆推。

在定义变换的时候,也可以直接使用matrix函数,通过参数的变换实现平移、旋转、斜切缩放等变换,下边只介绍2d相关操作。

  • 位移:横向位移只操作e参数,纵向位移只操作f参数。
  • 缩放:横向只操作ace参数,纵向操作只操作bdf参数。
  • 斜切:横向只操作c参数,纵向只操作b位置,数值为弧度rad。
  • 旋转:只操作abcd位置。

1.4 联系与区别

transform元素变化是将元素进行空间以及状态进行变换,transition过渡和animation动画是定义元素实现空间变换或状态变换过程中的形式,其中transform可是过渡或状态的一个css属性。 transition只能操作元素样式变化的开始和结束时候的状态,而无法完成在过程的精准变化。

2. JS动画

2.1 JS动画原理

JS本身是无法单独完成动画的,而是通过借助类似间歇性定时器改变元素的样式属性值,进而实现元素的动态。人眼每秒能够识别的24帧,也就是每秒渲染24次,超多24帧在人眼中就会形成连续的动画效果。

注意尽量不要直接操作元素样式值,而是通过transform完成操作,这样性能和效率更高一些。

尽管使用间歇性定时器能够实现动画,但是有失帧的情况,所以实现的动画性能和视觉都不是特别好。

2.2 JS动画帧

timer=requestAnimationFarme(function),屏幕每秒60帧,当屏幕每次渲染的时候,便会执行一次动画帧函数,但是如果需要持续动画效果,需要使用递归。

cancelAnimationFrame(timer);

var timer = 0;
    run.onclick = function(){
        cancelAnimationFrame(timer);
        timer = requestAnimationFrame(move);
        function move(){
            x += speed;
            box.style.transform = 'translateX('+x+'px)';
            timer = requestAnimationFrame(move);
        }
    };

2.3 Tween算法基础

Tween算法是使用封装函数的方式实现了获取根据一系列参数计算,得到当前时刻元素应当的属性值。

// t动画已经执行了几次,b动画开始时值,c动画初始值和目标值之间的差值,d动画执行的次数
var val = Tween['动画方式'](t,b,c,d); // 返回执行到第t次,动画的属性值

使用动画帧和Tween实现动画

(function(){
    var run = document.querySelector("#run");
    var stop = document.querySelector("#stop");
    var box = document.querySelector("#box");
    var x = 0;
    var speed = 5;
    var timer = 0;
    var t = 0; //动画执行到第几次 (动画已经消耗的时间)
    var b = 100; // 动画开始前的初始值
    var c = 500; // 动画初始值 和 目标点之间的差值
    var d = 60; // 动画执行总次数, 动画执行时间
    run.onclick = function(){
        cancelAnimationFrame(timer);
        timer = requestAnimationFrame(move);
        function move(){
            t++;
            var val = Tween["elasticOut"](t,b,c,d);// val 动画执行到第 t 次时,动画应该走到哪个位置
            // console.log(val,t);
            //box.style.transform = 'translateX('+val+'px)';
            box.style.width = val + 'px';
            console.log(t);
            if(t < d){
                timer = requestAnimationFrame(move);
            }
        }
    };
    stop.onclick = function(){
        cancelAnimationFrame(timer);
    };

})();   

2.4 mTween动画框架

mTween是基于Tween算法的,这里均不需要加单位。

css函数主要用来配合设置动画使用,两个参数是获取属性值,三个参数是设置属性值。

css(el,attr[,val]);

即使是非数值也能更改样式,只是无法用来实现动画效果。

由于transform变换无法直接获取计算后的样式,所以需要先设置css(el,‘translateX’,0);,然后才能在使用mTween进行设置transform的变换样式。

mTween({
    el:ele,
    attr:{
        attr1:value
    },
    duration:{
      multiple:num, // 根据动画样式中的最大差值计算出num倍的时间
        max:num, // 最大时间
        min:num // 最小时间
    },
    fx:'动画执行的形式,类似匀速',
    cb:function(){},
    moving:function(){} 
});
mTween.stop(el); // 停止某个元素的动画

// 如果需要动画执行完毕后再执行一次,需要在cb函数中将调用框架执行在执行一次,也可封一个函数

3. CSS动画与JS动画联系

css动画的执行效率比js效率更高。

4. Next

ES6基础、异步处理、DOM、BOM、Event事件。