Aitter's Blog

动画系列之CSS3动画基础

CSS3添加了几个动画效果的属性,通过设置这些属性,可以做出一些简单的动画效果而不需要再去借助JavaScript。
CSS3动画的属性主要分为三类:transform、transition以及animation。

基本内容

  • 动画的基本概念
    • 补间动画
    • 逐帧动画
  • Transform
    • 2D变换
    • 3D变换
    • Tranform过渡控制
  • 可动画的属性
  • Transition
    • 基本属性
    • 为不同属性指定过渡时长
    • 变换多个动画属性
    • 时长的简写
    • 强制开启GPU硬件加速
    • 消除Tansition动画闪屏和文字变虚
  • Animation动画
    • 基本属性
    • 关键帧序列
    • 运动方向
    • 重复次数
    • 播放与暂停
    • 起始状态控制
  • Transition 与 Animation 动画的触发
  • 监听动画结束的事件
    • 确保 transitioned 事件触发
    • 强制重绘触发动画
  • 会引起界面重绘的属性
  • Transition动画应用示例
  • Animation动画应用示例

动画的基本概念

补间动画

过滤动画 具有连贯性的动画,从一个关键帧到另一个关键帧的过滤,Transition 动画属于此类动画

逐帧动画

使用steps过渡的动画,在时间帧上逐帧绘制帧内容,由于是一帧一帧的画,所以逐帧动画具有非常大的灵活性,几乎可以表现任何想表现的内容。与电影相似,适用于表现细腻的动画,如人物动作,3D动画等。

缺点是:制作难度大,需要大量素材,导致最终文件的体积很大。

Transform过渡

位移Translate、旋转Rotate、倾斜Skew、缩放Scale 及矩阵变形Matrix

2D变换

  • 位移:translate(12px, 50%); 单位可指定 px % em rem
  • 缩放:scale(x, y)
  • 斜切:skew(0deg, 0deg)
  • 旋转:rotate(0deg)
  • 矩阵:matrix(0, 0, 0, 0, 0, 0)

3D变换

  • 位移:translateX() / translateY() / translateZ() /translate3d()
  • 缩放:scaleX() / scaleZ() / scaleY() / scale3d(2.5, 1.2, 0.3);
  • 斜切:skewX() / skewY()
  • 旋转:rotate3d(1, 2.0, 3.0, 10deg);
  • 矩阵:matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);

触发3D变形的属性

.parent {
perspective: 300px; 透视点
perspective-origin: center center; 观察者(消失点)的位置(默认为中心点)
backface-visibility: visible; 3D元素的背面是否可见(默认是visible)
}

MDN backface-visibility perspective perspective-origin

在子元素上触发3D空间

transform: perspective(600);

一本书平放在面前,看着书感受一下透视感,perspective属性值就是眼睛和书之间的距离,距离越远,数值越大,透视感越小;距离越近,数值越小,透视感越强。

简单易懂的教程示例:CSS3 3D教程

Transform 过渡控制

transform-origin / transform-style

定义过渡原点

transform-origin ( transform-origin-x 、transform-origin-y、transform-origin-z )

默认为元素的中心原点

transform-origin: left top; 左上角
transform-origin: right bottom; 右下角
transform-origin: center bottom; 底部中心
transform-origin: 50% 50%; 中心
transform-origin: 10px 2em;
transform-origin: right 20%;
transform-origin: right 20% 30px;
transform-origin: bottom top 2px;

定义子元素的是扁平化还是三维展示

transform-style: flat | preserve-3d

transform-style: preserve-3d; 用于触发子元素三维展示

可动画的属性

border / color / backgorund / heightwidth / left、right、top、bottom / padding / margin / opacity / font-size / box-shadow / text-shadow / transform

这里列举了 一些常见的可动画的属性,详情请查看 MDN 可动画的属性列表

Transition

定义过渡动画效果

基本属性

  • transition-property 需要进行过渡属性(只有指定的属性才进行过滤动画) 或者 全部可动画的属性 all
  • transition-delay 延迟时间
  • transition-duration 进行过渡的时间
  • transition-timing-function 指定动画函数

简写
transition: <property> <duration> <timing-function> <delay>;

为不同属性指定过渡时长

transition-property: opacity, left, top, height;
transition-duration: 3s, 5s, 1s, 2s;

时长的简写

如果有部分属性没有指定时长,则重复前面的时长

transition-property: opacity, left, top, height;
transition-duration: 3s, 5s;
等价于
transition-duration: 3s, 5s, 3s, 5s;

! 为了过渡的流畅性,一般需要指定动画的属性,可以有以下几种指定方式

transition: margin-left 4s;
/* property name | duration | delay */
transition: margin-left 4s 1s;
/* property name | duration | timing function | delay */
transition: margin-left 4s ease-in-out 1s;
/* Apply to 2 properties */
transition: margin-left 4s, color 1s;
/* duration | delay | property name */
transition: 4s 1s color;

变换多个动画属性

.box {
width: 100px; height: 100px; background-color: #0000FF;
transition: width 2s, height 2s, background-color 2s, transform 2s;
}
.box:hover {
width: 200px; height: 200px; background-color: #FFCCCC; transform: rotate(180deg);
}

强制开启GPU硬件加速

.box { transition: tanslate3d(0,0,0)或 tansition: translateZ(0) }

消除Tansition动画闪屏和文字变虚

.box{ transform-style: preserve-3d; backface-visibility:hidden; }

注意
避免使用会引起重排的属性做动画,如:letf / margin / padding / width / height 等会引起大量重排重绘的属性

Animation动画

可以将从一个CSS样式配置转换到另一个CSS样式配置

动画包括两个部分: 描述动画的样式规则和用于指定动画开始、结束以及中间点样式的关键帧。

优点:简单,性能良好,浏览器可以自动优化

基本属性

动画的实际表现是由 @keyframes规则实现

  • animation-name 动画的名称(由@keyframes定义)
  • animation-delay 动画延时
  • animation-direction 定义动画完成后,是从初始状态还是从最终状态重复动画
  • animation-duration 动画时长
  • animation-iteration-count 动画重复次数
  • animation-play-state 播放或暂停动画
  • animation-timing-function 设置动画关键帧之间的运动函数
  • animation-fill-mode 指定动画前后如何为元素应用样式

关键帧序列 keyframes

使用@keyframes定义动画序列关键帧

简单的动画可以使用 from/to 来定义 开始和结束关键帧

p {
animation-duration: 3s;
animation-name: slidein;
}
@keyframes slidein {
from {
margin-left: 100%;
width: 300%;
}
to {
margin-left: 0%;
width: 100%;
}
}

复杂的动画可以使用 百分比 更精确的控制

.tada {
animation-name: tada;
animation-duration: 3s;
}
@keyframes tada {
0% {
transform: scale3d(1, 1, 1);
}
10%, 20% {
transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
}
30%, 50%, 70%, 90% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
40%, 60%, 80% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
100% {
transform: scale3d(1, 1, 1);
}
}

运动方向 animation-direction

animation-direction: normal | alternate | alternate-reverse | reverse

  • normal: 正向运动
  • alternate: 先正向再反向再正再反循环反复
  • alternate-reverse: 先反向再正向再反再正循环反复
  • reverse: 反向运动

重复次数 animation-iteration-count

animation-iteration-count:infinite | <single-animation-iteration-count>

  • infinite: 循环执行
  • single-animation-iteration-count:循环的周期数值(可以是小数)

播放暂停 animation-play-state

animation-play-staterunning | paused [ , running | paused ]*

  • running:运动
  • paused:暂停

起始状态控制 animation-fill-mode

animation-fill-mode: none | forwards | backwards | both

设置元素动画之后的状态

  • none:不应用状态
  • forwards: 应用动画结束帧的状态(结束帧与动画运动次数和方向有关)
  • backwards: 应用动画起始帧的状态 (与动画运动方向有关)
  • both: 应用首帧与结束帧(同时应用 forwards 与 backwrads)

Transition 与 Animation 动画的触发

可以通过伪类 (:link、:visited、:hover、:active、:focus、:checked、:enabled、:disabled) 和 事件触发

如果直接给元素设置 transform,用户将直接看到元素的最终状态,而不会有动画

通过切换元素设定好的动画类可以很方便的控制 TransitonAnimation 动画

Transiton动画 有入场和出场动画,比如一个元素在 :hover状态时触发 transiton 动画,在鼠标离开后,动画自动反向播放回到 transition 之前的状态

Transition 通过伪类、事件或一个 Class 切换,可以很方便的实现入场和出场动画, 而 Animation 动画则要通过不同的 Class 来控制入场和出场

监听动画结束的事件

  • Transition动画监听 transitionend 事件
  • Animation动画监听 animationend 事件

简单粗暴的方法: 监听所有可能支持的事件

var transitionEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'OTransition' : 'oTransitionEnd',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
}
var animationEndEventNames = {
'WebkitAnimation' : 'webkitAnimationEnd',
'OAnimation' : 'oAnimationEnd',
'msAnimation' : 'MSAnimationEnd',
'animation' : 'animationend'
}
function getEventNames(nameMap){
retunr Object.keys(nameMap).map(v=>nameMap[v]).join(' ');
}
var animationEndEventName = getEventNames(transitionEndEventNames);
var transitionEndEventName = getEventNames(animationEndEventNames);
oDiv.addEventListener( transitionEndEventName, callbackFn )
oDiv.addEventListener( animationEndEventName, callbackFn )

只监听受支持的事件

通过检测 body.style 中是否有相关属性来获取受支持的属性名称

var transEndEventNames = {
WebkitTransition: 'webkitTransitionEnd',
MozTransition: 'transitionend',
OTransition: 'oTransitionEnd otransitionend',
transition: 'transitionend'
};
var animEndEventNames = {
WebkitAnimation: 'webkitAnimationEnd',
MozAnimation: 'animationend',
OAnimation: 'oAnimationEnd oanimationend',
animation: 'animationend'
};
var elStyle = (document.body || document.documentElement).style;
function getSupportName(nameMap){
var keys = Object.keys(nameMap).filter(v=>elStyle[v] !== undefined);
return nameMap[keys[0]];
}
var transitionEnd = getSupportName(transEndEventNames);
var animationEnd = getSupportName(animEndEventNames);
oDiv.addEventListener( transitionEnd, callbackFn )
oDiv.addEventListener( animationEnd, callbackFn )

注意

  • 当属性值没有发生变化或没有绘制行为发生,transitionend 事件不会被触发
  • 如果有多个属性进行transtion动画,则会触发多次 transitierend 事件

确保 transitioned 事件触发

结合jQuery 确保 transitioned 事件能被触发

$.fn.emulateTransitionEnd = function(duration) {
var called = false, $el = this;
$(this).once('webkitTransitionEnd', function() { called = true; });
var callback = function() { if (!called) $($el).trigger('webkitTransitionEnd'); };
setTimeout(callback, duration);
};

使用

$(this).one('webkitTransitionEnd', callback);
$(this).emulateTransitionEnd(options.duration + 50);
$(this).css(properties);

原理:同时给 元素 绑定两个 webkitTransitionEnd 事件和一个 setTimeout,如果 webkitTransitionEnd 能正确触发,则 calledtruesetTimeout 中回调内的代码则不执行

强制重绘触发动画

给一个 display:none 元素添加 Transition 动画时,先要将 display设置为block,同时应用 Transition 动画,但是两套css执行的时间间隔太近,浏览器会尝试优化css属性的变化,直接表现为transition最终的状态,无法显示动画效果,为了避免这种情况,可以在将 display 设置为 block 时,同时强制浏览器对css进行重绘,触发 transition 动画(一般获取一次元素的offsetHeight 属性即可)。

会引起界面重绘的属性

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. width,height
  5. 请求了getComputedStyle()

当请求上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。

结合 jQuery 添加一个强制重绘的方法:

$.fn.redraw = function(){
$(this).each(function(){
var redraw = this.offsetHeight;
});
};

使用

$('.element').css({left: '10px'})
.redraw()
.transition({left: '20px'});

这种方法在大多数浏览器都有效,但在个别 Andorid默认浏览器上偶尔还是会失效。这时就只能使用定时器或者增加class了。

Transition动画应用示例

Animation动画应用示例

扩展阅读