Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

requestAnimationFrame定时动画 #16

Open
heyach opened this issue Sep 28, 2021 · 0 comments
Open

requestAnimationFrame定时动画 #16

heyach opened this issue Sep 28, 2021 · 0 comments

Comments

@heyach
Copy link
Owner

heyach commented Sep 28, 2021

前言


用js的setInterval实现动画有很多缺点,首先是精度问题,如果有大量的dom操作,动画间隔是不准确的。
requestAnimationFrame会将所有的dom操作集中起来,跟随浏览器的刷新频率(60帧)一次完成,会区分隐藏不可见的元素,性能更好。
但是requestAnimationFrame和setInterval对时间的处理方式是不一样的,setInterval直接传递一个时间间隔(ms单位),以此执行回调函数

setInterval(() => {
    //
}, 1000)

而requestAnimationFrame会传递一个时间戳timestamp,表示自页面加载以来,回调函数被执行的时刻,一直增大。

13.502 30.098 46.995 63.45 ...

通过时间戳可以得到时间间隔从而进行处理,也就是说每个动画还需要一个变量来记录时间来和timestamp进行对比。

var time = 0;
function A(t) {
    requestAnimationFrame(A)
    console.log("距离上一次执行的时间间隔", t - time)
    time = t
}
A()
// 距离上一次执行的时间间隔 16.64300000000003
// 距离上一次执行的时间间隔 16.663000000000466
// 距离上一次执行的时间间隔 16.87199999999939
// 距离上一次执行的时间间隔 16.472000000000662
// 距离上一次执行的时间间隔 16.639999999999418
// 距离上一次执行的时间间隔 16.649000000000342
// 距离上一次执行的时间间隔 16.842999999999847
// 距离上一次执行的时间间隔 16.509000000000015
// 距离上一次执行的时间间隔 16.61999999999989
// 距离上一次执行的时间间隔 16.646999999999935
// 距离上一次执行的时间间隔 16.789999999999964
// 距离上一次执行的时间间隔 16.605999999999767
// 距离上一次执行的时间间隔 17.220000000000255
// 距离上一次执行的时间间隔 16.038000000000466
// 距离上一次执行的时间间隔 16.787999999999556
// 距离上一次执行的时间间隔 16.576000000000022
// 距离上一次执行的时间间隔 16.699999999999818
// 距离上一次执行的时间间隔 16.634000000000015
// 距离上一次执行的时间间隔 16.72400000000016
// 距离上一次执行的时间间隔 16.775000000000546

自定义时间间隔

由上面的例子可以看出,requestAnimationFrame需要自行控制执行的时间间隔,假如要实现一个秒计时器。

var interval = 1000
var lastTime = 0
function SecondTimer(t) {
    requestAnimationFrame(SecondTimer)
    if(t - lastTime >= interval) {
        console.log("1s到了")
        lastTime = t
    }
}
SecondTimer()

封装一个定时器

function Timer(interval, fn) {
    this.lastTime = 0
    this.interval = interval
    this.timer = null

    (function loop(timestamp){
        that.timer = requestAnimationFrame(loop)
        if(timestamp - that.lastTime > that.interval) {
            typeof fn == "function" && fn()
            that.lastTime = timestamp;
        }
    })()
}

Timer.prototype.clear = function() {
    cancelAnimationFrame(this.timer)
}

var secondTimer = new Timer(1000, () => {
    console.log("1s到了")
})
var second10Timer = new Timer(10000, () => {
    console.log("10s到了")
})

// 15s后清除
setTimeout(() => {
    secondTimer.clear()
    second10Timer.clear()
}, 15000)

实现canvas动画

先构建一个舞台stage,然后所有的动画元素都视为stage的子元素,stage以一个固定的频率刷新,子元素各自进行自己的动画

<canvas id="stage"></canvas>
window.onload = function() {
    var stage = document.getElementById("stage")
    stage.width = 300
    stage.height = 300
    stage.childrens = []
    var ctx = stage.getContext("2d")
    function Circle(x, y, r, c, fps) {
        this.x = x
        this.y = y
        this.r = r
        this.c = c
        this.fps = fps
        this.lt = 0
        this.init()
    }
    Circle.prototype.move = function() {
        if(this.x <= 300) {
            this.x += 1
        } 
        if(this.y <= 300) {
            this.y += 1
        }
    }
    Circle.prototype.draw = function() {
        ctx.beginPath()
        ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)
        ctx.strokeStyle = this.c
        ctx.stroke()
        ctx.closePath()
    }
    Circle.prototype.init = function() {
        this.draw()
    }
    Circle.prototype.update = function(t) {
        if(t - this.lt > this.fps) {
            this.move()
            this.lt = t
        }
        this.draw()
    }
    var c = new Circle(0, 0, 20, 'red', 16)
    stage.childrens.push(c)
    var c2 = new Circle(0, 0, 20, 'blue', 32)
    stage.childrens.push(c2)
    var c3 = new Circle(0, 0, 20, 'yellow', 64)
    stage.childrens.push(c3)
    
    stage.update = function(timestamp) {
        requestAnimationFrame(stage.update)
        ctx.clearRect(0, 0, stage.width, stage.height)
        stage.childrens.forEach(item => {
            item.update(timestamp)
        })
    }
    stage.update()
}

此时我们只需要改变元素的动画形式,就可以得到效果。

Circle.prototype.move = function() {
    // if(this.x <= 300) {
    //     this.x += 1;
    // } 
    // if(this.y <= 300) {
    //     this.y += 1;
    // }
    this.r = Math.random() * 50
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant