2016-05-26 238 views
0

我正在制作一个网站,当您向下滚动一条直线时会动画成一个正方形/矩形,效果非常好,但有一件事情可能会让我感到困扰这是一个很难解决的问题,但这里是:画布动画“放慢速度”快速向上滚动时向下滚动

当动画播放并且快速上下滚动(使用视口中的画布)时,您可以看到它稍微减慢。不知道是什么原因造成的,每当元素到达Window中的某个点时,我只发送一个动画给动画函数。有任何想法吗?

编辑: 更新了链接,只需向下滚动,就会更加清楚我的意思是减慢速度。

请参阅此fiddle或更低版本中的工作示例。

function createBorderAnimation(elm) { 
 

 
    console.log(elm); 
 

 
    var canvas = elm.get(0), 
 
    ctx = canvas.getContext('2d'); 
 

 
    canvas.width = $(window).width()/4 * 3; 
 
    canvas.height = $(window).height()/2; 
 

 
    var Point = function(x, y) { 
 
    this.x = x; 
 
    this.y = y; 
 
    }; 
 
    var points = [ 
 
    new Point(0, 0), 
 
    new Point(canvas.width, 0), 
 
    new Point(canvas.width, canvas.height), 
 
    new Point(0, canvas.height), 
 
    new Point(0, -10), 
 
    ]; 
 

 
    function calcWaypoints(vertices) { 
 
    var waypoints = []; 
 
    for (var i = 1; i < vertices.length; i++) { 
 
     var pt0 = vertices[i - 1]; 
 
     var pt1 = vertices[i]; 
 
     var dx = pt1.x - pt0.x; 
 
     var dy = pt1.y - pt0.y; 
 
     for (var j = 0; j < 50; j++) { 
 
     var x = pt0.x + dx * j/50; 
 
     var y = pt0.y + dy * j/50; 
 
     waypoints.push({ 
 
      x: x, 
 
      y: y 
 
     }); 
 
     } 
 
    } 
 
    return (waypoints); 
 
    } 
 

 
    var wayPoints = calcWaypoints(points); 
 

 
    ctx.strokeStyle = "rgba(0,0,0,0.5)"; 
 
    ctx.lineWidth = 5; 
 
    ctx.moveTo(points[0].x, points[0].y); 
 
    ctx.globalCompositeOperation = 'destination-atop'; 
 

 
    var counter = 1, 
 
    inter = setInterval(function() { 
 
     var point = wayPoints[counter++]; 
 
     ctx.lineTo(point.x, point.y); 
 
     ctx.stroke(); 
 

 
     if (counter >= wayPoints.length) { 
 
     clearInterval(inter); 
 
     $(canvas).parent().addClass('active'); 
 
     } 
 
    }, 1); 
 
    ctx.stroke(); 
 
} 
 
$(window).scroll(function() { 
 
    var st = $(window).scrollTop(); 
 
    $('canvas').each(function(key, elm) { 
 
    var _this = $(elm); 
 
    if (st > _this.offset().top - $(window).height()) { 
 
     if (!_this.hasClass('animating')) { 
 
     _this.addClass('animating'); 
 
     createBorderAnimation(_this); 
 
     } 
 
     TweenLite.set(_this, { 
 
     y: -(st - _this.offset().top)/(1.5 * 10) 
 
     }); 
 
    } 
 
    }); 
 
});
canvas { 
 
    width: 35vw; 
 
    height: 50vh; 
 
    display: inline-block; 
 
    vertical-align: top; 
 
} 
 

 
canvas:first-child { 
 
    margin: 0 5vw 0 0; 
 
} 
 
div { 
 
    width: 75vw; 
 
    height: 50vh; 
 
    margin: 100px auto; 
 
    font-size: 0; 
 
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div>

+0

唐'使用'setInterval'是放缓的原因。 – Blindman67

+0

'requestAnimationFrame'是要走的路,或者你可以建议替代品吗? – LVDM

+0

requestAnimationFrame是如何计时的任何动画, – Blindman67

回答

1

你从@ Blindman67和@markE(他的回答是现已删除)一些好的建议,关于

  • 不使用setInterval为动画
  • 限制您的滚动事件

我还会补充一点,你应该尽可能避免初始化事物(初始化一次,然后重用),并小心使用jQuery。

关于setInterval,这只是糟糕的,不精确的,并且可能导致浏览器在调用间隔太小时崩溃(这只是提示浏览器在间隔过去之后必须执行某些操作,所以如果某些操作需要更长时间比间隔,它会继续添加事情要做,并将尝试执行它们,阻止所有其他资源,最后只是崩溃......)

应该指出的是,滚动事件可能会触发真正快速取决于在使用的设备上,通常只比你的屏幕刷新速率快,所以它是需要最优化的事件之一。

在这里,您正在做一些广泛的jQuery方法的使用,在滚动事件中。 $(selector)方法是调用document.querySelectorAll()更复杂的方式,它本身已经是一种大声的操作。
此外,对于所有这些画布,您再次调用jQuery的方法来获取它们的大小和位置。要理解为什么它的速度很差,请记住,.height每次都会调用window.getComputedStyle(elem),这会返回分配给您的元素的每种样式,但它可能会很好,但在您的情况下不会(每秒调用300次)。

一个简单的解决方法:调用所有这些,并存储什么地方不会改变。

您还可以使用​​来油门吧,这里是使用标志一个简单的例子:

// our flag 
var scrolling; 
window.onscroll=function(){ 
    // only if we haven't already stacked our function 
    if(!scrolling){ 
     // raise our flag 
     scrolling = true 
     requestAnimationFrame(function(){ 
      // add the callback function 
      doSomething(); 
      // release the flag for next frame 
      scrolling = false; 
      }) 
     } 
    } 

因此,这里是你的代码的注释清理,使用​​,避免太多次调用jQuery的方法。它当然可以进一步优化和清理,但我希望你能得到主要想法。

var BorderAnimation = function() { 
 
    // a global array for all our animations 
 
    var list = []; 
 

 
    // init one animation per canvas 
 
    var initAnim = function() { 
 
    // our canvas 
 
    var c = this; 
 
    // our animation object 
 
    var a = {}; 
 
    a.ctx = c.getContext('2d'); 
 
    // a function to check if our canvas is visible in the screen 
 
    a.isVisible = function(min, max) { 
 
     return min < c.offsetTop + a.height && max > c.offsetTop; 
 
    }; 
 
    // a trigger 
 
    a.play = function() { 
 
     // fire only if we're not already animating, and if we're not already finished 
 
     if (!a.playing && !a.ended) { 
 
     a.playing = true; 
 
     loop(); 
 
     } 
 
    }; 
 
    // reverse trigger 
 
    a.pause = function() { 
 
     a.playing = false; 
 
     } 
 
     // our looping function 
 
    var loop = function() { 
 
     // perform our drawing operations 
 
     a.draw(a.ctx); 
 
     // and only if we should still be playing... 
 
     if (a.playing) { 
 
     //...loop 
 
     requestAnimationFrame(loop); 
 
     } 
 
    }; 
 
    // init our canvas' size and store it in our anim object 
 
    a.width = c.width = $(window).width()/4 * 3; 
 
    a.height = c.height = $(window).height()/2; 
 
    // initialize the drawings for this animation 
 
    initDrawing(a); 
 
    // push our animation in the global array 
 
    list.push(a); 
 
    }; 
 

 
    // this does need to be made outside, but I feel it's cleaner to separate it, 
 
    // \t and if I'm not wrong, it should avoid declaring these functions in the loop. 
 
    var initDrawing = function(anim) { 
 

 
    var ctx = anim.ctx; 
 

 
    var Point = function(x, y) { 
 
     this.x = x; 
 
     this.y = y; 
 
    }; 
 
    var points = [ 
 
     new Point(0, 0), 
 
     new Point(anim.width, 0), 
 
     new Point(anim.width, anim.height), 
 
     new Point(0, anim.height), 
 
     new Point(0, -10), 
 
    ]; 
 

 
    function calcWaypoints(vertices) { 
 
     var waypointsList = []; 
 
     for (var i = 1; i < vertices.length; i++) { 
 
     var pt0 = vertices[i - 1]; 
 
     var pt1 = vertices[i]; 
 
     var dx = pt1.x - pt0.x; 
 
     var dy = pt1.y - pt0.y; 
 
     for (var j = 0; j < 50; j++) { 
 
      var x = pt0.x + dx * j/50; 
 
      var y = pt0.y + dy * j/50; 
 
      waypointsList.push({ 
 
      x: x, 
 
      y: y 
 
      }); 
 
     } 
 
     } 
 
     return (waypointsList); 
 
    } 
 

 
    var wayPoints = calcWaypoints(points); 
 

 
    ctx.strokeStyle = "rgba(0,0,0,0.5)"; 
 
    ctx.lineWidth = 5; 
 
    ctx.globalCompositeOperation = 'destination-atop'; 
 
    // always better to add this, e.g if you plan to make a `reset` function 
 
    anim.ctx.beginPath(); 
 
    anim.ctx.moveTo(points[0].x, points[0].y); 
 

 
    var counter = 1; 
 
    // store into our drawing object the drawing operations 
 
    anim.draw = function() { 
 
     // we reached the end 
 
     if (counter >= wayPoints.length) { 
 
     anim.playing = false; 
 
     anim.ended = true; 
 
     return; 
 
     } 
 

 
     var point = wayPoints[counter++]; 
 

 
     ctx.lineTo(point.x, point.y); 
 
     // ctx.clearRect(0,0,anim.width, anim.height); // don't you need it ??? 
 
     ctx.stroke(); 
 
    } 
 
    }; 
 
    // a single call to the DOM 
 
    $('canvas').each(initAnim); 
 

 
    var scrolling; 
 
    var checkPos = function() { 
 
    // release the flag 
 
    scrolling = false; 
 
    var st = pageYOffset; // it's just the same as $(window).scrollTop(); 
 
    // loop over our list of animations 
 
    list.forEach(function(a) { 
 
     // the canvas is in the screen 
 
     if (a.isVisible(st, st + window.innerHeight)) { 
 
     // it will be blocked if already playing 
 
     a.play(); 
 
     } else { 
 
     // stop the loop 
 
     a.pause(); 
 
     } 
 
    }); 
 
    }; 
 
    $(window).scroll(function() { 
 
    if (!scrolling) { 
 
     // set the flag 
 
     scrolling = true; 
 
     requestAnimationFrame(checkPos); 
 
    } 
 
    }); 
 
    // if you want to do something with these animations, e.g debugging 
 
    return list; 
 
}();
canvas { 
 
    width: 35vw; 
 
    height: 50vh; 
 
    display: inline-block; 
 
    vertical-align: top; 
 
} 
 
canvas:first-child { 
 
    margin: 0 5vw 0 0; 
 
} 
 
div { 
 
    width: 75vw; 
 
    height: 50vh; 
 
    margin: 100px auto; 
 
    font-size: 0; 
 
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div> 
 
<div> 
 
    <canvas></canvas> 
 
    <canvas></canvas> 
 
</div>

+0

令人惊叹!感谢您输入的时间,昨天我已经基本做了与上面相同的事情,只创建一次路标,但仍然有间隔,但这种方式非常流畅,完美地工作,并将继续以这种方式使用它!你是对的我可能应该使用更多的本地JavaScript,再次感谢很多! – LVDM

+0

@LVDM,不客气。另外请注意,我并没有说你不应该使用jQuery,但要记住,每一个方法的调用都是一个影响,所以作为一般性的建议,将存储每个从总是返回相同值的方法返回的值(它也是申请香草) – Kaiido

+0

知道了谢谢! – LVDM