画布动画"放慢速度"向上滚动时迅速下降

时间:2016-05-26 08:31:47

标签: javascript jquery canvas html5-canvas

我正在网站上工作,当你向下滚动一条线时会动画成一个正方形/矩形,效果很好,但有一点让我烦恼可能会有点困难一个要解决但在这里是:

当动画正在播放并且您快速上下滚动(使用视口中的画布)时,您可以看到它减慢了一点点。不知道造成这种情况的原因是,每次元素到达Window中的某个点时,我只会向动画函数发送1次调用。有什么想法吗?

编辑: 更新了链接,只需向下滚动,通过放慢速度我的意思就更清楚了。

请参阅此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)
      });
    }
  });
});
&#13;
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;
}
&#13;
<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>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

你从@ Blindman67和@markE得到了一些很好的建议(他的答案现已删除),关于

  • 不要将setInterval用于动画
  • 限制滚动事件

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

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

应该注意的是,滚动事件可能会根据所使用的设备快速启动,并且通常比屏幕刷新率更快,因此它是需要最优化的事件之一。

这里你在scroll事件中广泛使用jQuery的方法。 $(selector)方法是一种更复杂的方式来调用document.querySelectorAll(),这本身就是一种嘈杂的操作。
而且,对于所有这些画布,你再次调用jQuery的方法来获得它们的大小和位置。要理解为什么它在高速下很糟糕,请记住.height每次调用window.getComputedStyle(elem),它确实返回分配给元素的每个样式,偶尔可能会很好,但不是在你的情况下(&gt;每秒300次通话)。

一个简单的解决方法:将所有这些调用一次并存储在某处不会改变的内容。

您也可以使用requestAnimationFrame 限制它,这是一个使用标志的简单示例:

// 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;
            })
        }
    }

所以这里是使用requestAnimationFrame对代码进行带注释的清理,并避免过多调用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, 
  //	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>