427 lines
16 KiB
JavaScript
427 lines
16 KiB
JavaScript
import { clear, drawParticle, drawParticlePlugin, drawPlugin, paintBase, paintImage } from "../Utils/CanvasUtils";
|
|
import { deepExtend, isSsr } from "../Utils/Utils";
|
|
import { getStyleFromHsl, getStyleFromRgb, rangeColorToHsl, rangeColorToRgb } from "../Utils/ColorUtils";
|
|
import { generatedAttribute } from "./Utils/Constants";
|
|
function setTransformValue(factor, newFactor, key) {
|
|
var _a;
|
|
const newValue = newFactor[key];
|
|
if (newValue !== undefined) {
|
|
factor[key] = ((_a = factor[key]) !== null && _a !== void 0 ? _a : 1) * newValue;
|
|
}
|
|
}
|
|
export class Canvas {
|
|
constructor(container) {
|
|
this.container = container;
|
|
this.size = {
|
|
height: 0,
|
|
width: 0,
|
|
};
|
|
this._context = null;
|
|
this._generated = false;
|
|
this._preDrawUpdaters = [];
|
|
this._postDrawUpdaters = [];
|
|
this._resizePlugins = [];
|
|
this._colorPlugins = [];
|
|
this._mutationObserver =
|
|
!isSsr() && typeof MutationObserver !== "undefined"
|
|
? new MutationObserver((records) => {
|
|
for (const record of records) {
|
|
if (record.type === "attributes" && record.attributeName === "style") {
|
|
this._repairStyle();
|
|
}
|
|
}
|
|
})
|
|
: undefined;
|
|
}
|
|
get _fullScreen() {
|
|
return this.container.actualOptions.fullScreen.enable;
|
|
}
|
|
clear() {
|
|
const options = this.container.actualOptions, trail = options.particles.move.trail, trailFill = this._trailFill;
|
|
if (options.backgroundMask.enable) {
|
|
this.paint();
|
|
}
|
|
else if (trail.enable && trail.length > 0 && trailFill) {
|
|
if (trailFill.color) {
|
|
this._paintBase(getStyleFromRgb(trailFill.color, trailFill.opacity));
|
|
}
|
|
else if (trailFill.image) {
|
|
this._paintImage(trailFill.image, trailFill.opacity);
|
|
}
|
|
}
|
|
else {
|
|
this.draw((ctx) => {
|
|
clear(ctx, this.size);
|
|
});
|
|
}
|
|
}
|
|
destroy() {
|
|
var _a, _b;
|
|
(_a = this._mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
if (this._generated) {
|
|
(_b = this.element) === null || _b === void 0 ? void 0 : _b.remove();
|
|
}
|
|
else {
|
|
this._resetOriginalStyle();
|
|
}
|
|
this.draw((ctx) => {
|
|
clear(ctx, this.size);
|
|
});
|
|
this._preDrawUpdaters = [];
|
|
this._postDrawUpdaters = [];
|
|
this._resizePlugins = [];
|
|
this._colorPlugins = [];
|
|
}
|
|
draw(cb) {
|
|
if (!this._context) {
|
|
return;
|
|
}
|
|
return cb(this._context);
|
|
}
|
|
drawParticle(particle, delta) {
|
|
var _a;
|
|
if (particle.spawning || particle.destroyed) {
|
|
return;
|
|
}
|
|
const radius = particle.getRadius();
|
|
if (radius <= 0) {
|
|
return;
|
|
}
|
|
const pfColor = particle.getFillColor(), psColor = (_a = particle.getStrokeColor()) !== null && _a !== void 0 ? _a : pfColor;
|
|
let [fColor, sColor] = this._getPluginParticleColors(particle);
|
|
if (!fColor) {
|
|
fColor = pfColor;
|
|
}
|
|
if (!sColor) {
|
|
sColor = psColor;
|
|
}
|
|
if (!fColor && !sColor) {
|
|
return;
|
|
}
|
|
this.draw((ctx) => {
|
|
var _a, _b, _c, _d;
|
|
const options = this.container.actualOptions, zIndexOptions = particle.options.zIndex, zOpacityFactor = (1 - particle.zIndexFactor) ** zIndexOptions.opacityRate, opacity = (_c = (_a = particle.bubble.opacity) !== null && _a !== void 0 ? _a : (_b = particle.opacity) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : 1, strokeOpacity = (_d = particle.strokeOpacity) !== null && _d !== void 0 ? _d : opacity, zOpacity = opacity * zOpacityFactor, zStrokeOpacity = strokeOpacity * zOpacityFactor, transform = {}, colorStyles = {
|
|
fill: fColor ? getStyleFromHsl(fColor, zOpacity) : undefined,
|
|
};
|
|
colorStyles.stroke = sColor ? getStyleFromHsl(sColor, zStrokeOpacity) : colorStyles.fill;
|
|
this._applyPreDrawUpdaters(ctx, particle, radius, zOpacity, colorStyles, transform);
|
|
drawParticle({
|
|
container: this.container,
|
|
context: ctx,
|
|
particle,
|
|
delta,
|
|
colorStyles,
|
|
backgroundMask: options.backgroundMask.enable,
|
|
composite: options.backgroundMask.composite,
|
|
radius: radius * (1 - particle.zIndexFactor) ** zIndexOptions.sizeRate,
|
|
opacity: zOpacity,
|
|
shadow: particle.options.shadow,
|
|
transform,
|
|
});
|
|
this._applyPostDrawUpdaters(particle);
|
|
});
|
|
}
|
|
drawParticlePlugin(plugin, particle, delta) {
|
|
this.draw((ctx) => {
|
|
drawParticlePlugin(ctx, plugin, particle, delta);
|
|
});
|
|
}
|
|
drawPlugin(plugin, delta) {
|
|
this.draw((ctx) => {
|
|
drawPlugin(ctx, plugin, delta);
|
|
});
|
|
}
|
|
async init() {
|
|
var _a;
|
|
this.resize();
|
|
this._initStyle();
|
|
this._initCover();
|
|
try {
|
|
await this._initTrail();
|
|
}
|
|
catch (e) {
|
|
console.error(e);
|
|
}
|
|
this.initBackground();
|
|
if (this.element) {
|
|
(_a = this._mutationObserver) === null || _a === void 0 ? void 0 : _a.observe(this.element, { attributes: true });
|
|
}
|
|
this.initUpdaters();
|
|
this.initPlugins();
|
|
this.paint();
|
|
}
|
|
initBackground() {
|
|
const options = this.container.actualOptions, background = options.background, element = this.element, elementStyle = element === null || element === void 0 ? void 0 : element.style;
|
|
if (!elementStyle) {
|
|
return;
|
|
}
|
|
if (background.color) {
|
|
const color = rangeColorToRgb(background.color);
|
|
elementStyle.backgroundColor = color ? getStyleFromRgb(color, background.opacity) : "";
|
|
}
|
|
else {
|
|
elementStyle.backgroundColor = "";
|
|
}
|
|
elementStyle.backgroundImage = background.image || "";
|
|
elementStyle.backgroundPosition = background.position || "";
|
|
elementStyle.backgroundRepeat = background.repeat || "";
|
|
elementStyle.backgroundSize = background.size || "";
|
|
}
|
|
initPlugins() {
|
|
this._resizePlugins = [];
|
|
for (const [, plugin] of this.container.plugins) {
|
|
if (plugin.resize) {
|
|
this._resizePlugins.push(plugin);
|
|
}
|
|
if (plugin.particleFillColor || plugin.particleStrokeColor) {
|
|
this._colorPlugins.push(plugin);
|
|
}
|
|
}
|
|
}
|
|
initUpdaters() {
|
|
this._preDrawUpdaters = [];
|
|
this._postDrawUpdaters = [];
|
|
for (const updater of this.container.particles.updaters) {
|
|
if (updater.afterDraw) {
|
|
this._postDrawUpdaters.push(updater);
|
|
}
|
|
if (updater.getColorStyles || updater.getTransformValues || updater.beforeDraw) {
|
|
this._preDrawUpdaters.push(updater);
|
|
}
|
|
}
|
|
}
|
|
loadCanvas(canvas) {
|
|
var _a, _b;
|
|
if (this._generated) {
|
|
(_a = this.element) === null || _a === void 0 ? void 0 : _a.remove();
|
|
}
|
|
this._generated =
|
|
canvas.dataset && generatedAttribute in canvas.dataset
|
|
? canvas.dataset[generatedAttribute] === "true"
|
|
: this._generated;
|
|
this.element = canvas;
|
|
this.element.ariaHidden = "true";
|
|
this._originalStyle = deepExtend({}, this.element.style);
|
|
this.size.height = canvas.offsetHeight;
|
|
this.size.width = canvas.offsetWidth;
|
|
this._context = this.element.getContext("2d");
|
|
(_b = this._mutationObserver) === null || _b === void 0 ? void 0 : _b.observe(this.element, { attributes: true });
|
|
this.container.retina.init();
|
|
this.initBackground();
|
|
}
|
|
paint() {
|
|
const options = this.container.actualOptions;
|
|
this.draw((ctx) => {
|
|
if (options.backgroundMask.enable && options.backgroundMask.cover) {
|
|
clear(ctx, this.size);
|
|
this._paintBase(this._coverColorStyle);
|
|
}
|
|
else {
|
|
this._paintBase();
|
|
}
|
|
});
|
|
}
|
|
resize() {
|
|
if (!this.element) {
|
|
return;
|
|
}
|
|
const container = this.container, pxRatio = container.retina.pixelRatio, size = container.canvas.size, newSize = {
|
|
width: this.element.offsetWidth * pxRatio,
|
|
height: this.element.offsetHeight * pxRatio,
|
|
};
|
|
if (newSize.height === size.height &&
|
|
newSize.width === size.width &&
|
|
newSize.height === this.element.height &&
|
|
newSize.width === this.element.width) {
|
|
return;
|
|
}
|
|
const oldSize = Object.assign({}, size);
|
|
this.element.width = size.width = this.element.offsetWidth * pxRatio;
|
|
this.element.height = size.height = this.element.offsetHeight * pxRatio;
|
|
if (this.container.started) {
|
|
this.resizeFactor = {
|
|
width: size.width / oldSize.width,
|
|
height: size.height / oldSize.height,
|
|
};
|
|
}
|
|
}
|
|
async windowResize() {
|
|
if (!this.element) {
|
|
return;
|
|
}
|
|
this.resize();
|
|
const container = this.container, needsRefresh = container.updateActualOptions();
|
|
container.particles.setDensity();
|
|
this._applyResizePlugins();
|
|
if (needsRefresh) {
|
|
await container.refresh();
|
|
}
|
|
}
|
|
_applyPostDrawUpdaters(particle) {
|
|
var _a;
|
|
for (const updater of this._postDrawUpdaters) {
|
|
(_a = updater.afterDraw) === null || _a === void 0 ? void 0 : _a.call(updater, particle);
|
|
}
|
|
}
|
|
_applyPreDrawUpdaters(ctx, particle, radius, zOpacity, colorStyles, transform) {
|
|
var _a;
|
|
for (const updater of this._preDrawUpdaters) {
|
|
if (updater.getColorStyles) {
|
|
const { fill, stroke } = updater.getColorStyles(particle, ctx, radius, zOpacity);
|
|
if (fill) {
|
|
colorStyles.fill = fill;
|
|
}
|
|
if (stroke) {
|
|
colorStyles.stroke = stroke;
|
|
}
|
|
}
|
|
if (updater.getTransformValues) {
|
|
const updaterTransform = updater.getTransformValues(particle);
|
|
for (const key in updaterTransform) {
|
|
setTransformValue(transform, updaterTransform, key);
|
|
}
|
|
}
|
|
(_a = updater.beforeDraw) === null || _a === void 0 ? void 0 : _a.call(updater, particle);
|
|
}
|
|
}
|
|
_applyResizePlugins() {
|
|
for (const plugin of this._resizePlugins) {
|
|
if (plugin.resize) {
|
|
plugin.resize();
|
|
}
|
|
}
|
|
}
|
|
_getPluginParticleColors(particle) {
|
|
let fColor, sColor;
|
|
for (const plugin of this._colorPlugins) {
|
|
if (!fColor && plugin.particleFillColor) {
|
|
fColor = rangeColorToHsl(plugin.particleFillColor(particle));
|
|
}
|
|
if (!sColor && plugin.particleStrokeColor) {
|
|
sColor = rangeColorToHsl(plugin.particleStrokeColor(particle));
|
|
}
|
|
if (fColor && sColor) {
|
|
break;
|
|
}
|
|
}
|
|
return [fColor, sColor];
|
|
}
|
|
_initCover() {
|
|
const options = this.container.actualOptions, cover = options.backgroundMask.cover, color = cover.color, coverRgb = rangeColorToRgb(color);
|
|
if (coverRgb) {
|
|
const coverColor = {
|
|
r: coverRgb.r,
|
|
g: coverRgb.g,
|
|
b: coverRgb.b,
|
|
a: cover.opacity,
|
|
};
|
|
this._coverColorStyle = getStyleFromRgb(coverColor, coverColor.a);
|
|
}
|
|
}
|
|
_initStyle() {
|
|
const element = this.element, options = this.container.actualOptions;
|
|
if (!element) {
|
|
return;
|
|
}
|
|
if (this._fullScreen) {
|
|
this._originalStyle = deepExtend({}, element.style);
|
|
this._setFullScreenStyle();
|
|
}
|
|
else {
|
|
this._resetOriginalStyle();
|
|
}
|
|
for (const key in options.style) {
|
|
if (!key || !options.style) {
|
|
continue;
|
|
}
|
|
const value = options.style[key];
|
|
if (!value) {
|
|
continue;
|
|
}
|
|
element.style.setProperty(key, value, "important");
|
|
}
|
|
}
|
|
async _initTrail() {
|
|
const options = this.container.actualOptions, trail = options.particles.move.trail, trailFill = trail.fill;
|
|
if (!trail.enable) {
|
|
return;
|
|
}
|
|
if (trailFill.color) {
|
|
const fillColor = rangeColorToRgb(trailFill.color);
|
|
if (!fillColor) {
|
|
return;
|
|
}
|
|
const trail = options.particles.move.trail;
|
|
this._trailFill = {
|
|
color: Object.assign({}, fillColor),
|
|
opacity: 1 / trail.length,
|
|
};
|
|
}
|
|
else {
|
|
await new Promise((resolve, reject) => {
|
|
if (!trailFill.image) {
|
|
return;
|
|
}
|
|
const img = document.createElement("img");
|
|
img.addEventListener("load", () => {
|
|
this._trailFill = {
|
|
image: img,
|
|
opacity: 1 / trail.length,
|
|
};
|
|
resolve();
|
|
});
|
|
img.addEventListener("error", (evt) => {
|
|
reject(evt.error);
|
|
});
|
|
img.src = trailFill.image;
|
|
});
|
|
}
|
|
}
|
|
_paintBase(baseColor) {
|
|
this.draw((ctx) => {
|
|
paintBase(ctx, this.size, baseColor);
|
|
});
|
|
}
|
|
_paintImage(image, opacity) {
|
|
this.draw((ctx) => {
|
|
paintImage(ctx, this.size, image, opacity);
|
|
});
|
|
}
|
|
_repairStyle() {
|
|
var _a, _b;
|
|
const element = this.element;
|
|
if (!element) {
|
|
return;
|
|
}
|
|
(_a = this._mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
this._initStyle();
|
|
this.initBackground();
|
|
(_b = this._mutationObserver) === null || _b === void 0 ? void 0 : _b.observe(element, { attributes: true });
|
|
}
|
|
_resetOriginalStyle() {
|
|
const element = this.element, originalStyle = this._originalStyle;
|
|
if (!(element && originalStyle)) {
|
|
return;
|
|
}
|
|
element.style.position = originalStyle.position;
|
|
element.style.zIndex = originalStyle.zIndex;
|
|
element.style.top = originalStyle.top;
|
|
element.style.left = originalStyle.left;
|
|
element.style.width = originalStyle.width;
|
|
element.style.height = originalStyle.height;
|
|
}
|
|
_setFullScreenStyle() {
|
|
const element = this.element;
|
|
if (!element) {
|
|
return;
|
|
}
|
|
const priority = "important";
|
|
element.style.setProperty("position", "fixed", priority);
|
|
element.style.setProperty("z-index", this.container.actualOptions.fullScreen.zIndex.toString(10), priority);
|
|
element.style.setProperty("top", "0", priority);
|
|
element.style.setProperty("left", "0", priority);
|
|
element.style.setProperty("width", "100%", priority);
|
|
element.style.setProperty("height", "100%", priority);
|
|
}
|
|
}
|