react-confetti-boom
Version:
A customizable React confetti explosion component for celebrations and special events
257 lines (245 loc) • 9.29 kB
JavaScript
'use strict';
var React = require('react');
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z = ".index-module_canvas__H2w7d {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n pointer-events: none;\n}";
var styles = {"canvas":"index-module_canvas__H2w7d"};
styleInject(css_248z);
var randomNumBetween = function (min, max) {
return Math.random() * (max - min) + min;
};
var hexToRgb = function (hex) {
var r = parseInt(hex.slice(1, 3), 16);
var g = parseInt(hex.slice(3, 5), 16);
var b = parseInt(hex.slice(5, 7), 16);
return {
r: r,
g: g,
b: b
};
};
var Particle = /** @class */function () {
function Particle(x, y, deg, colors, shapes, shapeSize, spread, launchSpeed, opacityDelta) {
if (deg === void 0) {
deg = 0;
}
if (shapes === void 0) {
shapes = ['circle', 'square'];
}
if (shapeSize === void 0) {
shapeSize = 12;
}
if (spread === void 0) {
spread = 30;
}
if (launchSpeed === void 0) {
launchSpeed = 1;
}
if (opacityDelta === void 0) {
opacityDelta = 0.004;
}
var DPR = window.devicePixelRatio > 1 ? 2 : 1;
this.x = x * window.innerWidth * DPR;
this.y = y * window.innerHeight * DPR;
this.width = shapeSize;
this.height = shapeSize;
this.theta = Math.PI / 180 * randomNumBetween(deg - spread, deg + spread);
this.radius = randomNumBetween(20 * launchSpeed, 70 * launchSpeed);
this.vx = this.radius * Math.cos(this.theta);
this.vy = this.radius * Math.sin(this.theta);
this.xFriction = 0.9;
this.yFriction = 0.87;
this.gravity = randomNumBetween(0.5, 0.6);
this.opacity = 1;
this.opacityDelta = opacityDelta;
this.rotate = randomNumBetween(0, 360);
this.widthDelta = randomNumBetween(0, 360);
this.heightDelta = randomNumBetween(0, 360);
this.rotateDelta = randomNumBetween(-1, 1);
this.colors = colors;
this.color = hexToRgb(this.colors[Math.floor(randomNumBetween(0, this.colors.length))]);
this.shapes = shapes;
this.shape = this.shapes[Math.floor(randomNumBetween(0, this.shapes.length))];
this.swingOffset = randomNumBetween(0, Math.PI * 2);
this.swingSpeed = Math.random() * 0.05 + 0.01;
this.swingAmplitude = randomNumBetween(0.1, 0.2);
}
Particle.prototype.update = function () {
this.vx *= this.xFriction;
this.vy *= this.yFriction;
this.vy += this.gravity;
this.vx += Math.sin(this.swingOffset) * this.swingAmplitude;
this.x += this.vx;
this.y += this.vy;
this.opacity -= this.opacityDelta;
this.widthDelta += 2;
this.heightDelta += 2;
this.rotate += this.rotateDelta;
this.swingOffset += this.swingSpeed;
};
Particle.prototype.drawSquare = function (ctx) {
ctx.fillRect(this.x, this.y, this.width * Math.cos(Math.PI / 180 * this.widthDelta), this.height * Math.sin(Math.PI / 180 * this.heightDelta));
};
Particle.prototype.drawCircle = function (ctx) {
ctx.beginPath();
ctx.ellipse(this.x, this.y, Math.abs(this.width * Math.cos(Math.PI / 180 * this.widthDelta)) / 2, Math.abs(this.height * Math.sin(Math.PI / 180 * this.heightDelta)) / 2, 0, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
};
Particle.prototype.draw = function (ctx) {
var translateXAlpha = this.width * 1.3;
var translateYAlpha = this.height * 1.3;
ctx.translate(this.x + translateXAlpha, this.y + translateYAlpha);
ctx.rotate(Math.PI / 100 * this.rotate);
ctx.translate(-(this.x + translateXAlpha), -(this.y + translateYAlpha));
// eslint-disable-next-line no-param-reassign
ctx.fillStyle = "rgba(".concat(this.color.r, ", ").concat(this.color.g, ", ").concat(this.color.b, ", ").concat(this.opacity, ")");
if (this.shape === 'square') this.drawSquare(ctx);
if (this.shape === 'circle') this.drawCircle(ctx);
ctx.resetTransform();
};
return Particle;
}();
var FPS = 60;
var INTERVAL = 1000 / FPS;
function Confetti(props) {
// common props
var _a = props.mode,
mode = _a === void 0 ? 'boom' : _a,
_b = props.particleCount,
particleCount = _b === void 0 ? 30 : _b,
_c = props.shapeSize,
shapeSize = _c === void 0 ? 12 : _c,
_d = props.colors,
colors = _d === void 0 ? ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'] : _d;
// boom or common props
var _e = props.mode === 'boom' || props.mode === undefined ? props : {
effectInterval: 1,
effectCount: Infinity
},
_f = _e.x,
x = _f === void 0 ? 0.5 : _f,
_g = _e.y,
y = _g === void 0 ? 0.5 : _g,
_h = _e.deg,
deg = _h === void 0 ? 270 : _h,
_j = _e.spreadDeg,
spreadDeg = _j === void 0 ? 30 : _j,
_k = _e.effectInterval,
effectInterval = _k === void 0 ? 3000 : _k,
_l = _e.effectCount,
effectCount = _l === void 0 ? 1 : _l,
_m = _e.launchSpeed,
launchSpeed = _m === void 0 ? 1 : _m;
// fall props (default: 80% of the screen height)
var _o = (props.mode === 'fall' ? props : {}).fadeOutHeight,
fadeOutHeight = _o === void 0 ? 0.8 : _o;
var canvasRef = React.useRef(null);
var ctxRef = React.useRef();
var particlesRef = React.useRef([]);
var animationFrameRef = React.useRef(0);
var effectCountRef = React.useRef(0);
var init = React.useCallback(function () {
var canvas = canvasRef.current;
var ctx = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('2d');
if (!canvas || !ctx) return;
ctxRef.current = ctx;
var DPR = window.devicePixelRatio > 1 ? 2 : 1;
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
canvas.style.width = "".concat(canvasWidth, "px");
canvas.style.height = "".concat(canvasHeight, "px");
canvas.width = canvasWidth * DPR;
canvas.height = canvasHeight * DPR;
ctx.scale(DPR, DPR);
}, []);
var createConfetti = React.useCallback(function () {
var isFallMode = mode === 'fall';
var effectiveCount = isFallMode ? particleCount / 30 : particleCount;
var effectiveX = isFallMode ? randomNumBetween(0, 1) : x;
var effectiveY = isFallMode ? randomNumBetween(-0.1, -0.3) : y;
var effectiveDeg = isFallMode ? 270 : deg;
var effectiveSpreadDeg = isFallMode ? 0 : spreadDeg;
var effectiveLaunchSpeed = isFallMode ? 0 : launchSpeed;
// opacity delta
var effectiveOpacityDelta = isFallMode ? 3.4 / fadeOutHeight / window.innerHeight : 0.004;
for (var i = 0; i < effectiveCount; i += 1) {
particlesRef.current.push(new Particle(effectiveX, effectiveY, effectiveDeg, colors, ['circle', 'square'], shapeSize, effectiveSpreadDeg, effectiveLaunchSpeed, effectiveOpacityDelta));
}
}, [mode, x, y, deg, colors, shapeSize, spreadDeg, launchSpeed, particleCount, fadeOutHeight]);
var render = React.useCallback(function () {
if (!ctxRef.current) return;
var now;
var delta;
var then = Date.now();
var effectDelta;
var effectThen = Date.now() - effectInterval;
var frame = function () {
var canvas = canvasRef.current;
if (!ctxRef.current) return;
if (!canvas) return;
animationFrameRef.current = requestAnimationFrame(frame);
now = Date.now();
delta = now - then;
effectDelta = now - effectThen;
if (delta < INTERVAL) return;
ctxRef.current.clearRect(0, 0, canvas.width, canvas.height);
if (effectDelta > effectInterval && effectCountRef.current < effectCount) {
createConfetti();
effectThen = now - effectDelta % effectInterval;
effectCountRef.current += 1;
}
var particles = particlesRef.current;
for (var i = particles.length - 1; i >= 0; i -= 1) {
var p = particles[i];
p.update();
p.draw(ctxRef.current);
var canvasHeight = (canvas === null || canvas === void 0 ? void 0 : canvas.height) || 0;
if (p.opacity <= 0 || p.y > canvasHeight) particles.splice(particles.indexOf(p), 1);
}
then = now - delta % INTERVAL;
if (effectCountRef.current >= effectCount && particles.length === 0) {
cancelAnimationFrame(animationFrameRef.current);
}
};
animationFrameRef.current = requestAnimationFrame(frame);
}, [effectInterval, effectCount, createConfetti]);
React.useEffect(function () {
init();
render();
return function () {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, [init, render]);
React.useEffect(function () {
effectCountRef.current = 0;
}, [effectCount]);
return React.createElement("canvas", {
className: styles.canvas,
ref: canvasRef
});
}
module.exports = Confetti;
//# sourceMappingURL=index.js.map