Array Push() 正在丢失由类侦听器添加的元素

问题描述 投票:0回答:1

代码应该连续地绘制一个又一个的形状

class CircleFade
。每个形状应绘制超过 100 个周期,然后应生成一个新形状并在链中重复该过程。类侦听器
setOnCycleEndListener()
用于触发使用
function createShapeWithListener()
创建新形状。

要绘制

CircleFade
和其他形状,
class RenderStack
使用在
RenderStack.shapes
数组中收集渲染的形状。 但是这个数组并没有被类监听器更新(看起来它刚刚被删除了)。但是,当通过
buttonGenerate
单击触发时,相同的操作可以正常工作。

const buttonGenerate = document.getElementById('button-generate');
const buttonAnimate = document.getElementById('button-animate');
const buttonDo = document.getElementById('button-do');
const canvas = document.getElementById('myCanvas');

const ctx = canvas.getContext('2d');

function rndColor() {
  return `rgb(${255 * Math.random()}, ${255 * Math.random()}, ${255 * Math.random()})`;
};

// Shape to draw
class CircleFade {
  constructor(ctx) {
    this.ctx = ctx;
    this.isActive = true;
    this.cycle = 100; // draw only 100 times
    this.x = Math.floor(Math.random() * canvas.width);
    this.y = Math.floor(Math.random() * canvas.height);
    this.onCycleEnd = null; // Листенер на событие конца цикла
  }

  draw() {
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, 10, 0, 2 * Math.PI);
    this.ctx.fillStyle = rndColor();
    this.ctx.fill();
    this.ctx.stroke();

    this.cycle--;
    if (this.cycle <= 0) {
      this.isActive = false;
      if (this.onCycleEnd) {
        this.onCycleEnd(); // Implementing listener
      }
    }
  }

  // Listener setup
  setOnCycleEndListener(callback) {
    this.onCycleEnd = callback;
  }
}

// Class to collect  and draw shapes
class RenderStack {
  constructor(ctx) {
    this.ctx = ctx;
    this.shapes = [];
    this.isPlaying = false;
  }

  add(shape) {
    this.shapes.push(shape);
  }

  start() {
    this.isPlaying = true;
    this.render(); // Начинаем рендеринг
  }

  stop() {
    this.isPlaying = false;
  }

  render() {
    this.ctx.clearRect(0, 0, canvas.width, canvas.height);
    // Update shape list and draw it
    this.shapes = this.shapes.filter((item) => {
      item.draw();
      return item.isActive;
    });

    // Stop the animation if the list i empty
    const length = this.shapes.length;
    if (length <= 0) {
      this.isPlaying = false;
      this.ctx.clearRect(0, 0, canvas.width, canvas.height);
    }

    console.log(`length: ${this.shapes.length}`);

    if (this.isPlaying) {
      requestAnimationFrame(() => this.render());
    }
  }
}

// Create new Shape and adding listener to it
function createShapeWithListener() {
  const shape = new CircleFade(ctx);
  // ---(b.)--- Adding shape by listener
  shape.setOnCycleEndListener(() => {
    console.log(`Cycle ended for shape at (${shape.x}, ${shape.y}), creating a new one`);
    const newShape = createShapeWithListener();
    renderStack.add(newShape);

    if (!renderStack.isPlaying) {
      renderStack.start();
    }
  });
  return shape;
}

const renderStack = new RenderStack(ctx);
// ---(a.)--- Adding shape by button click
buttonGenerate.addEventListener('click', () => {
  renderStack.add(createShapeWithListener());
  if (!renderStack.isPlaying) buttonAnimate.click();
});

buttonAnimate.addEventListener('click', () => {
  renderStack.isPlaying ? renderStack.stop() : renderStack.start();
  buttonAnimate.textContent = renderStack.isPlaying ? 'Stop' : 'Start';
});

// buttonDo.addEventListener('click', () => {
// });
body {
  font-family: Arial, sans-serif;
}

canvas {
  border: 1px solid black;
}

button {
  margin-top: 10px;
}
<canvas id="myCanvas" width="640" height="480"></canvas>
<div style="display: flex;  flex-direction: row; gap: 2px; ">
  <button id='button-generate'>Generate Figure</button>
  <button id="button-animate">Start</button>
  <!-- <button id="button-do">Do</button> -->
</div>

目标是渲染期间修改形状列表。 最好使用侦听器机制(或类似机制)来实现,以允许根据随机生成的场景自发改变处理时间。

javascript arrays event-handling listener requestanimationframe
1个回答
0
投票

render() 中的过滤器代码会破坏一些东西,因为它替换了 addEventListeners 正在使用的 this.shapes 数组

相反,修改现有数组

const buttonGenerate = document.getElementById('button-generate');
const buttonAnimate = document.getElementById('button-animate');
const buttonDo = document.getElementById('button-do');
const canvas = document.getElementById('myCanvas');

const ctx = canvas.getContext('2d');

function rndColor() {
  return `rgb(${255 * Math.random()}, ${255 * Math.random()}, ${255 * Math.random()})`;
};

// Shape to draw
class CircleFade {
  constructor(ctx) {
    this.ctx = ctx;
    this.isActive = true;
    this.cycle = 100; // draw only 100 times
    this.x = Math.floor(Math.random() * canvas.width);
    this.y = Math.floor(Math.random() * canvas.height);
    this.onCycleEnd = null; // Листенер на событие конца цикла
  }

  draw() {
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, 10, 0, 2 * Math.PI);
    this.ctx.fillStyle = rndColor();
    this.ctx.fill();
    this.ctx.stroke();

    this.cycle--;
    if (this.cycle <= 0) {
      this.isActive = false;
      if (this.onCycleEnd) {
        this.onCycleEnd(); // Implementing listener
      }
    }
  }

  // Listener setup
  setOnCycleEndListener(callback) {
    this.onCycleEnd = callback;
  }
}

// Class to collect  and draw shapes
class RenderStack {
  constructor(ctx) {
    this.ctx = ctx;
    this.shapes = [];
    this.isPlaying = false;
  }

  add(shape) {
    this.shapes.push(shape);
  }

  start() {
    this.isPlaying = true;
    this.render(); // Начинаем рендеринг
  }

  stop() {
    this.isPlaying = false;
  }

  render() {
    this.ctx.clearRect(0, 0, canvas.width, canvas.height);
    // Update shape list and draw it
        // EDIT This code breaks things because it replaces the this.shapes array that addEventListeners are using
        // this.shapes = this.shapes.filter((item) => {
        //   item.draw();
        //   return item.isActive;
        // });
        // EDIT Modify the existing array, must work from end to beginning
        for( let next = this.shapes.length - 1; next >= 0; --next )  {
          // First draw it
          this.shapes[next].draw( )
          // Then remove it if it's finished it's itterations
          if( !this.shapes[next].isActive ) this.shapes.splice(next, 1)
        }

    // Stop the animation if the list i empty
    const length = this.shapes.length;
    if (length <= 0) {
      this.isPlaying = false;
      this.ctx.clearRect(0, 0, canvas.width, canvas.height);
    }

    console.log(`length: ${this.shapes.length}`);

    if (this.isPlaying) {
      requestAnimationFrame(() => this.render());
    }
  }
}

// Create new Shape and adding listener to it
function createShapeWithListener() {
  const shape = new CircleFade(ctx);
  // ---(b.)--- Adding shape by listener
  shape.setOnCycleEndListener(() => {
    console.log(`Cycle ended for shape at (${shape.x}, ${shape.y}), creating a new one`);
    const newShape = createShapeWithListener();
    renderStack.add(newShape);

    if (!renderStack.isPlaying) {
      renderStack.start();
    }
  });
  return shape;
}

const renderStack = new RenderStack(ctx);
// ---(a.)--- Adding shape by button click
buttonGenerate.addEventListener('click', () => {
  renderStack.add(createShapeWithListener());
  if (!renderStack.isPlaying) buttonAnimate.click();
});

buttonAnimate.addEventListener('click', () => {
  renderStack.isPlaying ? renderStack.stop() : renderStack.start();
  buttonAnimate.textContent = renderStack.isPlaying ? 'Stop' : 'Start';
});

// buttonDo.addEventListener('click', () => {
// });
body {
  font-family: Arial, sans-serif;
}

canvas {
  border: 1px solid black;
}

button {
  margin-top: 10px;
}
<div style="display: flex;  flex-direction: row; gap: 2px; ">
  <button id='button-generate'>Generate Figure</button>
  <button id="button-animate">Start</button>
</div>
<canvas id="myCanvas" width="440" height="180"></canvas>

© www.soinside.com 2019 - 2024. All rights reserved.