原文链接:https://javascript.info/css-animations,translate with ❤️ by zhangbao.
CSS 动画允许我们在不使用 JavaScript 的情况才创建一些简单动画。
JavaScript 可以用来控制 CSS 动画,只用很少的代码就可以制作出更加丰富的动画。
CSS 过渡
CSS 过渡的概念很简单。我们描述了一个属性以及它的变化是如何应用动画的。那么当属性改变的时候,浏览器就会绘制这个动画。
也就是说:我们只需要改变属性。流畅的过渡交由浏览器帮我们完成。
例如:下面的 CSS 描述了一个 backgorund-color 属性会发生 3 秒过渡的动画。
.animated {
transition-property: background-color;
transition-duration: 3s;
}
现在如果有一个元素使用了 .animated 这个类,那么当背景色 background-color 发生改变时,就会看到 3 秒钟的过渡动画效果。
点击下面的按钮,就能看到背景色的动画:
<button id="color">Click me</button>
<style>
#color {
transition-property: background-color;
transition-duration: 3s;
}
</style>
<script>
color.onclick = function() {
this.style.backgroundColor = 'red';
};
</script>
一共有 4 种属性来描述 CSS 过度效果:
transition-property
transition-duration
transition-timing-function
transition-delay
我们稍后就会覆盖讲述,现在使用的 transition 属性是其实是上述具体属性的简写形式,按照这样的顺序:property duration timing-function delay。同时我们也可以设置多个属性的过渡效果。
例如,按钮发生过渡动画的属性数增至两个:color 和 fontx-size。
<button id="growing">Click me</button>
<style>
#growing {
transition: font-size 3s, color 2s;
}
</style>
<script>
growing.onclick = function() {
this.style.fontSize = '36px';
this.style.color = 'red';
};
</script>
下面我们来一一覆盖讲述过渡动画属性:
transition-property
也可以为 transition-property 属性指定一个属性列表,表示应用过渡动画效果的属性列表。例如:left,margin-left,height,color。
并不是所有的属性都支持动画的,但是多数是。关键字 all 表示“所有属性都应用过度动画”。
transition-duration
transition-duration 用于指定过度动画运行的时间,这是时间遵循 CSS 时间格式:秒 s 或者是毫秒 ms。
transition-delay
transition-delay 指定动画延迟,也就是动画开始之前的等待时间。例如,如果设置了 transition-delay: 1s,就表示动画开始时间要延迟 1 秒发生。
负值也是支持的,表示选择过渡期间的某个时刻作为过渡动画开始起点。例如,如果 transition-duration 是 2s,delay 是 -1s,那么就是从原本过渡动画的 1 秒那个时刻作为动画起始开始的,也就是从半路上。
下面的例子就是使用了 CSS transition 属性从 0 到 9 转移数字:
<style>
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
</style>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script>
stripe.onclick = function() {
stripe.classList.add('animate');
};
</script>
transform 是这样动画的:
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
}
上面的例子里,JavaScript 为元素添加了 .animate 这个类——然后动画就启动了。
stripe.classList.add('animate');
我们也可以从“中间”开始,从确切的数字开始,例如:对应于当前秒,使用负的 transition-delay。
下面例子中,如果你点击了数值,动画就会从当前秒开始:
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
// 例如, -3s 的话动画就是从高 3 秒处开始
stripe.style.transitionDelay = '-' + sec + 's';
stripe.classList.add('animate');
};
transition-timing-function
时间函数描述了动画过程如何随时间分布,它是开始很慢然后变快,还是反过来这样。
这是一个第一眼看上去最复杂的属性,但是如果花一点时间去学习的话,还是很简单的。
该属性接受两个值:贝塞尔曲线或帧函数。让我们从曲线开始,因为它使用地更频繁。
贝塞尔曲线
事件函数可以设置为贝塞尔曲线,其中4个控制点满足以下条件:
第一个控制点:(0, 0)。
第二个控制点:(1, 1)。
对于中间点,x 的值必须在 0..1 之间,y 可以为任何值。
CSS 中的贝塞尔曲线语法:cubic-bezier(x2, y2, x3, y3)。其中我们只需要制定第二和第三个控制点就可以了。因为第一个点被固定在 (0, 0) 位置上,而第四个点被固定在了 (1, 1) 的位置上。
时间函数用来描述动画过程当中的及时进度。
x 轴表示时间:0 表示起点,1 表示终点。都是相对应 transition-duration 属性而言的。
y 轴表示进程度:0 表示属性的初始值,1 表示属性的最终值。
最简单的变体是当动画以相同的线速度均匀地运动时。此时的贝塞尔曲线指定为 cubic-bezier(0, 0, 1, 1)。
看起来是这样子的:
我们可以看到,这是一条直线。随着时间(x)流逝,动画的完成度(y)稳步从 0 到 1。
下面这列小火车点击后,会从左到右匀速前进:
<style>
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
}
</style>
<div class="train" onclick="this.style.left='450px'">🚆</div>
CSS transition 属性是基于贝塞尔曲线的。
……我们怎样才能让列车减速?
我们可以使用另外一个贝塞尔曲线函数:cubic-bezier(0.0, 0.5, 0.5 ,1.0)。
如图:
我们可以看到,开始时很快:曲线上升,然后越来越慢。
下面将上面 .train 使用的贝塞尔曲线替换一下:
.train {
...
transition: left 5s cubic-bezier(0, 0.5, 0.5, 1.0);
}
这里提供了几个内置的贝塞尔曲线(关键字):linear,ease,ease-in,ease-out 和 ease-in-out。
linear 就是 cubic-bezier(0, 0, 1, 1) 的简写形式——一条直线,我们已经看到过了。
其他的关键字对应的 cubic-bezier 如下所示:
ease* | ease-in | ease-out | ease-in-out |
---|---|---|---|
(0.25, 0.1, 0.25, 1.0) | (0.42, 0, 1.0, 1.0) | (0, 0, 0.58, 1.0) | (0.42, 0, 0.58, 1.0) |
*——默认值,如果没有提供时间函数的话,默认就使用 ease。
因此,我们可以使用 ease-out 来作为火车减速的时间函数使用:
.train {
...
transition: left 5s ease-out;
}
但它看起来有点不同。
贝塞尔曲线可以使动画“跳出”其范围。
贝塞尔曲线的控制点的 y 坐标可以是任意值:负数或者一个很大的值,使动画超出正常范围。
接下来我们使用下面的动画代码:
.train {
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
/* JavaScript sets left to 400px */
}
left 属性从 100px 运动到 400px。
如果我们点击小火车的话,会看到:
首先,火车先倒退:left 值变成小于 100px 的值。
然后,开始前进,会比 400px 稍微大点。
最后回到 400px 这个位置。
为什么会发生 - 如果我们查看给定贝塞尔曲线的图表,这很明显:
我们将第二个点的 y 坐标移动到 0 之下,第三个点的 y 坐标移动到 0 之上,所以这个曲线超出了“正常的”象限。y 值没有落在正常的 0..1 的范围之内。
我们知道,y 值表示“动画过程的完成度”。y = 0 对应属性的初始值,而 y = 1 对应属性变化的最终值。因此,y < 0 表示的是比 left 初始值还要小的值,而 y > 1 表示比最终值还要大的值。
这肯定是一个“软”变种。 如果我们将y值设置为 -99 和 99,那么列车将跳出范围更多。
但是如何为特定任务制作贝塞尔曲线? 有很多工具。例如,我们可以在 http://cubic-bezier.com/ 网站上制作。
帧函数 Steps
时间函数 steps(number of steps[, start/end])
允许将动画分割成一帧一帧的。
让我们看一下数字的例子。
这是一个数字列表,没有任何动画,只是作为一个来源:
<style>
#digit {
border: 1px solid red;
width: 1.2em;
}
#stripe {
display: inline-block;
font: 32px monospace;
}
</style>
<div id="digit"><div id="stripe">0123456789</div></div>
我们将使列表中红色“窗口”之外的部分不可见,并使列表向左移动每个步骤,从而使数字以离散方式显示。
将有 9 个步骤,每个数字的步骤移动:
#stripe.animate {
transform: translate(-90%);
transition: transform 9s steps(9, start);
}
steps(9, strart) 的第一个参数是步骤数。转换过程被分隔成了 9 个部分(每个占 10% 的进度),中间的之间间隔也会平均分配在每个步骤中,transition: 9s 表示整个动画费时 5s,每个数字分配 1 秒。
第二个参数是两个关键字之一:start 或 end。
start 关键字表示在动画开始的时候就立即进入第一步骤。
我们可以在动画中观察到:当我们点击数字的时候,立即变成 1(第一步),然后在下一秒的时候开始的时候改变。
这个过程是这样进行的:
0s:-10%(第一个 1 秒钟开始的时候,改变立即发生)。
1s:-20%
……
8s:-80%
(最后一秒的开始显示最终值)。
针对 end 值的话,表示改变不是在开始的时候就发生的,而是在每个秒钟的最后。
因此,过程看起来就是这样子的:
0s:0
1s:-10%(第一次改变发生在第一个秒钟的最后)。
2s:-20%
……
9s:-90%
帧函数还提供了两种简写形式:
step-start:等同于 steps(1, start)。也就是,也就是说,动画立即开始并采取一步。所以它立即开始并完成,好像没有动画一样。
step-end:等同于 steps(1, end)。一步动画在 transition-duration 的结尾处执行。
这些值很少使用,因为这不是真正的动画,而是单步更改。
事件 transitionend
当 CSS 动画运动结束后,会触发 transitionend 事件。
这个事件通常用来在动画结束之后做些事情,我们还可以加入动画。
例如,下面示例中的船开始在那里游泳并在点击时返回,每次越往右越远:
这个动画是通过初始化 go 函数进行的。然后每次动画结束的时候,重新执行并朝相反的方向游行。
boat.onclick = function() {
//...
let times = 1;
function go() {
if (times % 2) {
// 朝右驰行
boat.classList.remove('back');
boat.style.marginLeft = 100 * times + 200 + 'px';
} else {
// 朝左驰行
boat.classList.add('back');
boat.style.marginLeft = 100 * times - 200 + 'px';
}
}
go();
boat.addEventListener('transitionend', function() {
times++;
go();
});
};
transitionend 的事件对象有几个特定属性:
event.propertyName
完成动画的属性名。如果我们同事动画了多个属性的话,这个属性就比较有用了。
(注:如果我们同时动画了多个属性,且持续时间是一样的,那么最终只会触发一次 transitionend 事件,对应的属性名是 transition-property 属性值列表里的最后一个;如果同时动画了多个属性,但每个属性持续时间不一,那么每次每个属性结束动画,都会触发一次 transitionend 事件)。
event.elapsedTime
动画花费的时间(以秒计),不包含 transition-delay 的时间。
Keyframes
当我们需要将多个简单的动画整合到一起的时候,可以使用 CSS 规则 @keyframes。
它指定了动画的“名称”和运动规则:什么,什么时候,在哪里去动画。使用 animation 属性我们可以为元素绑定动画,并且为其指定额外的一些参数。
这里是带解释的例子:
<div class="progress"></div>
<style>
@keyframes go-left-right { /* 动画的名称: "go-left-right" */
from { left: 0px; } /* 从 left: 0px 开始动画 */
to { left: calc(100% - 50px); } /* 运动到 left: 100%-50px */
}
.progress {
animation: go-left-right 3s infinite alternate;
/* 为元素指定动画 "go-left-right"
执行时间为 3 秒
执行次数: 无限次(infinite)
来回做动画运动
*/
position: relative;
border: 2px solid green;
width: 50px;
height: 20px;
background: lime;
}
</style>
网上有很多 @keyframes 属性的文章,并且还有详尽的规范。
也许你不会经常使用 @keyframes,除非你的网站上一切都在不断变化。
总结
CSS 动画允许平滑(或不)动画一个或多个 CSS 属性的更改。
它们适用于大多数动画任务。 我们也可以使用 JavaScript 进行动画制作,下一章将专注于此。
与 JavaScript 动画相比,CSS 动画的局限性:
CSS 动画 | JavaScript 动画 |
---|---|
- 简单事情简单做 |
- 快和轻 CPU
|
- JavaScript 动画非常灵活。 他们可以实现任何动画逻辑,如元素的“爆炸”。
- 不只是属性变化。 我们可以在 JavaScript 中创建新元素以用于动画。
|
大多数动画可以使用 CSS 实现,如本章所述。 而 transitionend 事件允许在动画之后运行 JavaScript,因此它与代码完美集成。
但在下一章中,我们将做一些 JavaScript 动画来涵盖更复杂的案例。
(完)