diff --git a/.github/workflows/auto_release.yml b/.github/workflows/auto_release.yml
new file mode 100644
index 0000000..cc276f6
--- /dev/null
+++ b/.github/workflows/auto_release.yml
@@ -0,0 +1,29 @@
+name: Auto Release
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ create_release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Get version from package.json
+ id: version
+ run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
+
+ - name: Create Release
+ id: create_release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+ with:
+ tag_name: v${{ env.version }}
+ release_name: Release v${{ env.version }}
+ body: Auto-generated release
+ draft: false
+ prerelease: false
diff --git a/.github/workflows/deploy_to_github_pages.yml b/.github/workflows/deploy_to_github_pages.yml
new file mode 100644
index 0000000..8f2c612
--- /dev/null
+++ b/.github/workflows/deploy_to_github_pages.yml
@@ -0,0 +1,33 @@
+name: Deploy to GitHub Pages
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: 16
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build project
+ run: yarn build
+
+ - name: Build example
+ run: cd example && yarn install && yarn build
+
+ - name: Deploy to GitHub Pages
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+ publish_dir: example/dist
diff --git a/.github/workflows/publish_to_github.yml b/.github/workflows/publish_to_github.yml
new file mode 100644
index 0000000..d5f87b5
--- /dev/null
+++ b/.github/workflows/publish_to_github.yml
@@ -0,0 +1,46 @@
+name: Publish Github Package
+
+on:
+ release:
+ types: [created]
+#on:
+# push:
+# branches:
+# - main
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '16.x'
+ registry-url: 'https://npm.pkg.github.com'
+ # Defaults to the user or organization that owns the workflow file
+ scope: '@almond-bongbong'
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build package
+ run: yarn build
+
+ - name: Install jq
+ run: sudo apt-get install -y jq
+
+ - name: Update package.json for GitHub Packages
+ run: |
+ jq '.name = "@almond-bongbong/react-confetti-boom"' package.json > package.temp.json
+ mv package.temp.json package.json
+
+ - name: Publish to GitHub Packages
+ run: npm publish
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/publish_to_npm.yml b/.github/workflows/publish_to_npm.yml
new file mode 100644
index 0000000..3ddd61e
--- /dev/null
+++ b/.github/workflows/publish_to_npm.yml
@@ -0,0 +1,33 @@
+name: Publish NPM Package
+
+on:
+ release:
+ types: [created]
+#on:
+# push:
+# branches:
+# - main
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '16.x'
+ registry-url: 'https://registry.npmjs.org'
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build package
+ run: yarn build
+
+ - name: Publish to npm
+ run: npm publish
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/README.md b/README.md
index 4ae0f1c..67e623d 100644
--- a/README.md
+++ b/README.md
@@ -1,71 +1,77 @@
-# node-module-template
+# React Confetti Boom
-This is a template for creating a node module.
+React Confetti Boom is a lightweight, customizable confetti animation component for React applications. Add a fun and engaging confetti effect to your app with just a few lines of code.
-## Build
-
-Rollup is used to build the module. The configuration is in `rollup.config.js`.
+## Installation
```bash
-import commonjs from '@rollup/plugin-commonjs';
-import { nodeResolve } from '@rollup/plugin-node-resolve';
-import typescript from 'rollup-plugin-typescript2';
-import peerDepsExternal from 'rollup-plugin-peer-deps-external';
-import postcss from 'rollup-plugin-postcss';
-import babel from '@rollup/plugin-babel';
-
-import packageJson from './package.json' assert { type: 'json' };
-
-const extensions = ['js', 'jsx', 'ts', 'tsx', 'mjs'];
-
-export default {
- input: './src/index.tsx',
- output: [
- {
- file: packageJson.main,
- format: 'cjs',
- sourcemap: true,
- },
- {
- file: packageJson.module,
- format: 'esm',
- sourcemap: true,
- },
- ],
- plugins: [
- peerDepsExternal(),
- nodeResolve({ extensions }),
- commonjs(),
- typescript({
- tsconfig: './tsconfig.json',
- }),
- babel({
- babelHelpers: 'bundled',
- exclude: 'node_modules/**',
- extensions,
- include: ['src/**/*'],
- }),
- postcss({
- extract: false,
- modules: true,
- sourceMap: false,
- use: ['sass'],
- }),
- ],
-};
+npm install react-confetti-boom
+```
+
+## Usage
+
+Import the Confetti component and add it to your JSX.
+
+```jsx
+import React from 'react';
+import Confetti from 'react-confetti-boom';
+
+function MyApp() {
+ return (
+
+
My React App
+
+
+ );
+}
+
+export default MyApp;
```
-1. @rollup/plugin-babel: Babel 플러그인을 사용하여 최신 JavaScript 문법을 이전 버전의 호환 가능한 코드로 변환합니다.
-2. @rollup/plugin-commonjs: CommonJS 모듈을 ES6 모듈로 변환합니다. 이를 통해 npm 패키지와 호환성을 보장합니다.
-3. @rollup/plugin-node-resolve: Node.js 모듈 해석 알고리즘을 사용하여 모듈 의존성을 해결합니다.
-4. rollup-plugin-typescript2: TypeScript 코드를 JavaScript로 변환합니다.
-5. rollup-plugin-peer-deps-external: 외부 peerDependencies를 번들에서 자동으로 제외합니다.
-6. rollup-plugin-postcss: CSS 및 SCSS와 같은 스타일시트를 처리하고 모듈로 번들링합니다.
+## Props
+
+| Name | Type | Default | Description |
+| -------------- | -------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
+| x | number | 0.5 | Horizontal starting position of confetti as a ratio of canvas width (0 to 1) |
+| y | number | 0.5 | Vertical starting position of confetti as a ratio of canvas height (0 to 1) |
+| particleCount | number | 30 | Number of confetti particles to generate |
+| deg | number | 270 | Initial angle (in degrees) at which particles are emitted |
+| shapeSize | number | 12 | Size of confetti particles |
+| spreadDeg | number | 30 | Angle (in degrees) that particles can deviate from the initial angle (deg) |
+| effectInterval | number | 3000 | Interval (in ms) between consecutive confetti bursts |
+| effectCount | number | 1 | Number of confetti bursts to render |
+| colors | string[] | ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'] | Array of colors for confetti particles, in hex format |
+
+## Example
-extensions 배열은 Rollup이 처리해야 하는 파일 확장자를 나열합니다.
+```jsx
+import React from 'react';
+import Confetti from 'react-confetti-boom';
+
+function Celebration() {
+ return (
+
+
Congratulations!
+
+
+ );
+}
+
+export default Celebration;
+```
-export default는 Rollup에 대한 기본 설정을 내보냅니다. input은 번들의 시작점을 지정하고, output은 번들의 결과물을 저장할 경로와 포맷을 설정합니다. 이 설정에서는 CommonJS 및 ES 모듈 포맷을 사용하여 두 개의 번들을 생성합니다.
+This example will render a confetti animation with 50 particles starting at 10% from the top of the canvas. The particles will be emitted at a 270-degree angle, with a 45-degree spread. The confetti bursts will occur every 2 seconds, for a total of 3 bursts. The confetti particles will use the provided array of colors.
-plugins 배열은 위에서 설명한 플러그인들을 포함합니다. 이 플러그인들은 번들링 과정에서 코드를 변환하고 최적화하는 데 사용됩니다.
+## License
-이 설정은 라이브러리의 소스 코드를 효율적으로 번들링하고, 최신 JavaScript 및 TypeScript 문법을 지원하며, 스타일시트와 모듈 처리를 포함하는 등 다양한 요구 사항을 충족합니다.
+MIT
diff --git a/example/package.json b/example/package.json
index fce04eb..8450ad3 100644
--- a/example/package.json
+++ b/example/package.json
@@ -9,11 +9,13 @@
"preview": "vite preview"
},
"dependencies": {
+ "dat.gui": "^0.7.9",
"react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-confetti-boom": "link:.."
+ "react-confetti-boom": "link:..",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
+ "@types/dat.gui": "^0.7.9",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react-swc": "^3.0.0",
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 169cc50..615a595 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,13 +1,90 @@
-import { useState } from 'react';
-import { Confetti } from 'react-confetti-boom';
+import { useCallback, useEffect, useState } from 'react';
+import Confetti from 'react-confetti-boom';
+import * as dat from 'dat.gui';
import './style.css';
+const DEFAULT_OPTIONS = {
+ deg: 270,
+ particleCount: 30,
+ effectCount: Infinity,
+ effectInterval: 3000,
+ shapeSize: 12,
+ spreadDeg: 30,
+ x: 0.5,
+ y: 0.5,
+ colors: ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'],
+};
+
function App() {
- const [count, setCount] = useState(0);
+ const [options, setOptions] = useState(DEFAULT_OPTIONS);
+
+ const handleChangeOption = useCallback((key: string, value: number) => {
+ setOptions((prev) => ({ ...prev, [key]: value }));
+ }, []);
+
+ const handleColors = useCallback((index: number, value: string) => {
+ setOptions((prev) => {
+ const newColors = [...prev.colors];
+ newColors[index] = value;
+ return { ...prev, colors: newColors };
+ });
+ }, []);
+
+ useEffect(() => {
+ const target = { ...DEFAULT_OPTIONS };
+ const gui = new dat.GUI();
+ gui.width = 300;
+ const confetti = gui.addFolder('Confetti');
+ confetti.open();
+ confetti
+ .add(target, 'particleCount', 0, 100)
+ .onChange((v) => handleChangeOption('particleCount', v));
+ confetti
+ .add(target, 'deg', 0, 360)
+ .onChange((v) => handleChangeOption('deg', v));
+ confetti
+ .add(target, 'effectCount', 1, 100)
+ .onChange((v) => handleChangeOption('effectCount', v));
+ confetti
+ .add(target, 'effectInterval', 0, 10000)
+ .name('effectInterval (ms)')
+ .onChange((v) => handleChangeOption('effectInterval', v));
+ confetti
+ .add(target, 'shapeSize', 1, 50)
+ .onChange((v) => handleChangeOption('shapeSize', v));
+ confetti
+ .add(target, 'spreadDeg', 0, 100)
+ .onChange((v) => handleChangeOption('spreadDeg', v));
+ confetti.add(target, 'x', 0, 1).onChange((v) => handleChangeOption('x', v));
+ confetti.add(target, 'y', 0, 1).onChange((v) => handleChangeOption('y', v));
+
+ const colors = gui.addFolder('Colors');
+ colors.open();
+
+ DEFAULT_OPTIONS.colors.forEach((color, i) => {
+ colors
+ .addColor(target.colors, i.toString())
+ .onChange((v) => handleColors(i, v));
+ });
+
+ return () => {
+ gui.destroy();
+ };
+ }, [handleChangeOption, handleColors]);
return (
-
+
);
}
diff --git a/example/yarn.lock b/example/yarn.lock
index 4ceba0b..82de82e 100644
--- a/example/yarn.lock
+++ b/example/yarn.lock
@@ -178,6 +178,11 @@
"@swc/core-win32-ia32-msvc" "1.3.46"
"@swc/core-win32-x64-msvc" "1.3.46"
+"@types/dat.gui@^0.7.9":
+ version "0.7.9"
+ resolved "https://registry.yarnpkg.com/@types/dat.gui/-/dat.gui-0.7.9.tgz#9e16985a0a6b4ef652c53212ee813e5968aff9a8"
+ integrity sha512-UiqZasQIask5cUwWOO6BgOjP1dNj9ChYtmeAb4WTKs5IJ7ha3ATRTeXY7/TzlmEZznh40lS6Ov5BdNA+tU+fWQ==
+
"@types/prop-types@*":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
@@ -216,6 +221,11 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
+dat.gui@^0.7.9:
+ version "0.7.9"
+ resolved "https://registry.yarnpkg.com/dat.gui/-/dat.gui-0.7.9.tgz#860cab06053b028e327820eabdf25a13cf07b17e"
+ integrity sha512-sCNc1OHobc+Erc1HqiswYgHdVNpSJUlk/Hz8vzOCsER7rl+oF/4+v8GXFUyCgtXpoCX6+bnmg07DedLvBLwYKQ==
+
esbuild@^0.17.5:
version "0.17.15"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.15.tgz#209ebc87cb671ffb79574db93494b10ffaf43cbc"
diff --git a/lib/index.d.ts b/lib/index.d.ts
index 0a871bd..4182665 100644
--- a/lib/index.d.ts
+++ b/lib/index.d.ts
@@ -1,3 +1,14 @@
///
-declare function Confetti(): JSX.Element;
-export { Confetti };
+interface Props {
+ x?: number;
+ y?: number;
+ particleCount?: number;
+ deg?: number;
+ shapeSize?: number;
+ spreadDeg?: number;
+ effectInterval?: number;
+ effectCount?: number;
+ colors?: string[];
+}
+declare function Confetti({ x, y, particleCount, deg, shapeSize, spreadDeg, effectInterval, effectCount, colors, }: Props): JSX.Element;
+export default Confetti;
diff --git a/lib/index.esm.js b/lib/index.esm.js
index 0f51902..2d91605 100644
--- a/lib/index.esm.js
+++ b/lib/index.esm.js
@@ -1,36 +1,5 @@
import React, { useRef, useCallback, useEffect } from '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 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;
};
@@ -46,29 +15,30 @@ var hexToRgb = function (hex) {
};
var Particle = /** @class */function () {
- function Particle(x, y, deg, colors, shapes, spread) {
+ function Particle(x, y, deg, colors, shapes, shapeSize, spread) {
if (deg === void 0) {
deg = 0;
}
- if (colors === void 0) {
- colors = ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'];
- }
if (shapes === void 0) {
shapes = ['circle', 'square'];
}
+ if (shapeSize === void 0) {
+ shapeSize = 12;
+ }
if (spread === void 0) {
spread = 30;
}
- this.x = x * window.innerWidth;
- this.y = y * window.innerHeight;
- this.width = 12;
- this.height = 12;
+ 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(30, 100);
+ this.radius = randomNumBetween(20, 70);
this.vx = this.radius * Math.cos(this.theta);
this.vy = this.radius * Math.sin(this.theta);
- this.friction = 0.89;
- this.gravity = 0.5;
+ this.friction = 0.87;
+ this.gravity = 0.55;
this.opacity = 1;
this.rotate = randomNumBetween(0, 360);
this.widthDelta = randomNumBetween(0, 360);
@@ -78,17 +48,22 @@ var Particle = /** @class */function () {
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, 0.4);
}
Particle.prototype.update = function () {
- this.vy += this.gravity;
this.vx *= this.friction;
this.vy *= this.friction;
+ this.vy += this.gravity;
+ if (this.vy > 0) this.vx += Math.sin(this.swingOffset) * this.swingAmplitude;
this.x += this.vx;
this.y += this.vy;
- this.opacity -= 0.005;
+ this.opacity -= 0.004;
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));
@@ -105,6 +80,7 @@ var Particle = /** @class */function () {
ctx.translate(this.x + translateXAlpha, this.y + translateYAlpha);
ctx.rotate(Math.PI / 180 * 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);
@@ -113,13 +89,63 @@ var Particle = /** @class */function () {
return Particle;
}();
+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 FPS = 60;
var INTERVAL = 1000 / FPS;
-function Confetti() {
+function Confetti(_a) {
+ var _b = _a.x,
+ x = _b === void 0 ? 0.5 : _b,
+ _c = _a.y,
+ y = _c === void 0 ? 0.5 : _c,
+ _d = _a.particleCount,
+ particleCount = _d === void 0 ? 30 : _d,
+ _e = _a.deg,
+ deg = _e === void 0 ? 270 : _e,
+ _f = _a.shapeSize,
+ shapeSize = _f === void 0 ? 12 : _f,
+ _g = _a.spreadDeg,
+ spreadDeg = _g === void 0 ? 30 : _g,
+ _h = _a.effectInterval,
+ effectInterval = _h === void 0 ? 3000 : _h,
+ _j = _a.effectCount,
+ effectCount = _j === void 0 ? 1 : _j,
+ _k = _a.colors,
+ colors = _k === void 0 ? ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'] : _k;
var canvasRef = useRef(null);
- var ctxRef = useRef(null);
- var particles = [];
+ var ctxRef = useRef();
+ var particlesRef = useRef([]);
var animationFrameRef = useRef(0);
+ var effectCountRef = useRef(0);
var init = useCallback(function () {
var canvas = canvasRef.current;
var ctx = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('2d');
@@ -135,58 +161,73 @@ function Confetti() {
ctx.scale(DPR, DPR);
}, []);
var createConfetti = useCallback(function (options) {
- for (var i = 0; i < options.count; i++) {
- particles.push(new Particle(options.x, options.y, options.deg, options.colors, options.shapes, options.spread));
+ for (var i = 0; i < options.particleCount; i += 1) {
+ particlesRef.current.push(new Particle(options.x, options.y, options.deg, options.colors, options.shapes, options.shapeSize, options.spreadDeg));
}
}, []);
var render = useCallback(function () {
- var canvas = canvasRef.current;
- if (!ctxRef.current || !canvas) return;
+ 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);
- createConfetti({
- x: 0,
- y: 0.5,
- count: 10,
- deg: -50
- });
- createConfetti({
- x: 1,
- y: 0.5,
- count: 10,
- deg: 230
- });
- for (var i = particles.length - 1; i >= 0; i--) {
+ if (effectDelta > effectInterval && effectCountRef.current < effectCount) {
+ createConfetti({
+ x: x,
+ y: y,
+ particleCount: particleCount,
+ deg: deg,
+ shapeSize: shapeSize,
+ spreadDeg: spreadDeg,
+ colors: colors
+ });
+ 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);
- if (p.opacity <= 0) particles.splice(particles.indexOf(p), 1);
- if (p.y > window.innerHeight) particles.splice(particles.indexOf(p), 1);
+ 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);
- }, []);
+ }, [x, y, particleCount, deg, effectInterval, shapeSize, effectCount, spreadDeg, colors, createConfetti]);
useEffect(function () {
init();
render();
return function () {
- if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
+ if (animationFrameRef.current) {
+ cancelAnimationFrame(animationFrameRef.current);
+ }
};
- }, []);
+ }, [init, render]);
+ useEffect(function () {
+ effectCountRef.current = 0;
+ // render();
+ }, [effectCount]);
return React.createElement("canvas", {
className: styles.canvas,
ref: canvasRef
});
}
-export { Confetti };
+export { Confetti as default };
//# sourceMappingURL=index.esm.js.map
diff --git a/lib/index.esm.js.map b/lib/index.esm.js.map
index 25524af..965d608 100644
--- a/lib/index.esm.js.map
+++ b/lib/index.esm.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.esm.js","sources":["../node_modules/style-inject/dist/style-inject.es.js"],"sourcesContent":["function styleInject(css, ref) {\n if ( ref === void 0 ) ref = {};\n var insertAt = ref.insertAt;\n\n if (!css || typeof document === 'undefined') { return; }\n\n var head = document.head || document.getElementsByTagName('head')[0];\n var style = document.createElement('style');\n style.type = 'text/css';\n\n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild);\n } else {\n head.appendChild(style);\n }\n } else {\n head.appendChild(style);\n }\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n}\n\nexport default styleInject;\n"],"names":[],"mappings":";;AAAA,SAAS,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE;AAC/B,EAAE,KAAK,GAAG,KAAK,KAAK,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;AACjC,EAAE,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;AAC9B;AACA,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,EAAE,OAAO,EAAE;AAC1D;AACA,EAAE,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,EAAE,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AAC9C,EAAE,KAAK,CAAC,IAAI,GAAG,UAAU,CAAC;AAC1B;AACA,EAAE,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC1B,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;AACzB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChD,KAAK,MAAM;AACX,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC9B,KAAK;AACL,GAAG,MAAM;AACT,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC5B,GAAG;AACH;AACA,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE;AACxB,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAC;AACnC,GAAG,MAAM;AACT,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;AACpD,GAAG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[0]}
\ No newline at end of file
+{"version":3,"file":"index.esm.js","sources":["../node_modules/style-inject/dist/style-inject.es.js"],"sourcesContent":["function styleInject(css, ref) {\n if ( ref === void 0 ) ref = {};\n var insertAt = ref.insertAt;\n\n if (!css || typeof document === 'undefined') { return; }\n\n var head = document.head || document.getElementsByTagName('head')[0];\n var style = document.createElement('style');\n style.type = 'text/css';\n\n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild);\n } else {\n head.appendChild(style);\n }\n } else {\n head.appendChild(style);\n }\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n}\n\nexport default styleInject;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE;AAC/B,EAAE,KAAK,GAAG,KAAK,KAAK,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;AACjC,EAAE,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;AAC9B;AACA,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,EAAE,OAAO,EAAE;AAC1D;AACA,EAAE,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,EAAE,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AAC9C,EAAE,KAAK,CAAC,IAAI,GAAG,UAAU,CAAC;AAC1B;AACA,EAAE,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC1B,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;AACzB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChD,KAAK,MAAM;AACX,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC9B,KAAK;AACL,GAAG,MAAM;AACT,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC5B,GAAG;AACH;AACA,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE;AACxB,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAC;AACnC,GAAG,MAAM;AACT,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;AACpD,GAAG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[0]}
\ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
index f7c77d5..c8887b1 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -2,37 +2,6 @@
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 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;
};
@@ -48,29 +17,30 @@ var hexToRgb = function (hex) {
};
var Particle = /** @class */function () {
- function Particle(x, y, deg, colors, shapes, spread) {
+ function Particle(x, y, deg, colors, shapes, shapeSize, spread) {
if (deg === void 0) {
deg = 0;
}
- if (colors === void 0) {
- colors = ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'];
- }
if (shapes === void 0) {
shapes = ['circle', 'square'];
}
+ if (shapeSize === void 0) {
+ shapeSize = 12;
+ }
if (spread === void 0) {
spread = 30;
}
- this.x = x * window.innerWidth;
- this.y = y * window.innerHeight;
- this.width = 12;
- this.height = 12;
+ 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(30, 100);
+ this.radius = randomNumBetween(20, 70);
this.vx = this.radius * Math.cos(this.theta);
this.vy = this.radius * Math.sin(this.theta);
- this.friction = 0.89;
- this.gravity = 0.5;
+ this.friction = 0.87;
+ this.gravity = 0.55;
this.opacity = 1;
this.rotate = randomNumBetween(0, 360);
this.widthDelta = randomNumBetween(0, 360);
@@ -80,17 +50,22 @@ var Particle = /** @class */function () {
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, 0.4);
}
Particle.prototype.update = function () {
- this.vy += this.gravity;
this.vx *= this.friction;
this.vy *= this.friction;
+ this.vy += this.gravity;
+ if (this.vy > 0) this.vx += Math.sin(this.swingOffset) * this.swingAmplitude;
this.x += this.vx;
this.y += this.vy;
- this.opacity -= 0.005;
+ this.opacity -= 0.004;
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));
@@ -107,6 +82,7 @@ var Particle = /** @class */function () {
ctx.translate(this.x + translateXAlpha, this.y + translateYAlpha);
ctx.rotate(Math.PI / 180 * 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);
@@ -115,13 +91,63 @@ var Particle = /** @class */function () {
return Particle;
}();
+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 FPS = 60;
var INTERVAL = 1000 / FPS;
-function Confetti() {
+function Confetti(_a) {
+ var _b = _a.x,
+ x = _b === void 0 ? 0.5 : _b,
+ _c = _a.y,
+ y = _c === void 0 ? 0.5 : _c,
+ _d = _a.particleCount,
+ particleCount = _d === void 0 ? 30 : _d,
+ _e = _a.deg,
+ deg = _e === void 0 ? 270 : _e,
+ _f = _a.shapeSize,
+ shapeSize = _f === void 0 ? 12 : _f,
+ _g = _a.spreadDeg,
+ spreadDeg = _g === void 0 ? 30 : _g,
+ _h = _a.effectInterval,
+ effectInterval = _h === void 0 ? 3000 : _h,
+ _j = _a.effectCount,
+ effectCount = _j === void 0 ? 1 : _j,
+ _k = _a.colors,
+ colors = _k === void 0 ? ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'] : _k;
var canvasRef = React.useRef(null);
- var ctxRef = React.useRef(null);
- var particles = [];
+ 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');
@@ -137,58 +163,73 @@ function Confetti() {
ctx.scale(DPR, DPR);
}, []);
var createConfetti = React.useCallback(function (options) {
- for (var i = 0; i < options.count; i++) {
- particles.push(new Particle(options.x, options.y, options.deg, options.colors, options.shapes, options.spread));
+ for (var i = 0; i < options.particleCount; i += 1) {
+ particlesRef.current.push(new Particle(options.x, options.y, options.deg, options.colors, options.shapes, options.shapeSize, options.spreadDeg));
}
}, []);
var render = React.useCallback(function () {
- var canvas = canvasRef.current;
- if (!ctxRef.current || !canvas) return;
+ 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);
- createConfetti({
- x: 0,
- y: 0.5,
- count: 10,
- deg: -50
- });
- createConfetti({
- x: 1,
- y: 0.5,
- count: 10,
- deg: 230
- });
- for (var i = particles.length - 1; i >= 0; i--) {
+ if (effectDelta > effectInterval && effectCountRef.current < effectCount) {
+ createConfetti({
+ x: x,
+ y: y,
+ particleCount: particleCount,
+ deg: deg,
+ shapeSize: shapeSize,
+ spreadDeg: spreadDeg,
+ colors: colors
+ });
+ 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);
- if (p.opacity <= 0) particles.splice(particles.indexOf(p), 1);
- if (p.y > window.innerHeight) particles.splice(particles.indexOf(p), 1);
+ 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);
- }, []);
+ }, [x, y, particleCount, deg, effectInterval, shapeSize, effectCount, spreadDeg, colors, createConfetti]);
React.useEffect(function () {
init();
render();
return function () {
- if (animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
+ if (animationFrameRef.current) {
+ cancelAnimationFrame(animationFrameRef.current);
+ }
};
- }, []);
+ }, [init, render]);
+ React.useEffect(function () {
+ effectCountRef.current = 0;
+ // render();
+ }, [effectCount]);
return React.createElement("canvas", {
className: styles.canvas,
ref: canvasRef
});
}
-exports.Confetti = Confetti;
+module.exports = Confetti;
//# sourceMappingURL=index.js.map
diff --git a/lib/index.js.map b/lib/index.js.map
index 00b8ca6..1259958 100644
--- a/lib/index.js.map
+++ b/lib/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sources":["../node_modules/style-inject/dist/style-inject.es.js"],"sourcesContent":["function styleInject(css, ref) {\n if ( ref === void 0 ) ref = {};\n var insertAt = ref.insertAt;\n\n if (!css || typeof document === 'undefined') { return; }\n\n var head = document.head || document.getElementsByTagName('head')[0];\n var style = document.createElement('style');\n style.type = 'text/css';\n\n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild);\n } else {\n head.appendChild(style);\n }\n } else {\n head.appendChild(style);\n }\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n}\n\nexport default styleInject;\n"],"names":[],"mappings":";;;;AAAA,SAAS,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE;AAC/B,EAAE,KAAK,GAAG,KAAK,KAAK,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;AACjC,EAAE,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;AAC9B;AACA,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,EAAE,OAAO,EAAE;AAC1D;AACA,EAAE,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,EAAE,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AAC9C,EAAE,KAAK,CAAC,IAAI,GAAG,UAAU,CAAC;AAC1B;AACA,EAAE,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC1B,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;AACzB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChD,KAAK,MAAM;AACX,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC9B,KAAK;AACL,GAAG,MAAM;AACT,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC5B,GAAG;AACH;AACA,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE;AACxB,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAC;AACnC,GAAG,MAAM;AACT,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;AACpD,GAAG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[0]}
\ No newline at end of file
+{"version":3,"file":"index.js","sources":["../node_modules/style-inject/dist/style-inject.es.js"],"sourcesContent":["function styleInject(css, ref) {\n if ( ref === void 0 ) ref = {};\n var insertAt = ref.insertAt;\n\n if (!css || typeof document === 'undefined') { return; }\n\n var head = document.head || document.getElementsByTagName('head')[0];\n var style = document.createElement('style');\n style.type = 'text/css';\n\n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild);\n } else {\n head.appendChild(style);\n }\n } else {\n head.appendChild(style);\n }\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n}\n\nexport default styleInject;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE;AAC/B,EAAE,KAAK,GAAG,KAAK,KAAK,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;AACjC,EAAE,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;AAC9B;AACA,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,EAAE,OAAO,EAAE;AAC1D;AACA,EAAE,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,EAAE,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AAC9C,EAAE,KAAK,CAAC,IAAI,GAAG,UAAU,CAAC;AAC1B;AACA,EAAE,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC1B,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;AACzB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChD,KAAK,MAAM;AACX,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC9B,KAAK;AACL,GAAG,MAAM;AACT,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AAC5B,GAAG;AACH;AACA,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE;AACxB,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAC;AACnC,GAAG,MAAM;AACT,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;AACpD,GAAG;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","x_google_ignoreList":[0]}
\ No newline at end of file
diff --git a/lib/js/Particle.d.ts b/lib/js/Particle.d.ts
index 13ca7cb..af51118 100644
--- a/lib/js/Particle.d.ts
+++ b/lib/js/Particle.d.ts
@@ -20,9 +20,12 @@ declare class Particle {
g: number;
b: number;
};
- shapes: string[];
+ shapes: readonly ['circle', 'square'];
shape: string;
- constructor(x: number, y: number, deg?: number, colors?: string[], shapes?: ('circle' | 'square')[], spread?: number);
+ swingOffset: number;
+ swingSpeed: number;
+ swingAmplitude: number;
+ constructor(x: number, y: number, deg: number | undefined, colors: string[], shapes?: readonly ["circle", "square"], shapeSize?: number, spread?: number);
update(): void;
drawSquare(ctx: CanvasRenderingContext2D): void;
drawCircle(ctx: CanvasRenderingContext2D): void;
diff --git a/package.json b/package.json
index b08da61..633b13f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-confetti-boom",
- "version": "1.0.0",
+ "version": "0.0.1",
"description": "react-confetti-boom",
"author": "",
"license": "MIT",
@@ -39,6 +39,8 @@
"keywords": [
"react",
"typescript",
- "module"
+ "module",
+ "confetti",
+ "particle"
]
}
diff --git a/src/index.module.scss b/src/index.module.scss
index d68c408..5b04de7 100644
--- a/src/index.module.scss
+++ b/src/index.module.scss
@@ -1,3 +1,8 @@
.canvas {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
pointer-events: none;
}
diff --git a/src/index.tsx b/src/index.tsx
index 1e01e56..bcd1cb5 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,15 +1,38 @@
import React, { useCallback, useEffect, useRef } from 'react';
-import styles from './index.module.scss';
import Particle from './js/Particle';
+import styles from './index.module.scss';
const FPS = 60;
const INTERVAL = 1000 / FPS;
-function Confetti() {
+interface Props {
+ x?: number;
+ y?: number;
+ particleCount?: number;
+ deg?: number;
+ shapeSize?: number;
+ spreadDeg?: number;
+ effectInterval?: number;
+ effectCount?: number;
+ colors?: string[];
+}
+
+function Confetti({
+ x = 0.5,
+ y = 0.5,
+ particleCount = 30,
+ deg = 270,
+ shapeSize = 12,
+ spreadDeg = 30,
+ effectInterval = 3000,
+ effectCount = 1,
+ colors = ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'],
+}: Props) {
const canvasRef = useRef(null);
- const ctxRef = useRef(null);
- const particles: Particle[] = [];
- const animationFrameRef = useRef(0);
+ const ctxRef = useRef();
+ const particlesRef = useRef([]);
+ const animationFrameRef = useRef(0);
+ const effectCountRef = useRef(0);
const init = useCallback(() => {
const canvas = canvasRef.current;
@@ -32,21 +55,23 @@ function Confetti() {
(options: {
x: number;
y: number;
- count: number;
- deg?: number;
- colors?: string[];
- shapes?: ('circle' | 'square')[];
- spread?: number;
+ particleCount: number;
+ deg: number;
+ shapeSize: number;
+ spreadDeg: number;
+ colors: string[];
+ shapes?: readonly ['circle', 'square'];
}) => {
- for (let i = 0; i < options.count; i++) {
- particles.push(
+ for (let i = 0; i < options.particleCount; i += 1) {
+ particlesRef.current.push(
new Particle(
options.x,
options.y,
options.deg,
options.colors,
options.shapes,
- options.spread,
+ options.shapeSize,
+ options.spreadDeg,
),
);
}
@@ -55,61 +80,93 @@ function Confetti() {
);
const render = useCallback(() => {
- const canvas = canvasRef.current;
- if (!ctxRef.current || !canvas) return;
+ if (!ctxRef.current) return;
let now;
let delta;
let then = Date.now();
+ let effectDelta;
+ let effectThen = Date.now() - effectInterval;
const frame = () => {
+ const 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);
- createConfetti({
- x: 0, // 0 ~ 1
- y: 0.5, // 0 ~ 1
- count: 10,
- deg: -50,
- });
- createConfetti({
- x: 1, // 0 ~ 1
- y: 0.5, // 0 ~ 1
- count: 10,
- deg: 230,
- });
-
- for (let i = particles.length - 1; i >= 0; i--) {
+ if (
+ effectDelta > effectInterval &&
+ effectCountRef.current < effectCount
+ ) {
+ createConfetti({
+ x,
+ y,
+ particleCount,
+ deg,
+ shapeSize,
+ spreadDeg,
+ colors,
+ });
+ effectThen = now - (effectDelta % effectInterval);
+ effectCountRef.current += 1;
+ }
+
+ const particles = particlesRef.current;
+ for (let i = particles.length - 1; i >= 0; i -= 1) {
const p = particles[i];
p.update();
p.draw(ctxRef.current);
- if (p.opacity <= 0) particles.splice(particles.indexOf(p), 1);
- if (p.y > window.innerHeight) particles.splice(particles.indexOf(p), 1);
+
+ const canvasHeight = 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);
- }, []);
+ }, [
+ x,
+ y,
+ particleCount,
+ deg,
+ effectInterval,
+ shapeSize,
+ effectCount,
+ spreadDeg,
+ colors,
+ createConfetti,
+ ]);
useEffect(() => {
init();
render();
return () => {
- if (animationFrameRef.current)
+ if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
+ }
};
- }, []);
+ }, [init, render]);
+
+ useEffect(() => {
+ effectCountRef.current = 0;
+ // render();
+ }, [effectCount]);
return ;
}
-export { Confetti };
+export default Confetti;
diff --git a/src/js/Particle.ts b/src/js/Particle.ts
index 81645f2..a964554 100644
--- a/src/js/Particle.ts
+++ b/src/js/Particle.ts
@@ -1,4 +1,4 @@
-import { hexToRgb, randomNumBetween } from './utils';
+import { randomNumBetween, hexToRgb } from './utils';
class Particle {
x: number;
@@ -18,27 +18,32 @@ class Particle {
rotateDelta: number;
colors: string[];
color: { r: number; g: number; b: number };
- shapes: string[];
+ shapes: readonly ['circle', 'square'];
shape: string;
+ swingOffset: number;
+ swingSpeed: number;
+ swingAmplitude: number;
constructor(
x: number,
y: number,
deg = 0,
- colors: string[] = ['#ff577f', '#ff884b', '#ffd384', '#fff9b0'],
- shapes: ('circle' | 'square')[] = ['circle', 'square'],
+ colors: string[],
+ shapes = ['circle', 'square'] as const,
+ shapeSize = 12,
spread = 30,
) {
- this.x = x * window.innerWidth;
- this.y = y * window.innerHeight;
- this.width = 12;
- this.height = 12;
+ const 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(30, 100);
+ this.radius = randomNumBetween(20, 70);
this.vx = this.radius * Math.cos(this.theta);
this.vy = this.radius * Math.sin(this.theta);
- this.friction = 0.89;
- this.gravity = 0.5;
+ this.friction = 0.87;
+ this.gravity = 0.55;
this.opacity = 1;
this.rotate = randomNumBetween(0, 360);
this.widthDelta = randomNumBetween(0, 360);
@@ -49,20 +54,24 @@ class Particle {
this.colors[Math.floor(randomNumBetween(0, this.colors.length))],
);
this.shapes = shapes;
- this.shape =
- this.shapes[Math.floor(randomNumBetween(0, this.shapes.length))];
+ 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, 0.4);
}
update() {
- this.vy += this.gravity;
this.vx *= this.friction;
this.vy *= this.friction;
+ this.vy += this.gravity;
+ if (this.vy > 0) this.vx += Math.sin(this.swingOffset) * this.swingAmplitude;
this.x += this.vx;
this.y += this.vy;
- this.opacity -= 0.005;
+ this.opacity -= 0.004;
this.widthDelta += 2;
this.heightDelta += 2;
this.rotate += this.rotateDelta;
+ this.swingOffset += this.swingSpeed;
}
drawSquare(ctx: CanvasRenderingContext2D) {
@@ -95,6 +104,7 @@ class Particle {
ctx.translate(this.x + translateXAlpha, this.y + translateYAlpha);
ctx.rotate((Math.PI / 180) * this.rotate);
ctx.translate(-(this.x + translateXAlpha), -(this.y + translateYAlpha));
+ // eslint-disable-next-line no-param-reassign
ctx.fillStyle = `rgba(${this.color.r}, ${this.color.g}, ${this.color.b}, ${this.opacity})`;
if (this.shape === 'square') this.drawSquare(ctx);
diff --git a/src/type.d.ts b/src/type.d.ts
index 8802366..5060ece 100644
--- a/src/type.d.ts
+++ b/src/type.d.ts
@@ -1 +1,7 @@
declare module "*.module.scss";
+
+type ConstructorParams = {
+ [K in keyof T as T[K] extends (...args: any[]) => any
+ ? never
+ : K]: T[K];
+};