代码应该连续地绘制一个又一个的形状
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>
目标是渲染期间修改形状列表。 最好使用侦听器机制(或类似机制)来实现,以允许根据随机生成的场景自发改变处理时间。
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>