这个对象从名字上来说就能看出来是一个用来做动画的东西,首先我们先用 JS 来实现一个动画看一看
js
const box = document.getElementsByClassName("box")[0];
const boxPosition = {
left: parseFloat(window.getComputedStyle(box).left),
top: parseFloat(window.getComputedStyle(box).top)
};
const proxBox = new Proxy(boxPosition, {
set(target, key, value) {
if (key === "left" || key === "top")
box.style[key] = value + "px";
Reflect.set(target, key, value);
}
});
setInterval(() => {
proxBox.left += 1;
proxBox.top += 1;
}, 16);这里是用代理实现的,如果读者没有接触过 ES 6,可以使用下面更简单的方式
js
const box = document.getElementsByClassName("box")[0];
box.style.left = window.getComputedStyle(box).left;
box.style.top = window.getComputedStyle(box).top;
setInterval(() => {
box.style.left = parseFloat(box.style.left) + 1 + "px";
box.style.top = parseFloat(box.style.top) + 1 + "px";
}, 16);接下来看效果:

这里解释一下为什么定时器设置的是 16 毫秒,其实很简单,浏览器的刷新频率一般是 60 帧,也就是一秒钟 60 张图片,而 1000/60=16.6666666667,也就是说 16 帧刚好能契合上浏览器的刷新率。
但是我们其实很清楚,但是浏览器计算也是需要时间的,我们无法保证渲染的时候已经计算好了,所以也很难实现完美的契合浏览器刷新率。
此时为了契合浏览器刷新率,更好实现 JS 动画,就可以使用 window.requestAnimationFrame() 方法来实现,该方法就是专门用来实现动画的,这个方法传入一个回调函数,当渲染帧到达的之前会先调用该方法,该方法结束后才会进行渲染,注意,该方法是一次性的,如果我要继续更新下一帧率的动画,需要使用递归。
既然涉及到动画,这里肯定涉及到时间,这个回调函数传入的参数就是一个时间,这个时间就是这个函数执行时刻的时间。并且这个 requestAnimationFrame 函数会返回一个 ID,我们可以使用 cancelAnimationFrame() 传入一个 id 从而取消这个渲染帧函数
js
/**
* 获取页面中第一个class为"box"的元素,并初始化boxPosition对象用于记录box的初始位置
* @returns {HTMLElement} 页面中第一个class为"box"的元素
*/
const box = document.getElementsByClassName("box")[0];
/**
* 初始化boxPosition对象,存储元素的初始left和top位置
* @type {{left: number, top: number}}
*/
const boxPosition = {
left: parseFloat(window.getComputedStyle(box).left),
top: parseFloat(window.getComputedStyle(box).top)
};
/**
* 创建一个Proxy代理对象proxBox,拦截对boxPosition.left和boxPosition.top的修改并同步到box元素的样式中
* @type {*}
*/
const proxBox = new Proxy(boxPosition, {
set(target, key, value) {
if (key === "left" || key === "top") {
box.style[key] = value + "px";
}
Reflect.set(target, key, value);
}
});
/**
* 初始化动画相关变量
*/
let lastTime = 0; // 上一帧时间
let isAnimate = false; // 是否正在执行动画
let rid = 0; // 动画请求ID
/**
* 实现动画逻辑的函数
* @param {number} time 当前时间戳(毫秒)
*/
function animate(time) {
if (!isAnimate) {
lastTime = time;
isAnimate = true;
} else {
// 计算自上一帧以来的时间差
const disTime = time - lastTime;
lastTime = time;
// 根据时间差计算移动距离,假设每毫秒移动0.1像素
const s = disTime * 0.1;
// 更新box的位置
proxBox.left += s;
proxBox.top += s;
}
rid = requestAnimationFrame(animate);
}
// 监听box元素的点击事件,启动或停止动画
box.onclick = function () {
if (!isAnimate) {
rid = requestAnimationFrame(animate);
} else {
cancelAnimationFrame(rid);
isAnimate = false;
rid = 0;
}
}这样实现出来的动画会更加细腻,这种方式的性能也比定时器动画要更好,当然,我们要使用动画最丝滑的还是 CSS 3
这里我们可以使用 requestAnimationFrame 封装一个动画函数
js
function animation(from, to, duration, fn) {
//在duration的时间内,从from变到to,每次变化调用fn,传入当前数值
const startTime = Date.now();//获取当前时间戳
const speed = (to - from) / duration;//获取速度
let rid;
function run() {
const time = Date.now() - startTime;//获取运行该函数时候的相对时间
const currentValue = from + speed * time;//获取当前数值
if (currentValue >= to) {//如果当前数值达到标准
cancelAnimationFrame(rid);//取消
fn(to);
return;//返回
}
fn(currentValue);
rid = requestAnimationFrame(run);
}
requestAnimationFrame(run);
}
const box = document.querySelector('.box');
animation(0, 200, 2000, (v) => {
box.style.left = v + 'px';
});