1. 体验动画
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
div {
width: 100px;
height: 100px;
background-color: pink;
position: absolute;
}
</style>
</head>
<body>
<button>动画</button>
<div class="box" style="left: 0px"></div>
<script>
var btn = document.getElementsByTagName("button")[0];
var div = document.getElementsByTagName("div")[0];
//匀速运动
btn.onclick = function () {
//定时器,每隔一定的时间向右走一些
setInterval(function () {
console.log(parseInt(div.style.left));
// div.style.left = parseInt(div.style.left)+10+"px"; //NaN不能用
//动画原理: 盒子未来的位置 = 盒子现在的位置 + 步长;
//赋值给style.left,用offsetLeft获取值。
//style.left获取值不方便,获取行内式,如果没有事“”;容易出现NaN;
//offsetLeft获取值特别方便,而且是现成number方便计算。因为他是只读的不能赋值。
div.style.left = div.offsetLeft + 10 + "px";
},300);
}
</script>
</body>
</html>
通过setInterval
每隔一定的时间向右走一些,实现了动画的效果,但是一旦动画开始不能停下来。
2. 动画封装
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.box1 {
margin: 0;
padding: 5px;
height: 200px;
background-color: #ddd;
position: relative;
}
button {
margin: 5px;
}
.box2 {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
left: 0;
}
</style>
</head>
<body>
<div class="box1">
<button>运动到200</button>
<button>运动到400</button>
<div class="box2"></div>
</div>
<script>
var btnArr = document.getElementsByTagName("button");
var box2 = document.getElementsByClassName("box2")[0];
var timer = null;
//绑定事件
btnArr[0].onclick = function () {
animate(200);
};
btnArr[1].onclick = function () {
animate(400);
};
function animate(target){
timer = setInterval(function () {
//盒子自身的位置+步长
box2.style.left = box2.offsetLeft + 10 + "px";
//如果停止盒子?清除定时器
if(box2.offsetLeft === target){
clearInterval(timer);
}
},30);
}
</script>
</body>
</html>
上面的改进的例子实现了盒子“走”到固定的位置就停下来,但是我们发现,他运动的方向是固定的,而且当第二次触发动画的时候,不会停下来。
3. 去除bug版
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.box1 {
margin: 0;
padding: 5px;
height: 200px;
background-color: #ddd;
position: relative;
}
button {
margin: 5px;
}
.box2 {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
left: 0;
}
</style>
</head>
<body>
<div class="box1">
<button>运动到200</button>
<button>运动到400</button>
<div class="box2"></div>
</div>
<script>
var btnArr = document.getElementsByTagName("button");
var box2 = document.getElementsByClassName("box2")[0];
var timer = null;
//绑定事件
btnArr[0].onclick = function () {
animate(200);
}
btnArr[1].onclick = function () {
animate(400);
}
function animate(target){
//BUG1:点击多次以后,越来越快:每次只能开一个定时器。(执行定时器前面,先清楚定时器)
//要用定时器,先清定时器。
clearInterval(timer);
//BUG2:无法返回。 原因就是步长不能为恒定值。
// 传递的目标值如果比当前值大,那么步长为+10;
// 传递的目标值如果比当前值小,那么步长为-10;
var speed = target>box2.offsetLeft ? 10:-10;
timer = setInterval(function () {
//BUG3:二次点击不停止问题。
//如果当前值===目标值,那么先判断之间的距离还有多少,如果小于步长,那么就别走了,马上清除定时器
var val = target - box2.offsetLeft;
//盒子自身的位置+步长
box2.style.left = box2.offsetLeft + speed + "px";
//如何停止盒子?清除定时器
if(Math.abs(val)<Math.abs(speed)){
box2.style.left = target+ "px";
clearInterval(timer);
}
},30);
}
</script>
</body>
</html>
4. 最终版
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
.box1 {
margin: 0;
padding: 5px;
height: 300px;
background-color: #ddd;
position: relative;
}
button {
margin: 5px;
}
.box2 {
width: 100px;
height: 100px;
background-color: red;
position: absolute;
left: 0;
top: 40px;
}
.box3 {
width: 100px;
height: 100px;
background-color: yellow;
position: absolute;
left: 0;
top: 150px;
}
</style>
</head>
<body>
<div class="box1">
<button>运动到200</button>
<button>运动到400</button>
<div class="box2"></div>
<div class="box3"></div>
</div>
<script>
var btnArr = document.getElementsByTagName("button");
var box2 = document.getElementsByClassName("box2")[0];
var box3 = document.getElementsByClassName("box3")[0];
//绑定事件
btnArr[0].onclick = function () {
//如果有一天我们要传递另外一个盒子,那么我们的方法就不好用了
//所以我们要增加第二个参数,被移动的盒子本身。
animate(box2,200);
animate(box3,200);
}
btnArr[1].onclick = function () {
animate(box2,400);
animate(box3,400);
}
function animate(ele,target){
//要用定时器,先清除定时器
//一个盒子只能有一个定时器,这样的话,不会和其他盒子出现定时器冲突
//而定时器本身讲成为盒子的一个属性
clearInterval(ele.timer);
//我们要求盒子既能向前又能向后,那么我们的步长就得有正有负
//目标值如果大于当前值取正,目标值如果小于当前值取负
var speed = target>ele.offsetLeft?10:-10;
ele.timer = setInterval(function () {
//在执行之前就获取当前值和目标值之差
var val = target - ele.offsetLeft;
ele.style.left = ele.offsetLeft + speed + "px";
//目标值和当前值只差如果小于步长,那么就不能再前进了
//因为步长有正有负,所有转换成绝对值来比较
if(Math.abs(val)<Math.abs(speed)){
ele.style.left = target + "px";
clearInterval(ele.timer);
}
},30)
}
</script>
</body>
</html>
5. window.requestAnimationFrame
编写动画的关键是循环间隔的设置,一方面,循环间隔足够短,动画效果才能显得平滑流畅;另一方面,循环间隔还要足够长,才能确保浏览器有能力渲染产生的变化。
大部分的电脑显示器的刷新频率是60HZ,也就是每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会提升。因此,最平滑动画的最佳循环间隔是 1000ms / 60 ,约为16.7ms。
setTimeout
/setInterval
有一个显著的缺陷在于时间是不精确的,setTimeout
/setInterval
只能保证延时或间隔不小于设定的时间。因为它们实际上只是把任务添加到了任务队列中,但是如果前面的任务还没有执行完成,它们必须要等待。
requestAnimationFrame
采用的是系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
综上所述,requestAnimationFrame
和 setTimeout
/setInterval
在编写动画时相比,优点如下:
requestAnimationFrame
不需要设置时间,采用系统时间间隔,能达到最佳的动画效果。requestAnimationFrame
会把每一帧中的所有DOM
操作集中起来,在一次重绘或回流中就完成。- 当
requestAnimationFrame()
运行在后台标签页或者隐藏的<iframe>
里时,requestAnimationFrame()
会被暂停调用以提升性能和电池寿命(大多数浏览器中)。
参考MDN,下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp
参数,该参数是一个double类型,用于存储时间值,表示requestAnimationFrame()
开始去执行回调函数的时刻。
通过一个例子体会一下:
<!DOCTYPE html>
<html lang="en">
<head>
<title>practise</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<style>
#SomeElementYouWantToAnimate {
width: 100px;
height: 100px;
background-color: lightcoral;
}
</style>
</head>
<body>
<div id="SomeElementYouWantToAnimate"></div>
<script>
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 300) + 'px';
if (progress < 3000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
</script>
</body>
</html>