@arwes/bgs
Version:
Futuristic Sci-Fi UI Web Framework
169 lines (168 loc) • 6.04 kB
JavaScript
import { easing, createAnimation } from '@arwes/animated';
import { getDistanceFromOriginToCornerProgress } from './getDistanceFromOriginToCornerProgress.js';
const defaultProps = {
color: '#777',
type: 'box',
distance: 30,
size: 4,
crossSize: 1,
origin: 'center',
originInverted: false,
easing: easing.inSine
};
const createBackgroundDots = (props) => {
const { canvas, animator } = props;
const ctx = canvas.getContext('2d');
if (!ctx) {
return { cancel: () => { } };
}
const dpr = Math.min(window.devicePixelRatio || 2, 2);
let transitionControl;
let resizeObserver;
let unsubscribe;
const getSettings = () => ({
...defaultProps,
...props.settingsRef.current
});
const resize = () => {
const { width, height } = canvas.getBoundingClientRect();
if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
canvas.width = width * dpr;
canvas.height = height * dpr;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(dpr, dpr);
};
const draw = (isEntering, progress) => {
if (!ctx) {
return;
}
const { color, type, distance, size, crossSize, origin, originInverted } = getSettings();
const { width, height } = canvas;
const xLength = 1 + Math.floor(width / distance);
const yLength = 1 + Math.floor(height / distance);
const xMargin = width % distance;
const yMargin = height % distance;
ctx.clearRect(0, 0, width, height);
for (let xIndex = 0; xIndex < xLength; xIndex++) {
const x = xMargin / 2 + xIndex * distance;
for (let yIndex = 0; yIndex < yLength; yIndex++) {
const y = yMargin / 2 + yIndex * distance;
const distanceFromOriginProgress = getDistanceFromOriginToCornerProgress(width / dpr, height / dpr, x, y, origin);
const distancePercentage = (isEntering && originInverted) || (!isEntering && !originInverted)
? 1 - distanceFromOriginProgress
: distanceFromOriginProgress;
const alphaProgress = progress / distancePercentage;
const alpha = Math.max(0, Math.min(1, alphaProgress));
ctx.beginPath();
ctx.globalAlpha = isEntering ? alpha : 1 - alpha;
if (type === 'circle') {
ctx.arc(x, y, size, 0, 2 * Math.PI);
}
else if (type === 'cross') {
const l = size / 2;
const b = crossSize / 2;
ctx.moveTo(x - l, y + b);
ctx.lineTo(x - l, y - b);
ctx.lineTo(x - b, y - b);
ctx.lineTo(x - b, y - l);
ctx.lineTo(x + b, y - l);
ctx.lineTo(x + b, y - b);
ctx.lineTo(x + l, y - b);
ctx.lineTo(x + l, y + b);
ctx.lineTo(x + b, y + b);
ctx.lineTo(x + b, y + l);
ctx.lineTo(x - b, y + l);
ctx.lineTo(x - b, y + b);
}
else {
ctx.rect(x - size / 2, y - size / 2, size, size);
}
ctx.fillStyle = color;
ctx.fill();
ctx.closePath();
}
}
};
const setup = () => {
canvas.style.opacity = '1';
if (typeof window !== 'undefined' && !resizeObserver) {
resizeObserver = new window.ResizeObserver(() => {
resize();
if (animator) {
switch (animator.state) {
case 'entered': {
draw(true, 1);
break;
}
}
}
else {
draw(true, 1);
}
});
resizeObserver.observe(canvas);
resize();
}
};
const stop = () => {
canvas.style.opacity = '0';
resizeObserver?.disconnect();
resizeObserver = undefined;
transitionControl?.cancel();
transitionControl = undefined;
};
const start = () => {
if (!animator) {
setup();
draw(true, 1);
return;
}
unsubscribe = animator.subscribe((node) => {
const settings = getSettings();
switch (node.state) {
case 'entering': {
setup();
transitionControl?.cancel();
transitionControl = createAnimation({
duration: node.settings.duration.enter,
easing: settings.easing,
onUpdate(progress) {
draw(true, progress);
}
});
break;
}
case 'entered': {
setup();
transitionControl?.cancel();
draw(true, 1);
break;
}
case 'exiting': {
setup();
transitionControl?.cancel();
transitionControl = createAnimation({
duration: node.settings.duration.exit,
easing: settings.easing,
onUpdate(progress) {
draw(false, progress);
}
});
break;
}
case 'exited': {
stop();
break;
}
}
});
};
const cancel = () => {
unsubscribe?.();
stop();
};
start();
return Object.freeze({ cancel });
};
export { createBackgroundDots };