element-portable/src/effects/fireworks/index.ts
David Langley 491f0cd08a
Change license (#13)
* Copyright headers 1

* Licence headers 2

* Copyright Headers 3

* Copyright Headers 4

* Copyright Headers 5

* Copyright Headers 6

* Copyright headers 7

* Add copyright headers for html and config file

* Replace license files and update package.json

* Update with CLA

* lint
2024-09-09 13:57:16 +00:00

175 lines
5.3 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
Copyright 2020 Nurjin Jafar
Copyright 2020 Nordeck IT + Consulting GmbH.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import ICanvasEffect from "../ICanvasEffect";
export type FireworksOptions = {
/**
* max fireworks count
*/
maxCount: number;
/**
* gravity value that firework adds to shift from it's start position
*/
gravity: number;
};
type FireworksParticle = {
/**
* color
*/
color: string;
/**
* x,y are the point where the particle starts to position on canvas
*/
x: number;
y: number;
/**
* vx,vy shift values from x and y
*/
vx: number;
vy: number;
/**
* the alpha opacity of the firework particle (between 0 and 1, where 1 is opaque and 0 is invisible)
*/
alpha: number;
/**
* w,h width and height
*/
w: number;
h: number;
};
export const DefaultOptions: FireworksOptions = {
maxCount: 500,
gravity: 0.05,
};
export default class Fireworks implements ICanvasEffect {
private readonly options: FireworksOptions;
public constructor(options: { [key: string]: any }) {
this.options = { ...DefaultOptions, ...options };
}
private context: CanvasRenderingContext2D | null = null;
private supportsAnimationFrame = window.requestAnimationFrame;
private particles: Array<FireworksParticle> = [];
public isRunning = false;
public start = async (canvas: HTMLCanvasElement, timeout = 3000): Promise<void> => {
if (!canvas) {
return;
}
this.isRunning = true;
this.context = canvas.getContext("2d");
this.supportsAnimationFrame.call(window, this.updateWorld);
if (timeout) {
window.setTimeout(this.stop, timeout);
}
};
private updateWorld = (): void => {
if (!this.isRunning && this.particles.length === 0) return;
this.update();
this.paint();
this.supportsAnimationFrame.call(window, this.updateWorld);
};
private update = (): void => {
if (this.particles.length < this.options.maxCount && this.isRunning) {
this.createFirework();
}
const alive: FireworksParticle[] = [];
for (let i = 0; i < this.particles.length; i++) {
if (this.move(this.particles[i])) {
alive.push(this.particles[i]);
}
}
this.particles = alive;
};
private paint = (): void => {
if (!this.context || !this.context.canvas) return;
this.context.globalCompositeOperation = "destination-out";
this.context.fillStyle = "rgba(0,0,0,0.5)";
this.context.fillRect(0, 0, this.context.canvas.width, this.context.canvas.height);
this.context.globalCompositeOperation = "lighter";
for (let i = 0; i < this.particles.length; i++) {
this.drawParticle(this.particles[i]);
}
};
private createFirework = (): void => {
if (!this.context || !this.context.canvas) return;
const width = this.context.canvas.width;
const height = this.context.canvas.height;
const xPoint = Math.random() * (width - 200) + 100;
const yPoint = Math.random() * (height - 200) + 100;
const nFire = Math.random() * 50 + 100;
const color =
"rgb(" +
~~(Math.random() * 200 + 55) +
"," +
~~(Math.random() * 200 + 55) +
"," +
~~(Math.random() * 200 + 55) +
")";
for (let i = 0; i < nFire; i++) {
const particle = <FireworksParticle>{};
particle.color = color;
particle.w = particle.h = Math.random() * 4 + 1;
particle.x = xPoint - particle.w / 2;
particle.y = yPoint - particle.h / 2;
particle.vx = (Math.random() - 0.5) * 10;
particle.vy = (Math.random() - 0.5) * 10;
particle.alpha = Math.random() * 0.5 + 0.5;
const vy = Math.sqrt(25 - particle.vx * particle.vx);
if (Math.abs(particle.vy) > vy) {
particle.vy = particle.vy > 0 ? vy : -vy;
}
this.particles.push(particle);
}
};
public stop = async (): Promise<void> => {
this.isRunning = false;
};
private drawParticle = (particle: FireworksParticle): void => {
if (!this.context || !this.context.canvas) {
return;
}
this.context.save();
this.context.beginPath();
this.context.translate(particle.x + particle.w / 2, particle.y + particle.h / 2);
this.context.arc(0, 0, particle.w, 0, Math.PI * 2);
this.context.fillStyle = particle.color;
this.context.globalAlpha = particle.alpha;
this.context.closePath();
this.context.fill();
this.context.restore();
};
private move = (particle: FireworksParticle): boolean => {
particle.x += particle.vx;
particle.vy += this.options.gravity;
particle.y += particle.vy;
particle.alpha -= 0.01;
return !(
particle.x <= -particle.w ||
particle.x >= screen.width ||
particle.y >= screen.height ||
particle.alpha <= 0
);
};
}