在用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); } }); }
这两种方法都可以避免出现切换页面导致动画变形,具体要用那种就看情况使用。

浙公网安备 33010602011771号