画布动画“放慢速度”快速向上滚动时向下滚动
我正在制作一个网站,当您向下滚动一条直线时会动画成一个正方形/矩形,效果非常好,但有一件事情可能会让我感到困扰这是一个很难解决的问题,但这里是:画布动画“放慢速度”快速向上滚动时向下滚动
当动画播放并且快速上下滚动(使用视口中的画布)时,您可以看到它稍微减慢。不知道是什么原因造成的,每当元素到达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>
你从@ 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>
唐'使用'setInterval'是放缓的原因。 – Blindman67
'requestAnimationFrame'是要走的路,或者你可以建议替代品吗? – LVDM
requestAnimationFrame是如何计时的任何动画, – Blindman67