The code should draw shapes class CircleFade
one after another continuously. Each shape should be drawn over 100 cycles, then a new shape should be generated and the process repeated in the chain. A class listener setOnCycleEndListener()
is used to trigger the creation of new shapes with function createShapeWithListener()
.
To draw CircleFade
and other shapes either class RenderStack
used collecting rendered shapes in RenderStack.shapes
array. But this array does not updated by the class listener (it seems like it just dropped). However, the same operation works correctly when triggered by a buttonGenerate
click.
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>
The target is modification shapes list during rendering. Desirable to do it with listener mechanism (or similar) to allow spontaneous changes in time of processing based on randomly generated scenarios.
Your filter code in render() breaks things because it replaces the this.shapes array that addEventListeners are using
Instead, modify the existing array
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);
}
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>