canvas2d:cancelAnimationFrame 不工作

Posted

技术标签:

【中文标题】canvas2d:cancelAnimationFrame 不工作【英文标题】:canvas2d: cancelAnimationFrame is not working 【发布时间】:2021-10-07 08:07:49 【问题描述】:

问题

一旦蜂巢的每个项目移动到其结束位置(动画通过单击六边形开始),它就会触发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
    
    
  ></canvas>
</body>
</html>

【问题讨论】:

您的变量raf 用于存储两个不同的id,但它显然只能指向其中一个。你将无法清除第一个。但您可能更喜欢存储一个全局布尔标志,并在再次调用 rAF 之前检查该标志是否打开。 【参考方案1】:

实际上问题是您在之后再次调用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;
&lt;canvas width="440" height="440"&gt;&lt;/canvas&gt;

【讨论】:

你完全正确!修复了问题。非常感谢!

以上是关于canvas2d:cancelAnimationFrame 不工作的主要内容,如果未能解决你的问题,请参考以下文章

为啥 javascript canvas2d 剪辑需要路径?

实现Canvas2D绘图 使元素绕中心居中旋转

微信小程序canvas2d绘制圆环进度条组件

视觉高级篇26 # 如何绘制带宽度的曲线?

视觉高级篇26 # 如何绘制带宽度的曲线?

我应该为浏览器应用程序使用什么3D技术? [关闭]