canvas2d: cancelAnimationFrame 不工作
canvas2d: cancelAnimationFrame is not working
问题
一旦蜂巢的每个项目都移动到其结束位置(动画 通过在六边形上单击 开始),它会触发 stop()
。到目前为止这有效。问题是 stop()
在无限循环中被调用(请参阅控制台日志记录 stop
)并且在我看来它不像 cancelAnimationFrame
有效。
目标
一旦蜂巢的每个项目都移动到其结束位置,动画应该停止,因为无论如何都没有任何东西在移动。再次单击后,每个项目都会再次折叠到中间(与打开动画完全相反),有点像切换。
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext("2d");
const hexagonArray = [];
var raf;
const items = [
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
];
class Hexagon {
constructor(heading, subheading, idx) {
this.idx = idx;
this.sixtyDeg = Math.PI * 2 / 6;
this.radius = 80;
this.gap = 1.9;
this.fromX = canvas.width / 2;
this.fromY = canvas.height / 2;
this.toX = this.fromX + this.gap * this.radius * Math.sin(this.sixtyDeg * idx);
this.toY = this.fromY + this.gap * this.radius * Math.cos(this.sixtyDeg * idx);
this.x = this.fromX;
this.y = this.fromY;
this.frames = 100;
this.frame = 0;
this.speedX = this.toX / this.frames;
this.speedY = this.toY / this.frames;
this.isCurrent = false;
this.heading = heading;
this.subheading = subheading;
}
draw() {
const { sixtyDeg, radius, x, y, idx } = this;
// ctx.fillStyle = "red";
// ctx.fillText(`no.${idx}`, x, y);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
ctx.lineTo(
x + radius * Math.cos(sixtyDeg * i),
y + radius * Math.sin(sixtyDeg * i)
);
}
ctx.closePath();
ctx.fill();
}
update(shouldOpen) {
this.x = expEaseInOut(
this.frame,
this.fromX,
this.toX - this.fromX,
this.frames
);
this.y = expEaseInOut(
this.frame,
this.fromY,
this.toY - this.fromY,
this.frames
);
if (
(shouldOpen ? this.frame < this.frames : this.frame > this.frames)
&& this.idx !== 0
) {
this.frame = shouldOpen
? this.frame + 1 // open
: this.frame - 1; // close
} else if (this.idx !== 0) {
stop();
}
this.draw();
}
toggle() {
let toggle = false;
this.update(!toggle);
toggle = !toggle;
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
raf = window.requestAnimationFrame(animate);
}
function stop() {
console.log('stop');
window.cancelAnimationFrame(raf);
}
canvas.addEventListener('click', function (event) {
const {
clientX,
clientY
} = event;
hexagonArray.some((hexagon) => {
const isInRange = (
inRange(clientX, hexagon.x - hexagon.radius, hexagon.x + hexagon.radius)
&& inRange(clientY, hexagon.y - hexagon.radius, hexagon.y + hexagon.radius)
);
if (isInRange) {
raf = window.requestAnimationFrame(animate);
return true;
}
});
});
function init() {
items.forEach(({ heading, subheading }, idx) => {
hexagonArray.push(new Hexagon(heading, subheading, idx));
});
hexagonArray.forEach((hexagon) => {
hexagon.draw();
});
}
init();
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
init();
});
function expEaseInOut(frame, initPos, distance, frames) {
frame /= frames / 2;
if (frame < 1) return distance/2 * Math.pow( 2, 10 * (frame - 1) ) + initPos;
frame--;
return distance/2 * ( -Math.pow( 2, -10 * frame) + 2 ) + initPos;
};
function inRange(n, min, max) {
return ((n-min)*(n-max) <= 0);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script async src="index.js"></script>
<title>canvas2d</title>
</head>
<body>
<style>
body {
min-width: 100vw;
min-height: 100vh;
background-color: lightgray;
}
canvas {
background-color: gray;
}
</style>
<canvas
width="440"
height="440"
></canvas>
</body>
</html>
实际上问题是您在之后再次调用 requestAnimationFrame,因此它一直在循环。
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// You're updating the 6 hexagons & calling "cancelAnimationFrame" in here
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
// BUT... you call the animation again just after, so it keeps looping
raf = window.requestAnimationFrame(animate);
}
如果在更新逻辑和绘图之前调用动画,它不会循环
function animate() {
raf = window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
}
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext("2d");
const hexagonArray = [];
let raf;
const items = [
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
];
class Hexagon {
constructor(heading, subheading, idx) {
this.idx = idx;
this.sixtyDeg = Math.PI * 2 / 6;
this.radius = 80;
this.gap = 1.9;
this.fromX = canvas.width / 2;
this.fromY = canvas.height / 2;
this.toX = this.fromX + this.gap * this.radius * Math.sin(this.sixtyDeg * idx);
this.toY = this.fromY + this.gap * this.radius * Math.cos(this.sixtyDeg * idx);
this.x = this.fromX;
this.y = this.fromY;
this.frames = 100;
this.frame = 0;
this.speedX = this.toX / this.frames;
this.speedY = this.toY / this.frames;
this.isCurrent = false;
this.heading = heading;
this.subheading = subheading;
}
draw() {
const { sixtyDeg, radius, x, y, idx } = this;
// ctx.fillStyle = "red";
// ctx.fillText(`no.${idx}`, x, y);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
ctx.lineTo(
x + radius * Math.cos(sixtyDeg * i),
y + radius * Math.sin(sixtyDeg * i)
);
}
ctx.closePath();
ctx.fill();
}
update(shouldOpen) {
this.x = expEaseInOut(
this.frame,
this.fromX,
this.toX - this.fromX,
this.frames
);
this.y = expEaseInOut(
this.frame,
this.fromY,
this.toY - this.fromY,
this.frames
);
if (
(shouldOpen ? this.frame < this.frames : this.frame > this.frames)
&& this.idx !== 0
) {
this.frame = shouldOpen
? this.frame + 1 // open
: this.frame - 1; // close
} else if (this.idx !== 0) {
stop();
}
this.draw();
}
toggle() {
let toggle = false;
this.update(!toggle);
toggle = !toggle;
}
}
function animate() {
raf = window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
}
function stop() {
console.log('stop');
window.cancelAnimationFrame(raf);
}
canvas.addEventListener('click', function (event) {
const {
clientX,
clientY
} = event;
hexagonArray.some((hexagon) => {
const isInRange = (
inRange(clientX, hexagon.x - hexagon.radius, hexagon.x + hexagon.radius)
&& inRange(clientY, hexagon.y - hexagon.radius, hexagon.y + hexagon.radius)
);
if (isInRange) {
raf = window.requestAnimationFrame(animate);
return true;
}
});
});
function init() {
items.forEach(({ heading, subheading }, idx) => {
hexagonArray.push(new Hexagon(heading, subheading, idx));
});
hexagonArray.forEach((hexagon) => {
hexagon.draw();
});
}
init();
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
init();
});
function expEaseInOut(frame, initPos, distance, frames) {
frame /= frames / 2;
if (frame < 1) return distance/2 * Math.pow( 2, 10 * (frame - 1) ) + initPos;
frame--;
return distance/2 * ( -Math.pow( 2, -10 * frame) + 2 ) + initPos;
};
function inRange(n, min, max) {
return ((n-min)*(n-max) <= 0);
}
body {
min-width: 100vw;
min-height: 100vh;
background-color: lightgray;
}
canvas {
background-color: gray;
}
<canvas width="440" height="440"></canvas>
问题
一旦蜂巢的每个项目都移动到其结束位置(动画 通过在六边形上单击 开始),它会触发 stop()
。到目前为止这有效。问题是 stop()
在无限循环中被调用(请参阅控制台日志记录 stop
)并且在我看来它不像 cancelAnimationFrame
有效。
目标
一旦蜂巢的每个项目都移动到其结束位置,动画应该停止,因为无论如何都没有任何东西在移动。再次单击后,每个项目都会再次折叠到中间(与打开动画完全相反),有点像切换。
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext("2d");
const hexagonArray = [];
var raf;
const items = [
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
];
class Hexagon {
constructor(heading, subheading, idx) {
this.idx = idx;
this.sixtyDeg = Math.PI * 2 / 6;
this.radius = 80;
this.gap = 1.9;
this.fromX = canvas.width / 2;
this.fromY = canvas.height / 2;
this.toX = this.fromX + this.gap * this.radius * Math.sin(this.sixtyDeg * idx);
this.toY = this.fromY + this.gap * this.radius * Math.cos(this.sixtyDeg * idx);
this.x = this.fromX;
this.y = this.fromY;
this.frames = 100;
this.frame = 0;
this.speedX = this.toX / this.frames;
this.speedY = this.toY / this.frames;
this.isCurrent = false;
this.heading = heading;
this.subheading = subheading;
}
draw() {
const { sixtyDeg, radius, x, y, idx } = this;
// ctx.fillStyle = "red";
// ctx.fillText(`no.${idx}`, x, y);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
ctx.lineTo(
x + radius * Math.cos(sixtyDeg * i),
y + radius * Math.sin(sixtyDeg * i)
);
}
ctx.closePath();
ctx.fill();
}
update(shouldOpen) {
this.x = expEaseInOut(
this.frame,
this.fromX,
this.toX - this.fromX,
this.frames
);
this.y = expEaseInOut(
this.frame,
this.fromY,
this.toY - this.fromY,
this.frames
);
if (
(shouldOpen ? this.frame < this.frames : this.frame > this.frames)
&& this.idx !== 0
) {
this.frame = shouldOpen
? this.frame + 1 // open
: this.frame - 1; // close
} else if (this.idx !== 0) {
stop();
}
this.draw();
}
toggle() {
let toggle = false;
this.update(!toggle);
toggle = !toggle;
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
raf = window.requestAnimationFrame(animate);
}
function stop() {
console.log('stop');
window.cancelAnimationFrame(raf);
}
canvas.addEventListener('click', function (event) {
const {
clientX,
clientY
} = event;
hexagonArray.some((hexagon) => {
const isInRange = (
inRange(clientX, hexagon.x - hexagon.radius, hexagon.x + hexagon.radius)
&& inRange(clientY, hexagon.y - hexagon.radius, hexagon.y + hexagon.radius)
);
if (isInRange) {
raf = window.requestAnimationFrame(animate);
return true;
}
});
});
function init() {
items.forEach(({ heading, subheading }, idx) => {
hexagonArray.push(new Hexagon(heading, subheading, idx));
});
hexagonArray.forEach((hexagon) => {
hexagon.draw();
});
}
init();
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
init();
});
function expEaseInOut(frame, initPos, distance, frames) {
frame /= frames / 2;
if (frame < 1) return distance/2 * Math.pow( 2, 10 * (frame - 1) ) + initPos;
frame--;
return distance/2 * ( -Math.pow( 2, -10 * frame) + 2 ) + initPos;
};
function inRange(n, min, max) {
return ((n-min)*(n-max) <= 0);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script async src="index.js"></script>
<title>canvas2d</title>
</head>
<body>
<style>
body {
min-width: 100vw;
min-height: 100vh;
background-color: lightgray;
}
canvas {
background-color: gray;
}
</style>
<canvas
width="440"
height="440"
></canvas>
</body>
</html>
实际上问题是您在之后再次调用 requestAnimationFrame,因此它一直在循环。
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// You're updating the 6 hexagons & calling "cancelAnimationFrame" in here
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
// BUT... you call the animation again just after, so it keeps looping
raf = window.requestAnimationFrame(animate);
}
如果在更新逻辑和绘图之前调用动画,它不会循环
function animate() {
raf = window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
}
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext("2d");
const hexagonArray = [];
let raf;
const items = [
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
{
heading: "Lorem Ipsum",
subheading: "dolor sit amet",
},
];
class Hexagon {
constructor(heading, subheading, idx) {
this.idx = idx;
this.sixtyDeg = Math.PI * 2 / 6;
this.radius = 80;
this.gap = 1.9;
this.fromX = canvas.width / 2;
this.fromY = canvas.height / 2;
this.toX = this.fromX + this.gap * this.radius * Math.sin(this.sixtyDeg * idx);
this.toY = this.fromY + this.gap * this.radius * Math.cos(this.sixtyDeg * idx);
this.x = this.fromX;
this.y = this.fromY;
this.frames = 100;
this.frame = 0;
this.speedX = this.toX / this.frames;
this.speedY = this.toY / this.frames;
this.isCurrent = false;
this.heading = heading;
this.subheading = subheading;
}
draw() {
const { sixtyDeg, radius, x, y, idx } = this;
// ctx.fillStyle = "red";
// ctx.fillText(`no.${idx}`, x, y);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
ctx.lineTo(
x + radius * Math.cos(sixtyDeg * i),
y + radius * Math.sin(sixtyDeg * i)
);
}
ctx.closePath();
ctx.fill();
}
update(shouldOpen) {
this.x = expEaseInOut(
this.frame,
this.fromX,
this.toX - this.fromX,
this.frames
);
this.y = expEaseInOut(
this.frame,
this.fromY,
this.toY - this.fromY,
this.frames
);
if (
(shouldOpen ? this.frame < this.frames : this.frame > this.frames)
&& this.idx !== 0
) {
this.frame = shouldOpen
? this.frame + 1 // open
: this.frame - 1; // close
} else if (this.idx !== 0) {
stop();
}
this.draw();
}
toggle() {
let toggle = false;
this.update(!toggle);
toggle = !toggle;
}
}
function animate() {
raf = window.requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
hexagonArray.forEach((hexagon) => {
hexagon.toggle();
});
}
function stop() {
console.log('stop');
window.cancelAnimationFrame(raf);
}
canvas.addEventListener('click', function (event) {
const {
clientX,
clientY
} = event;
hexagonArray.some((hexagon) => {
const isInRange = (
inRange(clientX, hexagon.x - hexagon.radius, hexagon.x + hexagon.radius)
&& inRange(clientY, hexagon.y - hexagon.radius, hexagon.y + hexagon.radius)
);
if (isInRange) {
raf = window.requestAnimationFrame(animate);
return true;
}
});
});
function init() {
items.forEach(({ heading, subheading }, idx) => {
hexagonArray.push(new Hexagon(heading, subheading, idx));
});
hexagonArray.forEach((hexagon) => {
hexagon.draw();
});
}
init();
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
/** -------------------------------------------------------------------------------- */
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
init();
});
function expEaseInOut(frame, initPos, distance, frames) {
frame /= frames / 2;
if (frame < 1) return distance/2 * Math.pow( 2, 10 * (frame - 1) ) + initPos;
frame--;
return distance/2 * ( -Math.pow( 2, -10 * frame) + 2 ) + initPos;
};
function inRange(n, min, max) {
return ((n-min)*(n-max) <= 0);
}
body {
min-width: 100vw;
min-height: 100vh;
background-color: lightgray;
}
canvas {
background-color: gray;
}
<canvas width="440" height="440"></canvas>