在用requestAnimationFrame写动画效果中遇到切换页面的问题

最近在用requestAnimationFrame弄了个简单波纹的动画,代码如下:

function ripple1(id, option={}){
    var svg = document.getElementById(id);
    if(!svg){
        console.log('no has this id');
        return false;
    }
    var n = option['n'] || 5;
    var color = option['color'] || 'black';
    var width = option['width'] || svg.width.baseVal.value;
    var height = option['heght'] || svg.height.baseVal.value;
    var lineWidth = option['lineWidth'] || 2;
    var min_R = option['min_R'] || 10;
    var max_R = option['max_R'] || Math.min(width, height)/2 - lineWidth/2;
    var time = option['time'] || 5000;

    var dt = 0;
    var last_time = Date.now();
    var rings = [];

    for(var i=0; i<n;i++){
        var r = i*(max_R-min_R)/n + min_R;
        var opacity = 1-i*1/n;
        var circle = new Circle(r, opacity);
        svg.appendChild(circle.el);
        rings.push(circle);
    }


    function run(){
        dt = Date.now() - last_time;
        last_time = Date.now();

        var pro = dt/time;
        var dr = pro*(max_R-min_R);
        var dopacity = pro*1;

        for(var i=0; i<rings.length; i++){
            var circle = rings[i];
            circle.move(dr, dopacity);
            circle.draw();
        }

        window.requestAnimationFrame(run)
    }

    window.requestAnimationFrame(run)

    function Circle(r, opacity){
        var circle = document.createElementNS("http://www.w3.org/2000/svg","circle");
        circle.setAttribute('cx', width/2);
        circle.setAttribute('cy', height/2);
        circle.setAttribute('r', r);
        circle.setAttribute('stroke', color);
        circle.setAttribute('stroke-width', lineWidth);
        circle.setAttribute('fill', 'none');
        circle.setAttribute('stroke-opacity', opacity);

        this.el = circle;
        this.r = r;
        this.opacity = opacity;
    }
    Circle.prototype.move = function(dr, dopacity){
        this.r += dr;
        this.opacity -= dopacity;
        if(this.r > max_R){
            this.r = min_R;
            this.opacity = 1;
        }
    }
    Circle.prototype.draw = function(){
        this.el.setAttribute('r', this.r);
        this.el.setAttribute('stroke-opacity', this.opacity);
    }

}

效果一开始如下面左图是好的,然后切换页面一会回来后就发现变成了右边的样子,全都集中到一块了。

 

 

查了一下资料才发现是因为页面在后台的时候requestAnimationFrame会自动进入暂停状态,这样就导致我计算的动画间隔dt变成非常大,然后就出现了这个问题。

我目前发现有两种方法可以避免出现这种问题:

第一种就是把动画转换成一个按固定时间的周期动画,记录初始位置,然后根据经过的时间计算在周期动画中的进度,进行绘图,代码如下:

function ripple2(id, option={}){
    var svg = document.getElementById(id);
    if(!svg){
        console.log('no has this id');
        return false;
    }
    var n = option['n'] || 5;
    var color = option['color'] || 'black';
    var width = option['width'] || svg.width.baseVal.value;
    var height = option['heght'] || svg.height.baseVal.value;
    var lineWidth = option['lineWidth'] || 2;
    var min_R = option['min_R'] || 10;
    var max_R = option['max_R'] || Math.min(width, height)/2 - lineWidth/2;
    var time = option['time'] || 5000;

    var dt = 0;
    // 记录开始时间
    var start_time = Date.now();
    var rings = [];

    for(var i=0; i<n;i++){
        var r = i*(max_R-min_R)/n + min_R;
        var opacity = 1-i*1/n;
        var circle = new Circle(r, opacity);
        svg.appendChild(circle.el);
        rings.push(circle);
    }


    function run(){
        // 用经过的时间计算动画的结果,这样就不会切换页面后回来出现大间隔导致动画变形
        dt = Date.now() - start_time;
        // 计算在周期动画中的进度
        var pro = (dt % time)/time;
        var dr = pro*(max_R-min_R);
        var dopacity = pro*1;

        for(var i=0; i<rings.length; i++){
            var circle = rings[i];
            circle.draw(dr, dopacity);
        }

        window.requestAnimationFrame(run)
    }

    window.requestAnimationFrame(run)

    function Circle(r, opacity){
        var circle = document.createElementNS("http://www.w3.org/2000/svg","circle");
        circle.setAttribute('cx', width/2);
        circle.setAttribute('cy', height/2);
        circle.setAttribute('r', r);
        circle.setAttribute('stroke', color);
        circle.setAttribute('stroke-width', lineWidth);
        circle.setAttribute('fill', 'none');
        circle.setAttribute('stroke-opacity', opacity);

        this.el = circle;
        this.r = r;
        this.opacity = opacity;
    }
    Circle.prototype.draw = function(dr, dopacity){
        // 根据进度和初始位置绘制图案
        var r = this.r + dr;
        var opacity = this.opacity - dopacity;
        if(r > max_R){
            r = r - max_R + min_R;
            opacity = opacity + 1;
        }
        this.el.setAttribute('r', r);
        this.el.setAttribute('stroke-opacity', opacity);
    }

}

这样动画就不会出现之前的切换页面的问题。

第二种是如果一定要用动画间隔来绘制的话,可以用visibilitychange来监听页面切换的事件来控制动画是否进行和last_time的刷新,

visibilitychange可以参考下这里: 页面可见性

修改后的代码如下:

function ripple3(id, option={}){
    var svg = document.getElementById(id);
    if(!svg){
        console.log('no has this id');
        return false;
    }
    var n = option['n'] || 5;
    var color = option['color'] || 'black';
    var width = option['width'] || svg.width.baseVal.value;
    var height = option['heght'] || svg.height.baseVal.value;
    var lineWidth = option['lineWidth'] || 2;
    var min_R = option['min_R'] || 10;
    var max_R = option['max_R'] || Math.min(width, height)/2 - lineWidth/2;
    var time = option['time'] || 5000;
    

    var dt = 0;
    var last_time = Date.now();
    var rings = [];
    // 方便控制动画
    var loop = null;

    for(var i=0; i<n;i++){
        var r = i*(max_R-min_R)/n + min_R;
        var opacity = 1-i*1/n;
        var circle = new Circle(r, opacity);
        svg.appendChild(circle.el);
        rings.push(circle);
    }


    function run(){
        dt = Date.now() - last_time;
        last_time = Date.now();

        var pro = dt/time;
        var dr = pro*(max_R-min_R);
        var dopacity = pro*1;

        for(var i=0; i<rings.length; i++){
            var circle = rings[i];
            circle.move(dr, dopacity);
            circle.draw();
        }

        loop = window.requestAnimationFrame(run);
    }

    loop = window.requestAnimationFrame(run);

    function Circle(r, opacity){
        var circle = document.createElementNS("http://www.w3.org/2000/svg","circle");
        circle.setAttribute('cx', width/2);
        circle.setAttribute('cy', height/2);
        circle.setAttribute('r', r);
        circle.setAttribute('stroke', color);
        circle.setAttribute('stroke-width', lineWidth);
        circle.setAttribute('fill', 'none');
        circle.setAttribute('stroke-opacity', opacity);

        this.el = circle;
        this.r = r;
        this.opacity = opacity;
    }
    Circle.prototype.move = function(dr, dopacity){
        this.r += dr;
        this.opacity -= dopacity;
        if(this.r > max_R){
            this.r = this.r - max_R + min_R;
            this.opacity = this.opacity + 1; } } Circle.prototype.draw
= function(){ this.el.setAttribute('r', this.r); this.el.setAttribute('stroke-opacity', this.opacity); } document.addEventListener('visibilitychange', function() { if (document.hidden) { // 离开该页面 暂停动画 cancelAnimationFrame(loop); } else { // 打开该页面 更新最后时间,开始动画 last_time = Date.now(); loop = window.requestAnimationFrame(run); } }); }

这两种方法都可以避免出现切换页面导致动画变形,具体要用那种就看情况使用。

posted @ 2018-04-26 17:11  敌塔师  阅读(1370)  评论(0)    收藏  举报