我们还需要那些属性呢?
首先就是计时器的id:timer;然后运动的总次数:count;当前的运动次数:curCount;当前值:curValue
所以我们现在可以写出一个大概的函数模型,如下:
function animationFunc({ begin, end, interval = 16, total = 3000, callback }) {
let timer = null // 接收定时器ID
let count = Math.ceil(total / interval) // 运动次数【向上取整】
let step = Math.abs((end - begin)) / count // 每次运动的步长
let current = begin // 当前的位置
let currentCount = 0 // 当前运动次数
timer = setInterval(() => {
// 如果存在回调函数就调用-并传递当前值
callback && callback(current)
}, interval)
}
其实到现在,我们的这个插件已经实现的差不多了,只是缺少一些值的计算和停止条件
每次执行后次数+1,当前值 = 当前值 + 每次运动的步长,如果当前次数大于或者等于总次数,就停止执行,如下:
function animationFunc({ begin, end, interval = 16, total = 3000, callback }) {
let timer = null
let count = Math.ceil(total / interval)
let step = Math.abs(end - begin) / count
let curValue = begin
let currentCount = 0
timer = setInterval(() => {
// 改变当前值
curValue += step
// 次数+1
currentCount++
// 如果当前次数大于或者等于总次数就停止执行
if (currentCount >= count) {
// 动画结束
callback && callback(curValue)
clearInterval(timer)
timer = null
return
}
callback && callback(curValue)
}, interval)
}
那我们具体使用看一下,html 中写了一个使用绝对定位固定的 div,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
position: absolute;
left: 0;
top: 20px;
background-color: skyblue;
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script src="./index.js"></script>
</body>
</html>
看看使用代码,如下:
const box = document.querySelector('.box')
// 动画函数
function animationFunc({ begin, end, interval = 16, total = 3000, callback }) {
let timer = null
let count = Math.ceil(total / interval)
let step = Math.abs(end - begin) / count
let curValue = begin
let currentCount = 0
timer = setInterval(() => {
curValue += step
currentCount++
if (currentCount >= count) {
callback && callback(curValue)
clearInterval(timer)
timer = null
return
}
callback && callback(curValue)
}, interval)
}
box.onclick = function () {
animationFunc({
begin: 0,
end: 500,
callback: curValue => {
box.style.left = curValue + 'px'
}
})
}
执行效果如图:
当然经过 gif 的转换和录屏本身的原因,会存在不小的抖动
上述我们已经实现了这个动画的函数,而且由于具体实现的业务不为动画函数本身决定,大大增加了灵活程度,但是业务总是千奇百怪的,比如我可能是希望能在动画执行开始和完成的时机调用一个方法,告诉我动画函数执行完成了,此时我们可以多加两个参数 onBegin、onFinish,同时为了具备的更加语义化 callback 改为 onChange,如下:
const box = document.querySelector('.box')
// 动画函数
function animationFunc({ begin, end, interval = 16, total = 3000, onChange, onBegin, onFinish }) {
// 执行的时候调用一次 onBegin
onBegin && onBegin()
let timer = null
let count = Math.ceil(total / interval)
let step = Math.abs(end - begin) / count
let curValue = begin
let currentCount = 0
timer = setInterval(() => {
curValue += step
currentCount++
if (currentCount >= count) {
// 执行完成后调用 onFinish
onFinish && onFinish()
onChange && onChange(curValue)
clearInterval(timer)
timer = null
return
}
onChange && onChange(curValue)
}, interval)
}
box.onclick = function () {
animationFunc({
begin: 0,
end: 500,
onChange: curValue => {
box.style.left = curValue + 'px'
},
onBegin: () => {
box.innerHTML = '开始执行动画'
},
onFinish: () => {
box.innerHTML = '动画执行完成'
}
})
}
效果如图:
至于具体需要利用这两个时机完成什么,或者做点什么,就看各自的业务需求了,还可以根据你自己的需求作出更多扩展,比如传递多个起始值可以执行多组动画
当然,考虑严谨性,可以考虑多写一些参数的类型判断或者当前的值如果大于了结束值,就赋值为结束值,如下:
function isFunction(value) {
// 为空表示未使用,则默认返回 true
if (value === null || value === undefined) return true
return typeof value === 'function'
}
// 动画函数
function animationFunc({ begin, end, interval = 16, total = 3000, onChange, onBegin, onFinish }) {
if (!isFunction(onBegin)) {
throw new Error('onBegin 必须是函数')
}
if (!isFunction(onFinish)) {
throw new Error('onFinish 必须是函数')
}
if (!isFunction(onChange)) {
throw new Error('onChange 必须是函数')
}
if (begin === undefined || begin === null) {
throw new Error('begin 必须有值')
}
if (end === undefined || end === null) {
throw new Error('end 必须有值')
}
onBegin && onBegin()
let timer = null
let count = Math.ceil(total / interval)
let step = Math.abs(end - begin) / count
let curValue = begin
let currentCount = 0
timer = setInterval(() => {
curValue += step
currentCount++
if (curValue > end) curValue = end
if (currentCount >= count) {
onFinish && onFinish()
onChange && onChange(curValue)
clearInterval(timer)
timer = null
return
}
onChange && onChange(curValue)
}, interval)
}