├── example ├── sveltekit │ ├── .npmrc │ ├── src │ │ ├── global.d.ts │ │ ├── app.html │ │ └── routes │ │ │ └── index.svelte │ ├── static │ │ ├── favicon.png │ │ └── global.css │ ├── .gitignore │ ├── jsconfig.json │ ├── svelte.config.js │ ├── package.json │ ├── README.md │ └── yarn.lock ├── svelte │ ├── .gitignore │ ├── public │ │ ├── favicon.png │ │ ├── index.html │ │ └── global.css │ ├── src │ │ ├── main.js │ │ └── App.svelte │ ├── package.json │ ├── rollup.config.js │ ├── README.md │ └── yarn.lock ├── example.gif ├── example.png ├── test │ ├── star.png │ ├── sparkle.png │ ├── snowflake.png │ ├── test.html │ └── test.js ├── jquery │ ├── jquery.js │ └── jquery.html ├── esm │ ├── esm.js │ ├── yarn.lock │ ├── package.json │ └── esm.html ├── vanilla │ ├── vanilla.js │ └── vanilla.html └── example.css ├── .editorconfig ├── .prettierrc.json ├── babel.config.js ├── CHANGELOG.md ├── .gitignore ├── src ├── animationFrame.js ├── helpers.js ├── sparticle.js └── sparticles.js ├── package.json ├── rollup.config.js ├── dist ├── sparticles.min.js └── sparticles.esm.js ├── LICENSE └── README.md /example/sveltekit/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /example/sveltekit/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /example/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeydotme/sparticles/HEAD/example/example.gif -------------------------------------------------------------------------------- /example/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeydotme/sparticles/HEAD/example/example.png -------------------------------------------------------------------------------- /example/test/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeydotme/sparticles/HEAD/example/test/star.png -------------------------------------------------------------------------------- /example/test/sparkle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeydotme/sparticles/HEAD/example/test/sparkle.png -------------------------------------------------------------------------------- /example/test/snowflake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeydotme/sparticles/HEAD/example/test/snowflake.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | indent_style = space 5 | indent_size = 2 -------------------------------------------------------------------------------- /example/svelte/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeydotme/sparticles/HEAD/example/svelte/public/favicon.png -------------------------------------------------------------------------------- /example/sveltekit/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simeydotme/sparticles/HEAD/example/sveltekit/static/favicon.png -------------------------------------------------------------------------------- /example/sveltekit/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /example/jquery/jquery.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | var $main = $("main"); 4 | window.mySparticles = new Sparticles($main.get(0)); 5 | 6 | }); 7 | -------------------------------------------------------------------------------- /example/esm/esm.js: -------------------------------------------------------------------------------- 1 | import Sparticles from "./node_modules/sparticles/dist/sparticles.mjs"; 2 | 3 | new Sparticles( document.querySelector("main") ); 4 | -------------------------------------------------------------------------------- /example/esm/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | sparticles@../../: 6 | version "1.3.0" 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "printWidth": 100, 4 | "semi": true, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "es5" 8 | } 9 | -------------------------------------------------------------------------------- /example/svelte/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.body, 5 | props: { 6 | name: 'world' 7 | } 8 | }); 9 | 10 | export default app; -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = [ 2 | [ 3 | "@babel/preset-env", 4 | { 5 | targets: { 6 | ie: "9", 7 | }, 8 | }, 9 | ], 10 | ]; 11 | 12 | module.exports = { presets }; 13 | -------------------------------------------------------------------------------- /example/vanilla/vanilla.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 4 | window.onload = function() { 5 | var $main = document.querySelector("main"); 6 | window.mySparticles = new Sparticles($main); 7 | } 8 | 9 | }()); 10 | -------------------------------------------------------------------------------- /example/sveltekit/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "$lib": ["src/lib"], 6 | "$lib/*": ["src/lib/*"] 7 | } 8 | }, 9 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 10 | } 11 | -------------------------------------------------------------------------------- /example/esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparticles-esm-test", 3 | "version": "1.0.0", 4 | "description": "test for importing sparticles to a browser esm setup", 5 | "main": "esm.html", 6 | "author": "me", 7 | "license": "MIT", 8 | "private": true, 9 | "dependencies": { 10 | "sparticles": "../../" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/sveltekit/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | adapter: adapter(), 7 | 8 | // hydrate the
element in src/app.html 9 | target: 'body' 10 | } 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /example/sveltekit/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %svelte.head% 10 | 11 | 12 | %svelte.body% 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/sveltekit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "svelte-kit dev", 6 | "build": "svelte-kit build", 7 | "package": "svelte-kit package", 8 | "preview": "svelte-kit preview" 9 | }, 10 | "devDependencies": { 11 | "@sveltejs/adapter-auto": "next", 12 | "@sveltejs/kit": "next", 13 | "svelte": "^3.44.0", 14 | "sparticles": "../../" 15 | }, 16 | "type": "module" 17 | } -------------------------------------------------------------------------------- /example/svelte/src/App.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Sparticles in Svelte

11 |

Visit svelte.dev to read the documentation

12 |
13 | 14 | 19 | -------------------------------------------------------------------------------- /example/svelte/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/sveltekit/src/routes/index.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Sparticles in SvelteKit

11 |

Visit kit.svelte.dev to read the documentation

12 |
13 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /example/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "rollup -c", 6 | "dev": "rollup -c -w", 7 | "start": "sirv public" 8 | }, 9 | "devDependencies": { 10 | "@rollup/plugin-commonjs": "^11.0.0", 11 | "@rollup/plugin-node-resolve": "^7.0.0", 12 | "rollup": "^1.20.0", 13 | "rollup-plugin-livereload": "^1.0.0", 14 | "rollup-plugin-svelte": "^5.0.3", 15 | "rollup-plugin-terser": "^5.1.2", 16 | "sparticles": "../../", 17 | "svelte": "^3.0.0" 18 | }, 19 | "dependencies": { 20 | "sirv-cli": "^0.4.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/esm/esm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Sparticles with Vanilla Javascript 9 | 10 | 11 |
12 |
13 | 14 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Sparticles Test Area 13 | 14 | 15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /example/vanilla/vanilla.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Sparticles with Vanilla Javascript 10 | 11 | 12 |
13 |
14 | 15 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/example.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | color: rgb(237, 231, 255); 7 | background: rgb(33, 32, 39); 8 | padding: 10px; 9 | margin: 0; 10 | height: 100%; 11 | } 12 | 13 | main { 14 | height: 100%; 15 | } 16 | 17 | nav { 18 | position: fixed; 19 | bottom: 10px; 20 | left: 10px; 21 | } 22 | 23 | nav ul { 24 | list-style: none; 25 | } 26 | 27 | nav li { 28 | margin: 0 10px 0 -2px; 29 | padding: 0 10px 0 0; 30 | display: inline-block; 31 | } 32 | 33 | nav li:not(:last-child) { 34 | border-right: 1px solid white; 35 | } 36 | 37 | nav a { 38 | color: rgba(255, 255, 255, 0.6); 39 | line-height: 1.4; 40 | display: inline-block; 41 | } 42 | 43 | nav a.current, 44 | nav a:hover, 45 | nav a:active, 46 | nav a:focus { 47 | color: white; 48 | } 49 | -------------------------------------------------------------------------------- /example/svelte/public/global.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | color: rgb(237, 231, 255); 7 | background: rgb(33, 32, 39); 8 | padding: 10px; 9 | margin: 0; 10 | height: 100%; 11 | } 12 | 13 | nav { 14 | position: fixed; 15 | bottom: 10px; 16 | left: 10px; 17 | } 18 | 19 | nav ul { 20 | list-style: none; 21 | } 22 | 23 | nav li { 24 | margin: 0 10px 0 -2px; 25 | padding: 0 10px 0 0; 26 | display: inline-block; 27 | } 28 | 29 | nav li:not(:last-child) { 30 | border-right: 1px solid white; 31 | } 32 | 33 | nav a { 34 | color: rgba(255, 255, 255, 0.6); 35 | line-height: 1.4; 36 | display: inline-block; 37 | } 38 | 39 | nav a.current, 40 | nav a:hover, 41 | nav a:active, 42 | nav a:focus { 43 | color: white; 44 | } 45 | 46 | canvas { 47 | position: absolute; 48 | inset: 0; 49 | } -------------------------------------------------------------------------------- /example/jquery/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Sparticles with jQuery 11 | 12 | 13 |
14 |
15 | 16 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/sveltekit/static/global.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | color: rgb(237, 231, 255); 7 | background: rgb(33, 32, 39); 8 | padding: 10px; 9 | margin: 0; 10 | height: 100%; 11 | } 12 | 13 | nav { 14 | position: fixed; 15 | bottom: 10px; 16 | left: 10px; 17 | } 18 | 19 | nav ul { 20 | list-style: none; 21 | } 22 | 23 | nav li { 24 | margin: 0 10px 0 -2px; 25 | padding: 0 10px 0 0; 26 | display: inline-block; 27 | } 28 | 29 | nav li:not(:last-child) { 30 | border-right: 1px solid white; 31 | } 32 | 33 | nav a { 34 | color: rgba(255, 255, 255, 0.6); 35 | line-height: 1.4; 36 | display: inline-block; 37 | } 38 | 39 | nav a.current, 40 | nav a:hover, 41 | nav a:active, 42 | nav a:focus { 43 | color: white; 44 | } 45 | 46 | canvas { 47 | position: absolute; 48 | inset: 0; 49 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | ## 1.1.0 3 | 4 | - shapes and images can now be used together in an array to mix basic shapes and custom images; 5 | ```js 6 | let mySparticles = new Sparticles( el, { 7 | 8 | shape: ["circle", "image"], 9 | imageUrl: "./snowflake.png" 10 | 11 | }) 12 | ``` 13 | 14 | - fixed some performance / looping errors 15 | - refactored/renamed a lot of prototype methods 16 | 17 | - added the ability to write custom shapes in the off-chance that's 18 | better/easier than using a custom svg-image 19 | ```js 20 | // first make sure the Sparticles is ready in page. 21 | // then you can add a custom offScreenCanvas before initialising. 22 | Sparticles.prototype.offScreenCanvas.doge = function(style, color, canvas) { 23 | // code for custom shape here, access to "this" object 24 | // which is the Sparticles prototype. 25 | }; 26 | // then initialise your sparticles instance with the custom shape 27 | let mySparticles = new Sparticles( el, { shape: "doge" }); 28 | 29 | 30 | ``` 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /example/sveltekit/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte); 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm init svelte@next 12 | 13 | # create a new project in my-app 14 | npm init svelte@next my-app 15 | ``` 16 | 17 | > Note: the `@next` is temporary 18 | 19 | ## Developing 20 | 21 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 22 | 23 | ```bash 24 | npm run dev 25 | 26 | # or start the server and open the app in a new browser tab 27 | npm run dev -- --open 28 | ``` 29 | 30 | ## Building 31 | 32 | Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then: 33 | 34 | ```bash 35 | npm run build 36 | ``` 37 | 38 | > You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. 39 | -------------------------------------------------------------------------------- /src/animationFrame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Limited Animation Frame method, to allow running 3 | * a given handler at the maximum desired frame rate. 4 | * inspired from https://gist.github.com/addyosmani/5434533 5 | * @param {Function} handler method to execute upon every frame 6 | * @param {Number} fps how many frames to render every second 7 | */ 8 | export const AnimationFrame = function(handler = () => {}, fps = 60) { 9 | this.fps = fps; 10 | this.handler = handler; 11 | let renderId = 0; 12 | 13 | /** 14 | * begin the animation loop which is assigned 15 | * to the instance in the constructor 16 | */ 17 | this.start = function() { 18 | if (!this.started) { 19 | let then = performance.now(); 20 | const interval = 1000 / this.fps; 21 | const tolerance = 0; 22 | 23 | const loop = now => { 24 | const delta = now - then; 25 | renderId = requestAnimationFrame(loop); 26 | if (delta >= interval - tolerance) { 27 | this.handler(delta); 28 | then = now - (delta % interval); 29 | } 30 | }; 31 | 32 | renderId = requestAnimationFrame(loop); 33 | this.started = true; 34 | } 35 | }; 36 | 37 | /** 38 | * stop the currently running animation loop 39 | */ 40 | this.stop = function() { 41 | cancelAnimationFrame(renderId); 42 | this.started = false; 43 | }; 44 | }; 45 | 46 | export default AnimationFrame; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sparticles", 3 | "version": "1.3.1", 4 | "description": "Lightweight, High Performance Particles in Canvas", 5 | "main": "dist/sparticles.mjs", 6 | "files": [ 7 | "dist/sparticles.js", 8 | "dist/sparticles.min.js", 9 | "dist/sparticles.esm.js", 10 | "src/sparticles.js" 11 | ], 12 | "repository": "https://github.com/simeydotme/sparticles.git", 13 | "author": "simeydotme ", 14 | "homepage": "http://sparticlesjs.dev", 15 | "license": "MPL-2.0", 16 | "private": false, 17 | "scripts": { 18 | "build": "rollup --config", 19 | "dev": "rollup --config --watch", 20 | "lint": "yarn -s lint-code && yarn -s lint-config", 21 | "lint-code": "echo '\n✨ linting source code...\n' && prettier --write 'src/**/*.js'", 22 | "lint-config": "echo '\n✨ linting config files...\n' && prettier --write '*.js' && echo ''", 23 | "lint-staged": "pretty-quick --staged --pattern 'src/**/*.js' && pretty-quick --pattern '*.js'" 24 | }, 25 | "husky": { 26 | "hooks": { 27 | "pre-commit": "yarn lint-staged" 28 | } 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "^7.7.2", 32 | "@babel/preset-env": "^7.7.1", 33 | "@rollup/plugin-babel": "^5.2.1", 34 | "dat.gui": "^0.7.6", 35 | "husky": "^3.0.9", 36 | "lodash": "^4.17.19", 37 | "prettier": "^1.19.1", 38 | "pretty-quick": "^2.0.1", 39 | "rollup": "^1.32.0", 40 | "rollup-plugin-filesize": "^6.2.1", 41 | "rollup-plugin-livereload": "^1.0.4", 42 | "rollup-plugin-serve": "^1.0.1", 43 | "rollup-plugin-terser": "^5.3.0", 44 | "stats.js": "^0.17.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/svelte/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | 7 | const production = !process.env.ROLLUP_WATCH; 8 | 9 | export default { 10 | input: 'src/main.js', 11 | output: { 12 | sourcemap: true, 13 | format: 'iife', 14 | name: 'app', 15 | file: 'public/build/bundle.js' 16 | }, 17 | plugins: [ 18 | svelte({ 19 | // enable run-time checks when not in production 20 | dev: !production, 21 | // we'll extract any component CSS out into 22 | // a separate file - better for performance 23 | css: css => { 24 | css.write('public/build/bundle.css'); 25 | } 26 | }), 27 | 28 | // If you have external dependencies installed from 29 | // npm, you'll most likely need these plugins. In 30 | // some cases you'll need additional configuration - 31 | // consult the documentation for details: 32 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 33 | resolve({ 34 | browser: true, 35 | dedupe: ['svelte'] 36 | }), 37 | commonjs(), 38 | 39 | // In dev mode, call `npm run start` once 40 | // the bundle has been generated 41 | !production && serve(), 42 | 43 | // Watch the `public` directory and refresh the 44 | // browser on changes when not in production 45 | !production && livereload('public'), 46 | 47 | // If we're building for production (npm run build 48 | // instead of npm run dev), minify 49 | production && terser() 50 | ], 51 | watch: { 52 | clearScreen: false 53 | } 54 | }; 55 | 56 | function serve() { 57 | let started = false; 58 | 59 | return { 60 | writeBundle() { 61 | if (!started) { 62 | started = true; 63 | 64 | require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 65 | stdio: ['ignore', 'inherit', 'inherit'], 66 | shell: true 67 | }); 68 | } 69 | } 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { terser } from "rollup-plugin-terser"; 2 | import babel from "@rollup/plugin-babel"; 3 | import filesize from "rollup-plugin-filesize"; 4 | import livereload from "rollup-plugin-livereload"; 5 | import serve from "rollup-plugin-serve"; 6 | import pkg from "./package.json"; 7 | 8 | const dev = process.env.ROLLUP_WATCH; 9 | const prod = !dev; 10 | const banner = () => `/**! 11 | * Sparticles - ${pkg.description} 12 | * @version ${pkg.version} 13 | * @license ${pkg.license} 14 | * @author ${pkg.author} 15 | * @website ${pkg.homepage} 16 | * @repository ${pkg.repository} 17 | */ 18 | `; 19 | 20 | export default [ 21 | { 22 | input: "src/sparticles.js", 23 | output: [ 24 | { 25 | file: "dist/sparticles.esm.js", 26 | format: "esm", 27 | banner: banner(), 28 | plugins: [filesize()], 29 | }, 30 | { 31 | file: "dist/sparticles.mjs", 32 | format: "esm", 33 | banner: banner(), 34 | plugins: [filesize()], 35 | }, 36 | { 37 | name: "Sparticles", 38 | file: "dist/sparticles.js", 39 | format: "iife", 40 | banner: banner(), 41 | plugins: [filesize()], 42 | }, 43 | { 44 | name: "Sparticles", 45 | file: "dist/sparticles.min.js", 46 | format: "iife", 47 | banner: banner(), 48 | plugins: [prod && terser()], 49 | }, 50 | ], 51 | plugins: [ 52 | babel({ 53 | babelrc: false, 54 | exclude: "node_modules/**", 55 | babelHelpers: "bundled", 56 | }), 57 | dev && 58 | serve({ 59 | contentBase: "", 60 | host: "localhost", 61 | openPage: "/example/vanilla/vanilla.html", 62 | port: 5555, 63 | }), 64 | dev && livereload(), 65 | ], 66 | watch: { 67 | include: "src/**", 68 | chokidar: { 69 | usePolling: true, 70 | interval: 2000, 71 | binaryInterval: 2000, 72 | }, 73 | buildDelay: 1000, 74 | }, 75 | }, 76 | ]; 77 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * return the cartesian x/y delta value from a degree 3 | * eg: 90 (→) = [1,0] 4 | * @param {Number} angle angle in degrees 5 | * @returns {Number[]} cartesian delta values 6 | */ 7 | export const cartesian = angle => { 8 | return [Math.cos(radian(angle - 90)), Math.sin(radian(angle - 90))]; 9 | }; 10 | 11 | /** 12 | * clamp the input number to the min/max values 13 | * @param {Number} value value to clamp between min and max 14 | * @param {Number} min minimum value possible 15 | * @param {Number} max maximum value possible 16 | * @returns {Number} the input num clamped between min/max 17 | */ 18 | export const clamp = (value, min = 0, max = 1) => { 19 | return Math.max(min, Math.min(max, value)); 20 | }; 21 | /** 22 | * return the radian equivalent to a degree value 23 | * @param {Number} angle angle in degrees 24 | * @returns {Number} radian equivalent 25 | */ 26 | export const radian = angle => { 27 | return (angle * Math.PI) / 180; 28 | }; 29 | 30 | /** 31 | * return random number between a min and max value 32 | * @param {Number} min minimum value 33 | * @param {Number} max maximum value 34 | * @param {Boolean} rounded should the result be rounded 35 | * @returns {Number} a random number between min and max 36 | */ 37 | export const random = (min = 0, max = 1, value = Math.random()) => { 38 | if (max <= min) { 39 | value = min; 40 | } else if ((min !== 0 || max !== 1) && max > min) { 41 | value = value * (max - min) + min; 42 | } 43 | return value; 44 | }; 45 | 46 | /** 47 | * return a random value from an array 48 | * @param {Array} array an array to get random value from 49 | * @returns {*} random value from array 50 | */ 51 | export const randomArray = array => { 52 | return array[Math.floor(random(0, array.length))]; 53 | }; 54 | 55 | /** 56 | * return a random HSL colour string for use in random color effect 57 | * @returns {String} "hsl(100,100,80)" 58 | */ 59 | export const randomHsl = () => { 60 | const h = round(random(0, 360)); 61 | const s = round(random(90, 100)); 62 | const l = round(random(45, 85)); 63 | return `hsl(${h},${s}%,${l}%)`; 64 | }; 65 | 66 | /** 67 | * return a boolean to pass a dice roll 68 | * @param {Number} odds a fraction to use as the probability, can be supplied as "1/2" 69 | * @returns {Boolean} 70 | */ 71 | export const roll = odds => { 72 | return odds > random(); 73 | }; 74 | 75 | /** 76 | * round a number to the nearest integer value 77 | * @param {Number} value value to round to the nearest integer 78 | * @returns {Number} nearest integer 79 | */ 80 | export const round = value => { 81 | return (0.5 + value) | 0; 82 | }; 83 | -------------------------------------------------------------------------------- /example/svelte/README.md: -------------------------------------------------------------------------------- 1 | *Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)* 2 | 3 | --- 4 | 5 | # svelte app 6 | 7 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template. 8 | 9 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit): 10 | 11 | ```bash 12 | npx degit sveltejs/template svelte-app 13 | cd svelte-app 14 | ``` 15 | 16 | *Note that you will need to have [Node.js](https://nodejs.org) installed.* 17 | 18 | 19 | ## Get started 20 | 21 | Install the dependencies... 22 | 23 | ```bash 24 | cd svelte-app 25 | npm install 26 | ``` 27 | 28 | ...then start [Rollup](https://rollupjs.org): 29 | 30 | ```bash 31 | npm run dev 32 | ``` 33 | 34 | Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes. 35 | 36 | By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`. 37 | 38 | 39 | ## Building and running in production mode 40 | 41 | To create an optimised version of the app: 42 | 43 | ```bash 44 | npm run build 45 | ``` 46 | 47 | You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com). 48 | 49 | 50 | ## Single-page app mode 51 | 52 | By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere. 53 | 54 | If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json: 55 | 56 | ```js 57 | "start": "sirv public --single" 58 | ``` 59 | 60 | 61 | ## Deploying to the web 62 | 63 | ### With [now](https://zeit.co/now) 64 | 65 | Install `now` if you haven't already: 66 | 67 | ```bash 68 | npm install -g now 69 | ``` 70 | 71 | Then, from within your project folder: 72 | 73 | ```bash 74 | cd public 75 | now deploy --name my-project 76 | ``` 77 | 78 | As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon. 79 | 80 | ### With [surge](https://surge.sh/) 81 | 82 | Install `surge` if you haven't already: 83 | 84 | ```bash 85 | npm install -g surge 86 | ``` 87 | 88 | Then, from within your project folder: 89 | 90 | ```bash 91 | npm run build 92 | surge public my-project.surge.sh 93 | ``` 94 | -------------------------------------------------------------------------------- /example/test/test.js: -------------------------------------------------------------------------------- 1 | 2 | let colorType = { 3 | type: "single" 4 | }; 5 | 6 | let colors = { 7 | color1: "rgba(244,0,0,1)", 8 | color2: "rgba(252,248,230,1)", 9 | color3: "rgba(255,227,241,1)", 10 | color4: "rgba(230,248,255,1)" 11 | }; 12 | 13 | let options = { 14 | alphaSpeed: 3, 15 | alphaVariance: 8, 16 | color: colors.color1, 17 | randomColorCount: 7, 18 | composition: "source-over", 19 | bounce: false, 20 | count: 200, 21 | direction: 180, 22 | drift: 0, 23 | glow: 0, 24 | imageUrl: [ 25 | "./snowflake.png", 26 | "./star.png", 27 | "./star.png", 28 | "./star.png", 29 | "./star.png", 30 | "./star.png", 31 | "./star.png", 32 | "./star.png", 33 | "./star.png", 34 | "./star.png" 35 | ], 36 | maxAlpha: 1, 37 | maxSize: 50, 38 | minAlpha: 1, 39 | minSize: 20, 40 | parallax: 0, 41 | rotate: false, 42 | rotation: 0, 43 | shape: ["star"], 44 | speed: 1, 45 | style: "both", 46 | twinkle: false, 47 | xVariance: 0, 48 | yVariance: 0, 49 | }; 50 | 51 | window.onload = function() { 52 | 53 | Sparticles.prototype.offScreenCanvas.wow = function(style, color, canvas) { 54 | const ctx = canvas.getContext("2d"); 55 | const size = this.settings.maxSize; 56 | const lineSize = this.getLineSize(size); 57 | const glowSize = this.getGlowSize(size); 58 | const canvasSize = size + lineSize + glowSize; 59 | canvas.width = canvasSize; 60 | canvas.height = canvasSize; 61 | this.renderGlow(ctx, color, size); 62 | this.renderStyle(ctx, color, lineSize, style); 63 | ctx.beginPath(); 64 | ctx.ellipse(canvasSize / 2, canvasSize / 2, size / 2, size / 2, 0, 0, 360); 65 | this.renderColor(ctx, style); 66 | return canvas; 67 | }; 68 | 69 | 70 | initStats(); 71 | initSparticles(); 72 | initGui(); 73 | } 74 | 75 | window.initSparticles = function() { 76 | var $main = document.querySelector("main"); 77 | window.mySparticles = new Sparticles($main,JSON.parse(JSON.stringify(options))); 78 | }; 79 | 80 | window.initStats = function() { 81 | var stats = new Stats(); 82 | document.body.appendChild(stats.dom); 83 | function statsDisplay() { 84 | stats.begin(); 85 | stats.end(); 86 | requestAnimationFrame(statsDisplay); 87 | } 88 | requestAnimationFrame(statsDisplay); 89 | }; 90 | 91 | window.initGui = function() { 92 | let s = window.mySparticles; 93 | const shapes = ["random", "circle", "square", "triangle", "diamond", "star", "line", "image"]; 94 | const styles = ["fill", "stroke", "both"]; 95 | const colorOptions = ["single", "multi", "random"]; 96 | const composites = [ 97 | "source-over", 98 | "source-in", 99 | "source-out", 100 | "source-atop", 101 | "destination-over", 102 | "destination-in", 103 | "destination-out", 104 | "destination-atop", 105 | "lighter", 106 | "copy", 107 | "xor", 108 | "multiply", 109 | "screen", 110 | "overlay", 111 | "darken", 112 | "color-dodge", 113 | "color-burn", 114 | "hard-light", 115 | "soft-light", 116 | "difference", 117 | "exclusion", 118 | "hue", 119 | "saturation", 120 | "color", 121 | "luminosity" 122 | ]; 123 | 124 | const rerender = () => { 125 | if( window.mySparticles && window.mySparticles instanceof Sparticles ) { 126 | try { 127 | window.mySparticles.destroy(); 128 | } catch(e) { 129 | document.querySelector("main").removeChild( s.canvas ); 130 | } 131 | } 132 | window.initSparticles(); 133 | }; 134 | 135 | var rerenderColors = function(v) { 136 | if (colorType.type === "random") { 137 | options.color = "random"; 138 | } else if (colorType.type === "single") { 139 | options.color = colors.color1; 140 | } else { 141 | options.color = Object.keys(colors).map(i => { 142 | return colors[i]; 143 | }); 144 | } 145 | rerender(); 146 | }; 147 | 148 | const gui = new dat.GUI({ load: options }); 149 | const part = gui.addFolder("Particles"); 150 | part.open(); 151 | part.add(options, "count", 1, 500, 1).onFinishChange(rerender); 152 | part.add(options, "shape", shapes).onFinishChange(rerender); 153 | part.add(options, "style", styles).onFinishChange(rerender); 154 | part.add(options, "rotate").onFinishChange(rerender); 155 | part.add(options, "bounce").onFinishChange(rerender); 156 | const image = part.addFolder("Image"); 157 | // image.add(options, "imageUrl").onFinishChange(rerender); 158 | part.add(options, "minSize", 1, 250, 1).onFinishChange(rerender); 159 | part.add(options, "maxSize", 1, 250, 1).onFinishChange(rerender); 160 | const anim = gui.addFolder("Animation"); 161 | anim.add(options, "direction", 0, 360, 1).onFinishChange(rerender); 162 | anim.add(options, "speed", 0, 100, 0.1).onFinishChange(rerender); 163 | anim.add(options, "rotation", 0, 100, 0.1).onFinishChange(rerender); 164 | const move = anim.addFolder("Movement"); 165 | move.add(options, "parallax", 0, 10, 0.1).onFinishChange(rerender); 166 | move.add(options, "drift", 0, 30, 0.01).onFinishChange(rerender); 167 | move.add(options, "xVariance", 0, 20, 0.1).onFinishChange(rerender); 168 | move.add(options, "yVariance", 0, 20, 0.1).onFinishChange(rerender); 169 | const vis = gui.addFolder("Visual"); 170 | vis.add(options, "glow", 0,150).onFinishChange(rerender); 171 | vis.add(options, "composition", composites).onFinishChange(rerender); 172 | const alpha = vis.addFolder("Alpha"); 173 | alpha.add(options, "twinkle").onFinishChange(rerender); 174 | alpha.add(options, "minAlpha", -2, 2, 0.1).onFinishChange(rerender); 175 | alpha.add(options, "maxAlpha", -2, 2, 0.1).onFinishChange(rerender); 176 | alpha.add(options, "alphaSpeed", 0, 50, 1).onFinishChange(rerender); 177 | alpha.add(options, "alphaVariance", 0, 20, 1).onFinishChange(rerender); 178 | const color = vis.addFolder("Color"); 179 | color.open(); 180 | color.add(colorType, "type", colorOptions).onFinishChange(rerenderColors); 181 | color.addColor(colors, "color1").onFinishChange(rerenderColors); 182 | color.addColor(colors, "color2").onFinishChange(rerenderColors); 183 | color.addColor(colors, "color3").onFinishChange(rerenderColors); 184 | color.addColor(colors, "color4").onFinishChange(rerenderColors); 185 | const control = gui.addFolder("Controls"); 186 | control.add(s,"start"); 187 | control.add(s,"stop"); 188 | }; 189 | -------------------------------------------------------------------------------- /dist/sparticles.min.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * Sparticles - Lightweight, High Performance Particles in Canvas 3 | * @version 1.3.1 4 | * @license MPL-2.0 5 | * @author simeydotme 6 | * @website http://sparticlesjs.dev 7 | * @repository https://github.com/simeydotme/sparticles.git 8 | */ 9 | var Sparticles=function(){"use strict";function t(t,e){var i=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),i.push.apply(i,s)}return i}function e(e){for(var s=1;s0&&void 0!==arguments[0]?arguments[0]:function(){},e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:60;this.fps=e,this.handler=t;var i=0;this.start=function(){var t=this;if(!this.started){var e=performance.now(),s=1e3/this.fps;i=requestAnimationFrame((function n(r){var h=r-e;i=requestAnimationFrame(n),h>=s-0&&(t.handler(h),e=r-h%s)})),this.started=!0}},this.stop=function(){cancelAnimationFrame(i),this.started=!1}},n=function(t){return[Math.cos(h(t-90)),Math.sin(h(t-90))]},r=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1;return Math.max(e,Math.min(i,t))},h=function(t){return t*Math.PI/180},a=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:Math.random();return e<=t?i=t:(0!==t||1!==e)&&e>t&&(i=i*(e-t)+t),i},o=function(t){return t[Math.floor(a(0,t.length))]},c=function(){var t=p(a(0,360)),e=p(a(90,100)),i=p(a(45,85));return"hsl(".concat(t,",").concat(e,"%,").concat(i,"%)")},l=function(t){return t>a()},p=function(t){return.5+t|0},g=function(t){return t?(this.canvas=t.canvas,this.settings=t.settings,this.colors=t.colors,this.shapes=t.shapes,this.images=t.images,this.styles=t.styles,this.ctx=t.canvas.getContext("2d"),this.setup(),this.init()):console.warn("Invalid parameters given to Sparticle()",arguments),this};g.prototype.setup=function(){var t=this.settings;this.frame=0,this.frameoffset=p(a(0,360)),this.size=p(a(t.minSize,t.maxSize)),this.da=this.getAlphaDelta(),this.dx=this.getDeltaX(),this.dy=this.getDeltaY(),this.dd=this.getDriftDelta(),this.dr=this.getRotationDelta(),this.color=this.getColor(),this.shape=this.getShape(),this.image=this.getImage(),this.style=this.getStyle(),this.rotation=t.rotate?h(a(0,360)):0,this.vertical=t.direction>150&&t.direction<210||t.direction>330&&t.direction<390||t.direction>-30&&t.direction<30,this.horizontal=t.direction>60&&t.direction<120||t.direction>240&&t.direction<300},g.prototype.init=function(){var t=this.settings,e=this.canvas;this.alpha=0,(t.speed>0||0===t.alphaSpeed)&&(this.alpha=a(t.minAlpha,t.maxAlpha)),t.bounce?(this.px=p(a(2,e.width-this.size-2)),this.py=p(a(2,e.height-this.size-2))):(this.px=p(a(2*-this.size,e.width+this.size)),this.py=p(a(2*-this.size,e.height+this.size)))},g.prototype.reset=function(){this.setup(),this.py<0?this.py=this.canvas.height+2*this.size:this.py>this.canvas.height&&(this.py=0-2*this.size),this.px<0?this.px=this.canvas.width+2*this.size:this.px>this.canvas.width&&(this.px=0-2*this.size)},g.prototype.bounce=function(){this.settings.direction;(this.py<=0||this.py+this.size>=this.canvas.height)&&(this.dy=-this.dy,this.horizontal&&(this.dd=-this.dd)),(this.px<=0||this.px+this.size>=this.canvas.width)&&(this.dx=-this.dx,this.vertical&&(this.dd=-this.dd))},g.prototype.isOffCanvas=function(){var t=0-2*this.size,e=this.canvas.height+2*this.size,i=this.canvas.width+2*this.size;return this.pxi||this.pye},g.prototype.isTouchingEdge=function(){var t=this.canvas.height-this.size,e=this.canvas.width-this.size;return this.px<0||this.px>e||this.py<0||this.py>t},g.prototype.getColor=function(){return"random"===this.settings.color?o(this.colors):Array.isArray(this.settings.color)?o(this.settings.color):this.settings.color},g.prototype.getShape=function(){return"random"===this.settings.shape?o(this.shapes):Array.isArray(this.settings.shape)?o(this.settings.shape):this.settings.shape},g.prototype.getImage=function(){return Array.isArray(this.settings.imageUrl)?o(this.settings.imageUrl):this.settings.imageUrl},g.prototype.getStyle=function(){return o(this.styles)},g.prototype.getDelta=function(){var t=.1*this.settings.speed;return this.settings.speed&&this.settings.parallax?t+this.size*this.settings.parallax/50:t},g.prototype.getDeltaVariance=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,e=this.settings.speed||10;return t>0?a(-t,t)*e/100:0},g.prototype.getDeltaX=function(){var t=this.getDelta(),e=this.getDeltaVariance(this.settings.xVariance);return n(this.settings.direction)[0]*t+e},g.prototype.getDeltaY=function(){var t=this.getDelta(),e=this.getDeltaVariance(this.settings.yVariance);return n(this.settings.direction)[1]*t+e},g.prototype.getAlphaDelta=function(){var t=this.settings.alphaVariance,e=a(1,t+1);return l(.5)&&(e=-e),e},g.prototype.getDriftDelta=function(){return this.settings.drift?a(this.settings.drift-this.settings.drift/2,this.settings.drift+this.settings.drift/2):0},g.prototype.getRotationDelta=function(){var t=0;return this.settings.rotate&&this.settings.rotation&&(t=h(a(.5,1.5)*this.settings.rotation),l(.5)&&(t=-t)),t},g.prototype.update=function(){return this.frame+=1,this.updatePosition(),this.updateAlpha(),this},g.prototype.updateAlpha=function(){return this.settings.alphaSpeed>0&&(this.settings.twinkle?this.alpha=this.updateTwinkle():this.alpha=this.updateFade()),this.alpha},g.prototype.updateFade=function(){var t=this.da/1e3*this.settings.alphaSpeed*.5,e=this.alpha+t,i=this.da>0&&e>this.settings.maxAlpha,s=this.da<0&&ethis.settings.maxAlpha,s=t=1&&!(arguments[0]instanceof HTMLElement)&&(i=arguments[0],n=arguments[1],r=arguments[2],t=void 0),n&&!r&&(r=n);var h={alphaSpeed:10,alphaVariance:1,bounce:!1,color:"random",randomColor:c,randomColorCount:3,composition:"source-over",count:50,direction:180,drift:1,glow:0,imageUrl:"",maxAlpha:1,maxSize:10,minAlpha:0,minSize:1,parallax:1,rotate:!0,rotation:1,shape:"circle",speed:10,style:"fill",twinkle:!1,xVariance:2,yVariance:2};return this.el=t||document.body,this.settings=e(e({},h),i),this.resizable=!n&&!r,this.width=this.resizable?this.el.clientWidth:n,this.height=this.resizable?this.el.clientHeight:r,this.init=function(){var t=this;return this.sparticles=[],this.colors=this.getColorArray(),this.shapes=this.getShapeArray(),this.styles=this.getStyleArray(),this.imageUrls=this.getImageArray(),this.setupMainCanvas(),this.setupOffscreenCanvasses((function(){t.createSparticles(),t.start()})),window.addEventListener("resize",this),this},this.handleEvent=function(t){var e=this;"resize"===t.type&&(clearTimeout(this.resizeTimer),this.resizeTimer=setTimeout((function(){e.resizable&&(e.width=e.el.clientWidth,e.height=e.el.clientHeight,e.setCanvasSize().resetSparticles())}),200))},this.start=function(){var t=this;return this.loop||(this.loop=new s((function(e){t.drawFrame(e)}))),this.loop.start(),this},this.stop=function(){return this.loop.stop(),this},this.destroy=function(){for(var t in this.stop(),this.el.removeChild(this.canvas),window.removeEventListener("resize",this),this)this.hasOwnProperty(t)&&delete this[t];return this},this.setCanvasSize=function(t,e){return t&&(this.resizable=!1),this.width=t||this.width,this.height=e||this.height,this.canvas.width=this.width,this.canvas.height=this.height,this},this.resetSparticles=this.createSparticles=function(){this.sparticles=[],this.ctx.globalCompositeOperation=this.settings.composition;for(var t=0;t 150 && _.direction < 210) || 48 | (_.direction > 330 && _.direction < 390) || 49 | (_.direction > -30 && _.direction < 30); 50 | this.horizontal = 51 | (_.direction > 60 && _.direction < 120) || (_.direction > 240 && _.direction < 300); 52 | }; 53 | 54 | /** 55 | * initialise a particle with the default values from 56 | * the Sparticles instance settings. 57 | * these values do not change when the particle goes offscreen 58 | */ 59 | Sparticle.prototype.init = function() { 60 | const _ = this.settings; 61 | const canvas = this.canvas; 62 | this.alpha = 0; 63 | if (_.speed > 0 || _.alphaSpeed === 0) { 64 | this.alpha = random(_.minAlpha, _.maxAlpha); 65 | } 66 | if (_.bounce) { 67 | this.px = round(random(2, canvas.width - this.size - 2)); 68 | this.py = round(random(2, canvas.height - this.size - 2)); 69 | } else { 70 | this.px = round(random(-this.size * 2, canvas.width + this.size)); 71 | this.py = round(random(-this.size * 2, canvas.height + this.size)); 72 | } 73 | }; 74 | 75 | /** 76 | * reset the particle after it has gone off canvas. 77 | * this should be better than popping it from the array 78 | * and creating a new particle instance. 79 | */ 80 | Sparticle.prototype.reset = function() { 81 | // give the particle a new set of initial values 82 | this.setup(); 83 | // set the particle's Y position 84 | if (this.py < 0) { 85 | this.py = this.canvas.height + this.size * 2; 86 | } else if (this.py > this.canvas.height) { 87 | this.py = 0 - this.size * 2; 88 | } 89 | // set the particle's X position 90 | if (this.px < 0) { 91 | this.px = this.canvas.width + this.size * 2; 92 | } else if (this.px > this.canvas.width) { 93 | this.px = 0 - this.size * 2; 94 | } 95 | }; 96 | 97 | /** 98 | * bounce the particle off the edge of canvas 99 | * when it has touched 100 | */ 101 | Sparticle.prototype.bounce = function() { 102 | const _ = this.settings; 103 | const dir = _.direction; 104 | 105 | // reverse the particle's Y position 106 | if (this.py <= 0 || this.py + this.size >= this.canvas.height) { 107 | this.dy = -this.dy; 108 | if (this.horizontal) { 109 | this.dd = -this.dd; 110 | } 111 | } 112 | // reverse the particle's X position 113 | if (this.px <= 0 || this.px + this.size >= this.canvas.width) { 114 | this.dx = -this.dx; 115 | if (this.vertical) { 116 | this.dd = -this.dd; 117 | } 118 | } 119 | }; 120 | 121 | /** 122 | * check if the particle is off the canvas based 123 | * on it's current position 124 | * @returns {Boolean} is the particle completely off canvas 125 | */ 126 | Sparticle.prototype.isOffCanvas = function() { 127 | const topleft = 0 - this.size * 2; 128 | const bottom = this.canvas.height + this.size * 2; 129 | const right = this.canvas.width + this.size * 2; 130 | return this.px < topleft || this.px > right || this.py < topleft || this.py > bottom; 131 | }; 132 | 133 | /** 134 | * check if the particle is touching the canvas edge 135 | * @returns {Boolean} is the particle touching edge 136 | */ 137 | Sparticle.prototype.isTouchingEdge = function() { 138 | const topleft = 0; 139 | const bottom = this.canvas.height - this.size; 140 | const right = this.canvas.width - this.size; 141 | return this.px < topleft || this.px > right || this.py < topleft || this.py > bottom; 142 | }; 143 | 144 | /** 145 | * get a random color for the particle from the 146 | * array of colors set in the options object 147 | * @returns {String} - random color from color array 148 | */ 149 | Sparticle.prototype.getColor = function() { 150 | if (this.settings.color === "random") { 151 | return randomArray(this.colors); 152 | } else if (Array.isArray(this.settings.color)) { 153 | return randomArray(this.settings.color); 154 | } else { 155 | return this.settings.color; 156 | } 157 | }; 158 | 159 | /** 160 | * get a random shape for the particle from the 161 | * array of shapes set in the options object 162 | * @returns {String} - random shape from shape array 163 | */ 164 | Sparticle.prototype.getShape = function() { 165 | if (this.settings.shape === "random") { 166 | return randomArray(this.shapes); 167 | } else if (Array.isArray(this.settings.shape)) { 168 | return randomArray(this.settings.shape); 169 | } else { 170 | return this.settings.shape; 171 | } 172 | }; 173 | 174 | /** 175 | * get the image for the particle from the array 176 | * of possible image urls 177 | * @returns {String} - random imageUrl from imageUrl array 178 | */ 179 | Sparticle.prototype.getImage = function() { 180 | if (Array.isArray(this.settings.imageUrl)) { 181 | return randomArray(this.settings.imageUrl); 182 | } else { 183 | return this.settings.imageUrl; 184 | } 185 | }; 186 | 187 | /** 188 | * get the style of the particle, either "fill" or "stroke" 189 | * depending on the settings as fill/stroke/both 190 | * @returns {String} - either "fill" or "stroke" 191 | */ 192 | Sparticle.prototype.getStyle = function() { 193 | return randomArray(this.styles); 194 | }; 195 | 196 | /** 197 | * get a random delta (velocity) for the particle 198 | * based on the speed, and the parallax value (if applicable) 199 | * @returns {Number} - the velocity to be applied to the particle 200 | */ 201 | Sparticle.prototype.getDelta = function() { 202 | let baseDelta = this.settings.speed * 0.1; 203 | if (this.settings.speed && this.settings.parallax) { 204 | return baseDelta + (this.size * this.settings.parallax) / 50; 205 | } else { 206 | return baseDelta; 207 | } 208 | }; 209 | 210 | /** 211 | * get a random variable speed for use as a multiplier, 212 | * based on the values given in the settings object, this 213 | * can be positive or negative 214 | * @returns {Number} - a variable delta speed 215 | */ 216 | Sparticle.prototype.getDeltaVariance = function(v = 0) { 217 | const s = this.settings.speed || 10; 218 | if (v > 0) { 219 | return (random(-v, v) * s) / 100; 220 | } else { 221 | return 0; 222 | } 223 | }; 224 | 225 | /** 226 | * get a random delta on the X axis, taking in to account 227 | * the variance range in the settings object and the particle's 228 | * direction as a multiplier 229 | * @returns {Number} - the X delta to be applied to particle 230 | */ 231 | Sparticle.prototype.getDeltaX = function() { 232 | const d = this.getDelta(); 233 | const dv = this.getDeltaVariance(this.settings.xVariance); 234 | return cartesian(this.settings.direction)[0] * d + dv; 235 | }; 236 | 237 | /** 238 | * get a random delta on the Y axis, taking in to account 239 | * the variance range in the settings object and the particle's 240 | * direction as a multiplier 241 | * @returns {Number} - the Y delta to be applied to particle 242 | */ 243 | Sparticle.prototype.getDeltaY = function() { 244 | const d = this.getDelta(); 245 | const dv = this.getDeltaVariance(this.settings.yVariance); 246 | return cartesian(this.settings.direction)[1] * d + dv; 247 | }; 248 | 249 | /** 250 | * get a random delta for the alpha change over time from 251 | * between a positive and negative alpha variance value 252 | * @returns {Number} - the alpha delta to be applied to particle 253 | */ 254 | Sparticle.prototype.getAlphaDelta = function() { 255 | let variance = this.settings.alphaVariance; 256 | let a = random(1, variance + 1); 257 | if (roll(1 / 2)) { 258 | a = -a; 259 | } 260 | return a; 261 | }; 262 | 263 | /** 264 | * return a random drift value either positive or negative 265 | * @returns {Number} - the drift value 266 | */ 267 | Sparticle.prototype.getDriftDelta = function() { 268 | if (!this.settings.drift) { 269 | return 0; 270 | } else { 271 | return random( 272 | this.settings.drift - this.settings.drift / 2, 273 | this.settings.drift + this.settings.drift / 2 274 | ); 275 | } 276 | }; 277 | 278 | /** 279 | * return a random rotation value either positive or negative 280 | * @returns {Number} - the rotation value 281 | */ 282 | Sparticle.prototype.getRotationDelta = function() { 283 | let r = 0; 284 | if (this.settings.rotate && this.settings.rotation) { 285 | r = radian(random(0.5, 1.5) * this.settings.rotation); 286 | if (roll(1 / 2)) { 287 | r = -r; 288 | } 289 | } 290 | return r; 291 | }; 292 | 293 | /** 294 | * progress the particle's frame number, as well 295 | * as the internal values for both the particle's 296 | * position and the particle's alpha. 297 | * @returns {Object} - reference to the current Sparticle instance 298 | */ 299 | Sparticle.prototype.update = function() { 300 | this.frame += 1; 301 | this.updatePosition(); 302 | this.updateAlpha(); 303 | return this; 304 | }; 305 | 306 | /** 307 | * progress the particle's alpha value depending on the 308 | * alphaSpeed and the twinkle setting 309 | * @returns {Number} - new alpha value of the particle 310 | */ 311 | Sparticle.prototype.updateAlpha = function() { 312 | if (this.settings.alphaSpeed > 0) { 313 | if (this.settings.twinkle) { 314 | this.alpha = this.updateTwinkle(); 315 | } else { 316 | this.alpha = this.updateFade(); 317 | } 318 | } 319 | return this.alpha; 320 | }; 321 | 322 | /** 323 | * progress the particle's alpha value according to 324 | * the fading effect 325 | * @returns {Number} - new alpha value of the particle 326 | */ 327 | Sparticle.prototype.updateFade = function() { 328 | const tick = (this.da / 1000) * this.settings.alphaSpeed * 0.5; 329 | let alpha = this.alpha + tick; 330 | const over = this.da > 0 && alpha > this.settings.maxAlpha; 331 | const under = this.da < 0 && alpha < this.settings.minAlpha; 332 | // if the alpha is over or under the min or max values, 333 | // then we reverse the delta so that it can increase or 334 | // decrease in opacity in the opposite direction 335 | if (over || under) { 336 | this.da = -this.da; 337 | alpha = this.settings.maxAlpha; 338 | if (under) { 339 | alpha = this.settings.minAlpha; 340 | } 341 | } 342 | return alpha; 343 | }; 344 | 345 | /** 346 | * progress the particle's alpha value according to 347 | * the twinkle effect 348 | * @returns {Number} - new alpha value of the particle 349 | */ 350 | Sparticle.prototype.updateTwinkle = function() { 351 | let alpha = this.alpha; 352 | const delta = Math.abs(this.da); 353 | const over = alpha > this.settings.maxAlpha; 354 | const under = alpha < this.settings.minAlpha; 355 | const tick = (delta / 1000) * this.settings.alphaSpeed * 0.5; 356 | const flickerOn = roll(1 / 30); 357 | const flickerOff = roll(1 / 30); 358 | // if the particle is resetting the twinkle effect, then 359 | // we simply want to quickly get back to max alpha 360 | // over a short period of time, otherwise just advance the tick 361 | if (this.resettingTwinkle) { 362 | alpha += tick * 5; 363 | } else if (flickerOn) { 364 | alpha += tick * 50; 365 | } else if (flickerOff) { 366 | alpha -= tick * 25; 367 | } else { 368 | alpha -= tick; 369 | } 370 | // once the alpha is under the min alpha value, then we need 371 | // to set the twinkle effect to reset, and once it is over 372 | // the max alpha, we stop resetting. 373 | if (under) { 374 | this.resettingTwinkle = true; 375 | alpha = this.settings.minAlpha; 376 | } else if (over) { 377 | this.resettingTwinkle = false; 378 | alpha = this.settings.maxAlpha; 379 | } 380 | return alpha; 381 | }; 382 | 383 | /** 384 | * progress the particle's position values, rotation and drift 385 | * according to the settings given 386 | */ 387 | Sparticle.prototype.updatePosition = function() { 388 | if (this.settings.bounce && this.isTouchingEdge()) { 389 | this.bounce(); 390 | } else if (this.isOffCanvas()) { 391 | this.reset(); 392 | return; 393 | } 394 | 395 | this.px += this.dx; 396 | this.py += this.dy; 397 | // drift must be applied after position x/y 398 | // as it modifies the values by wave function 399 | this.updateDrift(); 400 | this.updateRotation(); 401 | }; 402 | 403 | /** 404 | * progress the particle's rotation value according 405 | * to the settings given 406 | */ 407 | Sparticle.prototype.updateRotation = function() { 408 | if (this.settings.rotate && this.settings.rotation) { 409 | this.rotation += this.dr; 410 | } 411 | }; 412 | 413 | /** 414 | * progress the particle's drift value according 415 | * to the settings given 416 | */ 417 | Sparticle.prototype.updateDrift = function() { 418 | const _ = this.settings; 419 | const dir = _.direction; 420 | 421 | if (_.drift && _.speed) { 422 | if (this.vertical) { 423 | // apply HORIZONTAL drift ~ when "direction" is mostly vertical. 424 | this.px += (cartesian(this.frame + this.frameoffset)[0] * this.dd) / (this.getDelta() * 15); 425 | } else if (this.horizontal) { 426 | // apply VERTICAL drift ~ when "direction" is mostly horizontal. 427 | this.py += (cartesian(this.frame + this.frameoffset)[1] * this.dd) / (this.getDelta() * 15); 428 | } 429 | } 430 | }; 431 | 432 | Sparticle.prototype.render = function(canvasses) { 433 | let particleCanvas; 434 | if (this.shape !== "image") { 435 | particleCanvas = canvasses[this.color][this.shape][this.style]; 436 | } else { 437 | particleCanvas = canvasses[this.color][this.shape][this.image]; 438 | } 439 | const canvasSize = particleCanvas.width; 440 | const scale = this.size / canvasSize; 441 | const px = this.px / scale; 442 | const py = this.py / scale; 443 | this.ctx.globalAlpha = clamp(this.alpha, 0, 1); 444 | this.renderRotate(); 445 | this.ctx.transform(scale, 0, 0, scale, 0, 0); 446 | this.ctx.drawImage(particleCanvas, 0, 0, canvasSize, canvasSize, px, py, canvasSize, canvasSize); 447 | this.ctx.setTransform(1, 0, 0, 1, 0, 0); 448 | return this; 449 | }; 450 | 451 | Sparticle.prototype.renderRotate = function() { 452 | if (this.shape !== "circle" && this.settings.rotate) { 453 | const centerX = this.px + this.size / 2; 454 | const centerY = this.py + this.size / 2; 455 | this.ctx.translate(centerX, centerY); 456 | this.ctx.rotate(this.rotation); 457 | this.ctx.translate(-centerX, -centerY); 458 | } 459 | }; 460 | -------------------------------------------------------------------------------- /example/sveltekit/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@iarna/toml@^2.2.5": 6 | version "2.2.5" 7 | resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" 8 | integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== 9 | 10 | "@rollup/pluginutils@^4.1.1": 11 | version "4.1.2" 12 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.2.tgz#ed5821c15e5e05e32816f5fb9ec607cdf5a75751" 13 | integrity sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ== 14 | dependencies: 15 | estree-walker "^2.0.1" 16 | picomatch "^2.2.2" 17 | 18 | "@sveltejs/adapter-auto@next": 19 | version "1.0.0-next.4" 20 | resolved "https://registry.yarnpkg.com/@sveltejs/adapter-auto/-/adapter-auto-1.0.0-next.4.tgz#c87a05f110cf23062953269423be8f7809d84da1" 21 | integrity sha512-kfygrjF2uIgVVDsySl7I9oWSekU6adJCr/3qzLQDLM4FJ98FWmfhj4OOWsmF9X4JRPSm97t3RPugYsN3NLwKMQ== 22 | dependencies: 23 | "@sveltejs/adapter-cloudflare" "1.0.0-next.3" 24 | "@sveltejs/adapter-netlify" "1.0.0-next.36" 25 | "@sveltejs/adapter-vercel" "1.0.0-next.32" 26 | 27 | "@sveltejs/adapter-cloudflare@1.0.0-next.3": 28 | version "1.0.0-next.3" 29 | resolved "https://registry.yarnpkg.com/@sveltejs/adapter-cloudflare/-/adapter-cloudflare-1.0.0-next.3.tgz#2c89eb9baba40e1ce63e85412571cbd4e3ded0f4" 30 | integrity sha512-LauvvkBFCE8myGMLWOncv97/xU8JJn9OMl1miy8f/q8qs0CrduvR1sv2uCM9AylqT/FNPy3mony5wpe3w1v7sg== 31 | dependencies: 32 | esbuild "^0.13.15" 33 | 34 | "@sveltejs/adapter-netlify@1.0.0-next.36": 35 | version "1.0.0-next.36" 36 | resolved "https://registry.yarnpkg.com/@sveltejs/adapter-netlify/-/adapter-netlify-1.0.0-next.36.tgz#a1037f67940fce59c49aa64854843d428f55c49c" 37 | integrity sha512-LdrIXCTBnIubtt/lthcnyt5VljuHpZlVzUqpWXk9Eu6bpNKblqQLMHkTBQfIbPfanmNSDZXJQVsdcFLqF2/+Cw== 38 | dependencies: 39 | "@iarna/toml" "^2.2.5" 40 | esbuild "^0.13.15" 41 | 42 | "@sveltejs/adapter-vercel@1.0.0-next.32": 43 | version "1.0.0-next.32" 44 | resolved "https://registry.yarnpkg.com/@sveltejs/adapter-vercel/-/adapter-vercel-1.0.0-next.32.tgz#90a8c1078a477a81d16cfd0efb27e5df2dc90189" 45 | integrity sha512-ZcltaS5bAobGD5P0z7xJIjPHSlGpF7padMIkqTzJxwMEb/acGgdO5yzDS8XUEaSNgj+prpD2oG8+gm33ds8x0A== 46 | dependencies: 47 | esbuild "^0.13.15" 48 | 49 | "@sveltejs/kit@next": 50 | version "1.0.0-next.202" 51 | resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.0.0-next.202.tgz#7438bc1b20c1acdd611588c3c1c1470959b2a251" 52 | integrity sha512-rXmJ0FplkWvD1CaeCfejRYhOJYrlmeUm5Fkw7gIKDdWPQev5rqOhd9B9ZvRpq35oMqCAwaOfK+e5S6k+83feEQ== 53 | dependencies: 54 | "@sveltejs/vite-plugin-svelte" "^1.0.0-next.30" 55 | cheap-watch "^1.0.4" 56 | sade "^1.7.4" 57 | vite "^2.7.2" 58 | 59 | "@sveltejs/vite-plugin-svelte@^1.0.0-next.30": 60 | version "1.0.0-next.32" 61 | resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.32.tgz#bafcb15dfa7832566dac2b8a67ac6b03e2be5ef6" 62 | integrity sha512-Lhf5BxVylosHIW6U2s6WDQA39ycd+bXivC8gHsXCJeLzxoHj7Pv7XAOk25xRSXT4wHg9DWFMBQh2DFU0DxHZ2g== 63 | dependencies: 64 | "@rollup/pluginutils" "^4.1.1" 65 | debug "^4.3.3" 66 | kleur "^4.1.4" 67 | magic-string "^0.25.7" 68 | require-relative "^0.8.7" 69 | svelte-hmr "^0.14.7" 70 | 71 | cheap-watch@^1.0.4: 72 | version "1.0.4" 73 | resolved "https://registry.yarnpkg.com/cheap-watch/-/cheap-watch-1.0.4.tgz#0bcb4a3a8fbd9d5327936493f6b56baa668d8fef" 74 | integrity sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw== 75 | 76 | debug@^4.3.3: 77 | version "4.3.3" 78 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" 79 | integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== 80 | dependencies: 81 | ms "2.1.2" 82 | 83 | esbuild-android-arm64@0.13.15: 84 | version "0.13.15" 85 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz#3fc3ff0bab76fe35dd237476b5d2b32bb20a3d44" 86 | integrity sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg== 87 | 88 | esbuild-darwin-64@0.13.15: 89 | version "0.13.15" 90 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz#8e9169c16baf444eacec60d09b24d11b255a8e72" 91 | integrity sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ== 92 | 93 | esbuild-darwin-arm64@0.13.15: 94 | version "0.13.15" 95 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz#1b07f893b632114f805e188ddfca41b2b778229a" 96 | integrity sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ== 97 | 98 | esbuild-freebsd-64@0.13.15: 99 | version "0.13.15" 100 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz#0b8b7eca1690c8ec94c75680c38c07269c1f4a85" 101 | integrity sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA== 102 | 103 | esbuild-freebsd-arm64@0.13.15: 104 | version "0.13.15" 105 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz#2e1a6c696bfdcd20a99578b76350b41db1934e52" 106 | integrity sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ== 107 | 108 | esbuild-linux-32@0.13.15: 109 | version "0.13.15" 110 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz#6fd39f36fc66dd45b6b5f515728c7bbebc342a69" 111 | integrity sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g== 112 | 113 | esbuild-linux-64@0.13.15: 114 | version "0.13.15" 115 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz#9cb8e4bcd7574e67946e4ee5f1f1e12386bb6dd3" 116 | integrity sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA== 117 | 118 | esbuild-linux-arm64@0.13.15: 119 | version "0.13.15" 120 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz#3891aa3704ec579a1b92d2a586122e5b6a2bfba1" 121 | integrity sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA== 122 | 123 | esbuild-linux-arm@0.13.15: 124 | version "0.13.15" 125 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz#8a00e99e6a0c6c9a6b7f334841364d8a2b4aecfe" 126 | integrity sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA== 127 | 128 | esbuild-linux-mips64le@0.13.15: 129 | version "0.13.15" 130 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz#36b07cc47c3d21e48db3bb1f4d9ef8f46aead4f7" 131 | integrity sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg== 132 | 133 | esbuild-linux-ppc64le@0.13.15: 134 | version "0.13.15" 135 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz#f7e6bba40b9a11eb9dcae5b01550ea04670edad2" 136 | integrity sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ== 137 | 138 | esbuild-netbsd-64@0.13.15: 139 | version "0.13.15" 140 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz#a2fedc549c2b629d580a732d840712b08d440038" 141 | integrity sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w== 142 | 143 | esbuild-openbsd-64@0.13.15: 144 | version "0.13.15" 145 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz#b22c0e5806d3a1fbf0325872037f885306b05cd7" 146 | integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g== 147 | 148 | esbuild-sunos-64@0.13.15: 149 | version "0.13.15" 150 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz#d0b6454a88375ee8d3964daeff55c85c91c7cef4" 151 | integrity sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw== 152 | 153 | esbuild-windows-32@0.13.15: 154 | version "0.13.15" 155 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz#c96d0b9bbb52f3303322582ef8e4847c5ad375a7" 156 | integrity sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw== 157 | 158 | esbuild-windows-64@0.13.15: 159 | version "0.13.15" 160 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz#1f79cb9b1e1bb02fb25cd414cb90d4ea2892c294" 161 | integrity sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ== 162 | 163 | esbuild-windows-arm64@0.13.15: 164 | version "0.13.15" 165 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz#482173070810df22a752c686509c370c3be3b3c3" 166 | integrity sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA== 167 | 168 | esbuild@^0.13.12, esbuild@^0.13.15: 169 | version "0.13.15" 170 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.15.tgz#db56a88166ee373f87dbb2d8798ff449e0450cdf" 171 | integrity sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw== 172 | optionalDependencies: 173 | esbuild-android-arm64 "0.13.15" 174 | esbuild-darwin-64 "0.13.15" 175 | esbuild-darwin-arm64 "0.13.15" 176 | esbuild-freebsd-64 "0.13.15" 177 | esbuild-freebsd-arm64 "0.13.15" 178 | esbuild-linux-32 "0.13.15" 179 | esbuild-linux-64 "0.13.15" 180 | esbuild-linux-arm "0.13.15" 181 | esbuild-linux-arm64 "0.13.15" 182 | esbuild-linux-mips64le "0.13.15" 183 | esbuild-linux-ppc64le "0.13.15" 184 | esbuild-netbsd-64 "0.13.15" 185 | esbuild-openbsd-64 "0.13.15" 186 | esbuild-sunos-64 "0.13.15" 187 | esbuild-windows-32 "0.13.15" 188 | esbuild-windows-64 "0.13.15" 189 | esbuild-windows-arm64 "0.13.15" 190 | 191 | estree-walker@^2.0.1: 192 | version "2.0.2" 193 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 194 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 195 | 196 | fsevents@~2.3.2: 197 | version "2.3.2" 198 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 199 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 200 | 201 | function-bind@^1.1.1: 202 | version "1.1.1" 203 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 204 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 205 | 206 | has@^1.0.3: 207 | version "1.0.3" 208 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 209 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 210 | dependencies: 211 | function-bind "^1.1.1" 212 | 213 | is-core-module@^2.2.0: 214 | version "2.8.0" 215 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" 216 | integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== 217 | dependencies: 218 | has "^1.0.3" 219 | 220 | kleur@^4.1.4: 221 | version "4.1.4" 222 | resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d" 223 | integrity sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA== 224 | 225 | magic-string@^0.25.7: 226 | version "0.25.7" 227 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" 228 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== 229 | dependencies: 230 | sourcemap-codec "^1.4.4" 231 | 232 | mri@^1.1.0: 233 | version "1.2.0" 234 | resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" 235 | integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== 236 | 237 | ms@2.1.2: 238 | version "2.1.2" 239 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 240 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 241 | 242 | nanoid@^3.1.30: 243 | version "3.1.30" 244 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" 245 | integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== 246 | 247 | path-parse@^1.0.6: 248 | version "1.0.7" 249 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 250 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 251 | 252 | picocolors@^1.0.0: 253 | version "1.0.0" 254 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 255 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 256 | 257 | picomatch@^2.2.2: 258 | version "2.3.0" 259 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 260 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== 261 | 262 | postcss@^8.4.5: 263 | version "8.4.5" 264 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" 265 | integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== 266 | dependencies: 267 | nanoid "^3.1.30" 268 | picocolors "^1.0.0" 269 | source-map-js "^1.0.1" 270 | 271 | require-relative@^0.8.7: 272 | version "0.8.7" 273 | resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" 274 | integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= 275 | 276 | resolve@^1.20.0: 277 | version "1.20.0" 278 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 279 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 280 | dependencies: 281 | is-core-module "^2.2.0" 282 | path-parse "^1.0.6" 283 | 284 | rollup@^2.59.0: 285 | version "2.61.1" 286 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.61.1.tgz#1a5491f84543cf9e4caf6c61222d9a3f8f2ba454" 287 | integrity sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA== 288 | optionalDependencies: 289 | fsevents "~2.3.2" 290 | 291 | sade@^1.7.4: 292 | version "1.7.4" 293 | resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691" 294 | integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA== 295 | dependencies: 296 | mri "^1.1.0" 297 | 298 | source-map-js@^1.0.1: 299 | version "1.0.1" 300 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" 301 | integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== 302 | 303 | sourcemap-codec@^1.4.4: 304 | version "1.4.8" 305 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" 306 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== 307 | 308 | svelte-hmr@^0.14.7: 309 | version "0.14.7" 310 | resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.7.tgz#7fa8261c7b225d9409f0a86f3b9ea5c3ca6f6607" 311 | integrity sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog== 312 | 313 | svelte@^3.44.0: 314 | version "3.44.3" 315 | resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.44.3.tgz#795b1ced6ed3da44969099e5061b850c93c95e9a" 316 | integrity sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg== 317 | 318 | vite@^2.7.2: 319 | version "2.7.4" 320 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.7.4.tgz#06f68f8909943f9fe582c26120b0c2b85894a05e" 321 | integrity sha512-f+0426k9R/roz5mRNwJlQ+6UOnhCwIypJSbfgCmsVzVJe9jTTM5iRX2GWYUean+iqPBWaU/dYLryx9AoH2pmrw== 322 | dependencies: 323 | esbuild "^0.13.12" 324 | postcss "^8.4.5" 325 | resolve "^1.20.0" 326 | rollup "^2.59.0" 327 | optionalDependencies: 328 | fsevents "~2.3.2" 329 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sparticles 2 | ### javascript particles in canvas 3 | 4 | #### https://sparticlesjs.dev 5 | 6 | **Lightweight, _High Performance_ Particles in Canvas.** 7 | For those occasions when you ***👏 just 👏 gotta 👏 have 👏*** sparkles, 8 | snow, or stars on your homepage! 9 | 10 | ![Image of little coloured stars known as "Sparticles" running at 120fps](example/example.gif) 11 | 12 | 13 | --- 14 | 15 | - [installation](#installation) 16 | - [parameters](#parameters) 17 | - [options](#options) 18 | - [methods](#methods) 19 | - [styling](#styling) 20 | - [performance](#performance) 21 | - [why?](#why-sparticles-) 22 | 23 | --- 24 | 25 | 26 | # installation 27 | 28 | Depending on how your project looks, 29 | - you may want to [include a direct link to the script](#vanilla) and 30 | then initialise the sparkles, 31 | - or you may want 32 | to [import the module in to your application](#app--bundler) for a more modern approach. 33 | 34 | ## vanilla 35 | 36 | 1. firstly make sure you've downloaded [the latest version of the script](https://github.com/simeydotme/sparticles/releases) 37 | to your application directory _(if you are running on a CMS you might also 38 | need to upload the file to your server)_. The file you'll want to use is; `dist/sparticles.min.js` 39 | to make sure it downloads the fastest for your users. 40 | 41 | 2. After you've downloaded, or uploaded, the `sparticles.min.js` file to the 42 | correct place, you'll then need to include it in your web page; 43 | 44 | ```html 45 | 46 | ``` 47 | 48 | 3. And finally, you should then be able to initialise the Sparticles by 49 | running this code in your javascript; 50 | _(make sure this code runs _after_ you've included the script above.)_ 51 | 52 | ```html 53 | 59 | ``` 60 | 61 | ## jquery 62 | 63 | For jQuery sites, you may follow all of the steps above, but replace 64 | the third step with something like below; 65 | 66 | ```html 67 | 71 | ``` 72 | 73 | ## app / bundler 74 | 75 | If you're running a more modern type of application with something like Svelte or VueJs; 76 | 77 | 1. First you will want to install the module with NPM; 78 | 79 | ```bash 80 | yarn add --dev sparticles 81 | # or npm, if you prefer 82 | npm install --save-dev sparticles 83 | ``` 84 | 85 | 2. Then import the module in to the app where you want to use it 86 | 87 | ```js 88 | import Sparticles from "sparticles"; 89 | ``` 90 | 91 | 3. Finally initialise with vanillaJS 92 | 93 | ```js 94 | new Sparticles(node, { count: 100 }, 400); 95 | ``` 96 | 97 | 4. If you're using SvelteJS specifically, then your single-file component 98 | would look a little like this; 99 | 100 | ```html 101 | 113 | 114 |
115 |
116 | ``` 117 | 118 | # usage 119 | 120 | Providing that the script/module has been properly included, then it can be initialised 121 | by running the `Sparticles()` constructor; 122 | ```js 123 | let mySparticles = new Sparticles(); 124 | ``` 125 | 126 | # parameters 127 | 128 | When initialising the Sparticles instance there are some parameters that can be supplied. 129 | 130 | parameter | type | default | description 131 | ----------------------------|--------------------|--------------------|----------------------------------------------------------- 132 | **node** | `HTMLElement` | `document.body` | the element in the DOM which the Sparticles will append to 133 | **[options](#options)** | `Object` | `{}` | an object with [all the options for the instance](#options) 134 | **width** | `Number` | `node.clientWidth` | the width of the canvas element 135 | **height** | `Number` | `node.clientWidth` | the height of the canvas element (defaults to width) 136 | 137 | Leave the `width`/`height` properties empty to make the canvas resize to fit it's `node` 138 | 139 | --- 140 | 141 | - Supply nothing and get a default Sparticle instance on the `` 142 | ```js 143 | let mySparticles = new Sparticles(); 144 | ``` 145 | 146 | - Supply a single HTMLElement parameter for a default Sparticle instance on that element 147 | ```js 148 | let mySparticles = new Sparticles(document.getElementById("myDiv")); 149 | ``` 150 | 151 | - Supply a single `Object` parameter to customise a Sparticle instance on the `` 152 | ```js 153 | let mySparticles = new Sparticles({ color: "red" }); 154 | ``` 155 | 156 | - Supply the width and height parameters for a custom size 157 | ```js 158 | let mySparticles = new Sparticles({ color: "red" }, 400, 300); 159 | ``` 160 | 161 | # options 162 | 163 | A brief look at all the options, with more details below. 164 | 165 | option | type | default | description 166 | -------------------------------------------|-------------------|-----------------|----------------------------------------------------- 167 | **[composition](#composition)** | `String` | `source-over` | canvas globalCompositeOperation value for particles 168 | **[count](#count)** | `Number` | `50` | number of particles on the canvas simultaneously 169 | **[speed](#speed)** | `Number` | `10` | default velocity of every particle 170 | **[parallax](#parallax)** | `Number` | `1` | speed multiplier effect for larger particles (0 = none) 171 | **[direction](#direction)** | `Number` | `180` | default direction of particles in degrees (0 = ↑, 180 = ↓) 172 | **[xVariance](#xVariance)** | `Number` | `2` | random deviation of particles on x-axis from default direction 173 | **[yVariance](#yVariance)** | `Number` | `2` | random deviation of particles on y-axis from default direction 174 | **[rotate](#rotate)** | `Boolean` | `true` | can particles rotate 175 | **[rotation](#rotation)** | `Number` | `1` | default rotational speed for every particle 176 | **[alphaSpeed](#alphaSpeed)** | `Number` | `10` | rate of change in alpha over time 177 | **[alphaVariance](#alphaVariance)** | `Number` | `1` | random deviation of alpha change 178 | **[minAlpha](#minAlpha)** | `Number` | `0` | minumum alpha value of every particle 179 | **[maxAlpha](#maxAlpha)** | `Number` | `1` | maximum alpha value of every particle 180 | **[minSize](#minSize)** | `Number` | `1` | minimum size of every particle 181 | **[maxSize](#maxSize)** | `Number` | `10` | maximum size of every particle 182 | **[bounce](#bounce)** | `Boolean` | `false` | should the particles bounce off edge of canvas 183 | **[drift](#drift)** | `Number` | `1` | the "driftiness" of particles which have a horizontal/vertical direction 184 | **[glow](#glow)** | `Number` | `0` | the glow effect size of each particle 185 | **[twinkle](#twinkle)** | `Boolean` | `false` | particles to exhibit an alternative alpha transition as "twinkling" 186 | **[style](#style)** | `String` | `fill` | fill style of particles (one of; "fill", "stroke" or "both") 187 | **[shape](#shape)** | `String`/`Array` | `circle` | shape of particles (any of; circle, square, triangle, diamond, line, image) or "random" 188 | **[color](#color)** | `String`/`Array` | `random` | css color as string, or array of color strings (can also be "random") 189 | **[randomColor](#randomColor)** | `Function` | `randomHsl()` | function for returning a random color when the color is set as "random" 190 | **[randomColorCount](#randomColorCount)** | `Number` | `3` | number of random colours when the color is set as "random" 191 | **[imageUrl](#imageUrl)** | `String`/`Array` | | if shape is "image", define an image url (can be data-uri, **should be square (1:1 ratio)**) 192 | 193 | --- 194 | 195 | ## `composition` 196 | - Type: `String` 197 | - Default: `source-over` 198 | - Possible: [`see list here`](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation) 199 | 200 | The [global render composition](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation) 201 | when rendering particles on top of one-another. This, however, is a very expensive operation when set to anything 202 | other than the default value (`source-over`), and will ultimately degrade performance, especially with many particles. 203 | 204 | Will accept [any of the values that are provided as part of the Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation) 205 | 206 | ## `count` 207 | - Type: `Number` 208 | - Default: `50` 209 | - Range: `1 - 10000` 210 | 211 | Simply the number of particles drawn to the screen. 212 | Values over `500` may begin to degrade performance. 213 | 214 | ## `speed` 215 | - Type: `Number` 216 | - Default: `10` 217 | - Range: `0 - 100` 218 | 219 | The base value of speed across all particles. This is modified by options such as 220 | `parallax` and `[x/y]Variance` to determine the final velocity of each individual particle. 221 | A speed of `0` will render particles stationary before applying `[x/y]Variance`. 222 | 223 | ## `parallax` 224 | - Type: `Number` 225 | - Default: `1` 226 | - Range: `0 - 20` 227 | 228 | A value to apply more speed to larger particles, and less speed to smaller particles, creating 229 | an effect which makes larger particles appear closer to the screen. 230 | 231 | ## `direction` 232 | - Type: `Number` 233 | - Default: `180` 234 | - Range: `0 - 360` 235 | 236 | The base angle (in degrees) at which the particles are travelling, so long as they have a speed value. 237 | 238 | ## `xVariance` 239 | - Type: `Number` 240 | - Default: `2` 241 | - Range: `0 - 20` 242 | 243 | How much variance is applied between particles on the `X` axis. A value of `0` will make all particles 244 | appear to be going completely parallel, and look unnatural. 245 | 246 | Can be used in conjunction with `speed: 0;` to make particles which float randomly in space. 247 | 248 | ## `yVariance` 249 | - Type: `Number` 250 | - Default: `2` 251 | - Range: `0 - 20` 252 | 253 | How much variance is applied between particles on the `Y` axis. A value of `0` will make all particles 254 | appear to be going completely parallel, and look unnatural. 255 | 256 | Can be used in conjunction with `speed: 0;` to make particles which float randomly in space. 257 | 258 | ## `rotate` 259 | - Type: `Boolean` 260 | - Default: `true` 261 | 262 | Toggle whether the particles are allowed to spin about their axis. 263 | 264 | ## `rotation` 265 | - Type: `Number` 266 | - Default: `1` 267 | - Range: `0 - 20` 268 | 269 | How fast the particles can spin about their axis, this has a random multiplier added per-particle 270 | which prevents a completely unnatural spinning effect. 271 | 272 | ## `alphaSpeed` 273 | - Type: `Number` 274 | - Default: `10` 275 | - Range: `0 - 50` 276 | 277 | Rate of change for the alpha value of all particles. A higher value will encourage the particles 278 | to flicker like candle lights. A value of `0` will disable alpha change. 279 | 280 | ## `alphaVariance` 281 | - Type: `Number` 282 | - Default: `2` 283 | - Range: `0 - 10` 284 | 285 | How much variance is applied between each particle on the alpha value change over time. A value 286 | of `0` will cause all particles to change alpha at the same rate, a higher value will introduce more 287 | variety. 288 | 289 | ## `minAlpha` 290 | - Type: `Number` 291 | - Default: `0` 292 | - Range: `-5 - +1` 293 | 294 | The minimum alpha value a particle can change to. The lower the number the longer it will stay invisible 295 | on the canvas, this could be useful in some scenarios where the particle should fade out for a while. 296 | 297 | Must be lower than the `maxAlpha` value. 298 | 299 | ## `maxAlpha` 300 | - Type: `Number` 301 | - Default: `0` 302 | - Range: `0 - +5` 303 | 304 | The maximum alpha value a particle can change to. The higher the number the longer it will stay visible 305 | on the canvas, this could be useful in some scenarios where the particle should stay at max alpha for a time. 306 | 307 | Must be higher than the `minAlpha` value. 308 | 309 | ## `minSize` 310 | - Type: `Number` 311 | - Default: `1` 312 | - Range: `1 - 100` 313 | 314 | Minimum size (in pixels) of the particles. The actual size of each particle is variable between the `minSize` 315 | and `maxSize`. If the `minSize` and `maxSize` are the same value; then all particles will be uniformly sized. 316 | 317 | ## `maxSize` 318 | - Type: `Number` 319 | - Default: `10` 320 | - Range: `1 - 100` 321 | 322 | Maximum size (in pixels) of the particles. The actual size of each particle is variable between the `minSize` 323 | and `maxSize`. If the `minSize` and `maxSize` are the same value; then all particles will be uniformly sized. 324 | 325 | ## `style` 326 | - Type: `String` 327 | - Default: `"fill"` 328 | - Values: `"fill"`, `"stroke"` or `"both"` 329 | 330 | Particles can be either stroked (outline) or filled (solid) and this setting determines that style. It's 331 | also possible to randomize the style by choosing `"both"` 332 | 333 | ## `bounce` 334 | - Type: `Boolean` 335 | - Default: `false` 336 | 337 | Determine if particles should bounce off the boundaries of the canvas instead of resetting to the opposite side. 338 | This is best used with `speed: 0;` and a high value for `[x/yVariance]` to create a chaotic effect. 339 | 340 | ## `drift` 341 | - Type: `Number` 342 | - Default: `1` 343 | - Range: `1 - 20` 344 | 345 | How much a particle will "drift" as it falls. This is to imply a floatiness/wind effect like seen with snow flakes, 346 | or leaves. The `drift` will only apply if `speed > 0` and `direction` is near to a 90degree value (`0, 90, 180, 270`) 347 | 348 | ## `glow` 349 | - Type: `Number` 350 | - Default: `0` 351 | - Range: `0 - 50` 352 | 353 | Glow (or shadow) effect around the particle. This will not affect images. 354 | 355 | ## `twinkle` 356 | - Type: `Boolean` 357 | - Default: `false` 358 | 359 | Apply a "twinkle" effect to the particle when changing alpha. This works best with a higher `alphaSpeed` and 360 | `alphaVariance` value. 361 | 362 | ## `color` 363 | - Type: `String` / `Array` 364 | - Default: `"random"` 365 | - Values: any valid css/html color string 366 | 367 | A CSS/HTML color string to apply across all particles. 368 | If an array of colors (`[ "#ff0", "red", "hsl(10,50%,50%)" ]`) is given, then each particle will 369 | be assigned a random color from the array. Additionally `"random"` can be used to assign any random color. 370 | 371 | ## `randomColor` 372 | - Type: `Function` 373 | - Default: [`randomHSL()`](https://github.com/simeydotme/sparticles/blob/master/src/helpers.js#L55-L64) 374 | - Arguments: `index`, `total` 375 | 376 | Custom function to use when generating `random` colors. The default function will return a fairly 377 | pleasant `hsl()` color with a high saturation and medium lightness. This can be overridden to suit 378 | your environment better. The two arguments (`index`, `total`) are `Integer`s and allow for a little 379 | psuedo-randomizing. 380 | 381 | **example:** 382 | 383 | ```js 384 | randomColor: function( index, total ) { 385 | return `hsl( ${index}, 80%, ${total - index}% )`; 386 | } 387 | ``` 388 | 389 | ## `randomColorCount` 390 | - Type: `Number` 391 | - Default: `3` 392 | - Range: `1 - 50` 393 | 394 | How many random colors to generate when `color` is `random`. The more colors generated 395 | the more chance there is of a performance penalty. It should be OK up to `50`. 396 | 397 | ## `shape` 398 | - Type: `String` / `Array` 399 | - Default: `"circle"` 400 | - Values: `"circle"`, `"square"`, `"triangle"`, `"line"`, `"diamond"`, `"star"` or `"image"` 401 | 402 | Determine the shape of all the particles. 403 | If an array of shapes (`[ "circle", "star", "diamond" ]`) is given, then each particle will 404 | be assigned a random shape form the array. Additionally `"image"` can be used to [define a custom 405 | particle shape from an image when combined with `imageUrl`](#imageUrl). 406 | 407 | ## `imageUrl` 408 | - Type: `String` / `Array` 409 | - Default: `""` 410 | - Values: a valid url, or data-uri 411 | 412 | Determine the custom image to be used for all the particles. 413 | If an array of urls (`[ "http://my.image/shape.png", "http://my.svg/shape.svg" ]`) is given, then each particle 414 | will be assigned a random image as it's shape from the array. 415 | **This image should be a square (1:1)** 416 | 417 | - ℹ `imageUrl` only has an effect [if a `shape` in the array is; `"image"`](#shape). 418 | - ℹ `imageUrl` can accept **svg**, but the `` root needs a width/height. [(see issue)](https://github.com/simeydotme/sparticles/issues/2); 419 | 420 | # methods 421 | 422 | a few public methods can be accessed by storing a reference to the Sparticles instance 423 | and executed like so; 424 | 425 | ```js 426 | let mySparticles = new Sparticles(); 427 | mySparticles.destroy(); 428 | ``` 429 | 430 | method | description 431 | ---------------------------------------------------------|------------------------------------------------------ 432 | **[destroy()](#destroy)** | destroy the Sparticles instance and remove event listeners 433 | **[setCanvasSize( width, height )](#setCanvasSize)** | set the new size of the canvas 434 | **[resetSparticles()](#resetSparticles)** | reset all the particles on the canvas 435 | 436 | # styling 437 | 438 | If the Sparticles are simply going to be placed in a container (like a `
`) then the only 439 | styling that should be necessary is to set the width/height of the canvas [using the 440 | `width` and `height` parameters](#parameters). 441 | 442 | --- 443 | 444 | To place the Sparticles in the background of a web-page, you'll need to add a 445 | container to the `` which the canvas can sit in, then `position` it `fixed`: 446 | 447 | ```html 448 | 449 | 450 | 451 |
452 | 453 | 454 | ``` 455 | 456 | Then we set up the CSS styling for the Sparticles container depending on our 457 | situation: 458 | ```css 459 | /** 460 | * we need to make sure the background doesn't have a 461 | * background color as we want to place the container 462 | * behind it on the z-axis! 463 | */ 464 | html { background: black; } 465 | body { background: transparent; } 466 | 467 | .sparticles-container { 468 | position: fixed; 469 | left: 0; right: 0; 470 | top: 0; bottom: 0; 471 | /** 472 | * z-index: -1; this makes the still interactive 473 | * by placing the sparticles behind the content 474 | */ 475 | z-index: -1; 476 | } 477 | /** 478 | * we could a;so use "pointer-events: none;" in 479 | * modern browsers to put the particles on top of all our content 480 | */ 481 | @supports ( pointer-events: none ) { 482 | .sparticles-container { 483 | z-index: 2; 484 | pointer-events: none; 485 | } 486 | } 487 | ``` 488 | 489 | Finally we can initiate the Sparticles with the `.sparticles-container` 490 | as the DOM element it's bound to: 491 | ```js 492 | let container = document.querySelector(".sparticles-container"); 493 | let mySparticles = new Sparticles( container, { color: "red" }); 494 | // no need for width/height as the canvas will fill 495 | // the container which is fixed to the viewport size 496 | ``` 497 | 498 | # performance 499 | 500 | Sparticles is really quite fast! 501 | 502 | It was designed to be the smallest (_within reason_) and fastest performing 503 | particles script with such a large feature set! 504 | 505 | Some other popular particle scripts will eat up to 50% of your CPU to 506 | render 1,000 particles. Sparticles will do the same while only using 9% 507 | and will run at a buttery 120fps if your device can refresh that fast! 508 | 509 | Sparticles was built because other offerings in the space were either 510 | doing way too much and adding too many `kb` to load, or they were just 511 | too slow and unable to serve enough particles to lower end devices 512 | without chugging along jankily! 513 | 514 | I used to get a lot of requests from Editorial/Content teams who wanted 515 | snow/sparkles/whatever on their home page during events, and I either had 516 | to reject because the plugins were killing our user's devices or accept 517 | and live knowing I've failed the users/customers! 😢 ~~ so Sparticles should fix that! 518 | 519 | ## mobile 520 | 521 | - ℹ **It's quite easy to achieve 120fps+ with over 1,000 particles on a decent computer!** 522 | 523 | - ⚠ _But please remember **your users are not all running super-computers** with GPUs, they 524 | are [**probably on a mobile phone**](https://www.statista.com/statistics/277125/share-of-website-traffic-coming-from-mobile-devices/). 525 | Please avoid running heavy animations on phones! If you really have to then I'd advise reducing the particles down to 100 or less for a mobile device!_ 526 | 527 | Please take care of your mobile users! They are probably your primary user if you're 528 | running a commercial or non-tech website! use a script like below to determine the amount 529 | of particles based on their device; 530 | 531 | ```js 532 | let myElement = document.getElementById("myDiv"); 533 | // PLEASE DON'T PUSH A TON OF ANIMATION ON MOBILES! 534 | let count = (/Mobi|Android/i.test(navigator.userAgent)) ? 100 : 500; 535 | let mySparticles = new Sparticles(myElement, { count: count }, 400); 536 | ``` 537 | 538 | # why "Sparticles" ? 539 | ``` 540 | particles + [ speed ⚡ | snow ❄ | sparkles ✨ | stars ⭐ ] = Sparticles 🌈 541 | ``` 542 | -------------------------------------------------------------------------------- /src/sparticles.js: -------------------------------------------------------------------------------- 1 | import { AnimationFrame } from "./animationFrame.js"; 2 | import { clamp, randomHsl } from "./helpers.js"; 3 | import { Sparticle } from "./sparticle.js"; 4 | 5 | /** 6 | * Sparticles Constructor; 7 | * Create a , append to the given node, and start the particle effect 8 | * @param {HTMLElement} [node=document.body] - element to which canvas is appended to 9 | * @param {Object} [options={}] - settings to use for the particle effect 10 | * @param {String} [options.composition=source-over] - canvas globalCompositeOperation value for particles 11 | * @param {Number} [options.count=50] - number of particles on the canvas simultaneously 12 | * @param {Number} [options.speed=10] - default velocity of every particle 13 | * @param {Number} [options.parallax=1] - speed multiplier effect for larger particles (0 = none) 14 | * @param {Number} [options.direction=180] - default direction of particles in degrees (0 = ↑, 180 = ↓) 15 | * @param {Number} [options.xVariance=2] - random deviation of particles on x-axis from default direction 16 | * @param {Number} [options.yVariance=2] - random deviation of particles on y-axis from default direction 17 | * @param {Number} [options.rotate=true] - can particles rotate 18 | * @param {Number} [options.rotation=1] - default rotational speed for every particle 19 | * @param {Number} [options.alphaSpeed=10] - rate of change in alpha over time 20 | * @param {Number} [options.alphaVariance=1] - random deviation of alpha change 21 | * @param {Number} [options.minAlpha=0] - minumum alpha value of every particle 22 | * @param {Number} [options.maxAlpha=1] - maximum alpha value of every particle 23 | * @param {Number} [options.minSize=1] - minimum size of every particle 24 | * @param {Number} [options.maxSize=10] - maximum size of every particle 25 | * @param {Boolean} [options.bounce=false] - should the particles bounce off edge of canvas 26 | * @param {Number} [options.drift=1] - the "driftiness" of particles which have a horizontal/vertical direction 27 | * @param {Number} [options.glow=0] - the glow effect size of each particle 28 | * @param {Boolean} [options.twinkle=false] - particles to exhibit an alternative alpha transition as "twinkling" 29 | * @param {String} [options.style=fill] - fill style of particles (one of; "fill", "stroke" or "both") 30 | * @param {(String|String[])} [options.shape=circle] - shape of particles (any of; circle, square, triangle, diamond, line, image) or "random" 31 | * @param {(String|String[])} [options.imageUrl=] - if shape is "image", define an image url (can be data-uri, must be square (1:1 ratio)) 32 | * @param {(String|String[])} [options.color=random] - css color as string, or array of color strings (can also be "random") 33 | * @param {Function} [options.randomColor=randomHsl(index,total)] - a custom function for setting the random colors when color="random" 34 | * @param {Number} [options.randomColorCount=3] - the number of random colors to generate when color is "random" 35 | * @param {Number} [width] - the width of the canvas element 36 | * @param {Number} [height=width] - the height of the canvas element 37 | * @returns {Object} - reference to a new Sparticles instance 38 | */ 39 | const Sparticles = function(node, options, width, height) { 40 | if (arguments.length >= 1 && !(arguments[0] instanceof HTMLElement)) { 41 | options = arguments[0]; 42 | width = arguments[1]; 43 | height = arguments[2]; 44 | node = undefined; 45 | } 46 | if (width && !height) { 47 | height = width; 48 | } 49 | const defaults = { 50 | alphaSpeed: 10, 51 | alphaVariance: 1, 52 | bounce: false, 53 | color: "random", 54 | randomColor: randomHsl, 55 | randomColorCount: 3, 56 | composition: "source-over", 57 | count: 50, 58 | direction: 180, 59 | drift: 1, 60 | glow: 0, 61 | imageUrl: "", 62 | maxAlpha: 1, 63 | maxSize: 10, 64 | minAlpha: 0, 65 | minSize: 1, 66 | parallax: 1, 67 | rotate: true, 68 | rotation: 1, 69 | shape: "circle", 70 | speed: 10, 71 | style: "fill", 72 | twinkle: false, 73 | xVariance: 2, 74 | yVariance: 2, 75 | }; 76 | this.el = node || document.body; 77 | this.settings = { ...defaults, ...options }; 78 | this.resizable = !width && !height; 79 | this.width = this.resizable ? this.el.clientWidth : width; 80 | this.height = this.resizable ? this.el.clientHeight : height; 81 | 82 | /** 83 | * initialise the sparticles instance 84 | * @returns {Object} - reference to the Sparticles instance 85 | */ 86 | this.init = function() { 87 | this.sparticles = []; 88 | this.colors = this.getColorArray(); 89 | this.shapes = this.getShapeArray(); 90 | this.styles = this.getStyleArray(); 91 | this.imageUrls = this.getImageArray(); 92 | this.setupMainCanvas(); 93 | this.setupOffscreenCanvasses(() => { 94 | this.createSparticles(); 95 | this.start(); 96 | }); 97 | // defer to the default "handleEvent" handler 98 | // https://developer.mozilla.org/en-US/docs/Web/API/EventListener/handleEvent 99 | window.addEventListener("resize", this); 100 | return this; 101 | }; 102 | 103 | /** 104 | * handle event for screen resize; 105 | * debounce a canvas resize, 106 | * reset the particles 107 | */ 108 | this.handleEvent = function(event) { 109 | if (event.type === "resize") { 110 | clearTimeout(this.resizeTimer); 111 | this.resizeTimer = setTimeout(() => { 112 | if (this.resizable) { 113 | this.width = this.el.clientWidth; 114 | this.height = this.el.clientHeight; 115 | this.setCanvasSize().resetSparticles(); 116 | } 117 | }, 200); 118 | } 119 | }; 120 | 121 | /** 122 | * start/resume the sparticles animation 123 | * @returns {Object} - the Sparticle instance (for chaining) 124 | */ 125 | this.start = function() { 126 | const me = this; 127 | if (!this.loop) { 128 | this.loop = new AnimationFrame(t => { 129 | me.drawFrame(t); 130 | }); 131 | } 132 | this.loop.start(); 133 | return this; 134 | }; 135 | 136 | /** 137 | * stop/pause the sparticles animation 138 | * @returns {Object} - the Sparticle instance (for chaining) 139 | */ 140 | this.stop = function() { 141 | this.loop.stop(); 142 | return this; 143 | }; 144 | 145 | /** 146 | * destroy the current instance and free up some memory 147 | * @returns {Object} - the Sparticle instance (for chaining) 148 | */ 149 | this.destroy = function() { 150 | // stop the rendering and updating 151 | this.stop(); 152 | // remove the canvas element from the DOM 153 | this.el.removeChild(this.canvas); 154 | // remove the resize event for this instance 155 | window.removeEventListener("resize", this); 156 | // delete all the properties from the instance 157 | // to free up memory 158 | for (const prop in this) { 159 | if (this.hasOwnProperty(prop)) { 160 | delete this[prop]; 161 | } 162 | } 163 | return this; 164 | }; 165 | 166 | /** 167 | * set the canvas width and height 168 | * @param {Number} width - the width of the canvas 169 | * @param {Number} height - the height of the canvas 170 | * @returns {Object} - the Sparticle instance (for chaining) 171 | */ 172 | this.setCanvasSize = function(width, height) { 173 | if (width) { 174 | this.resizable = false; 175 | } 176 | this.width = width || this.width; 177 | this.height = height || this.height; 178 | this.canvas.width = this.width; 179 | this.canvas.height = this.height; 180 | return this; 181 | }; 182 | 183 | /** 184 | * create an array and populate it with new Sparticle instances. 185 | * @returns {Array} the array of Sparticle instances 186 | */ 187 | this.resetSparticles = this.createSparticles = function() { 188 | this.sparticles = []; 189 | this.ctx.globalCompositeOperation = this.settings.composition; 190 | for (let i = 0; i < this.settings.count; i++) { 191 | this.sparticles.push(new Sparticle(this, i)); 192 | } 193 | this.sort(); 194 | return this.sparticles; 195 | }; 196 | 197 | /** 198 | * sort the particle array by size so that parallax effect 199 | * doesn't appear to have slower/smaller particles in foreground 200 | */ 201 | this.sort = function() { 202 | if (this.settings.parallax) { 203 | this.sparticles.sort((a, b) => a.size - b.size); 204 | } 205 | }; 206 | 207 | // initialise the sparticles, and return the instance. 208 | return this.init(); 209 | }; 210 | 211 | /** 212 | * convert the input color to an array if it isn't already 213 | * @returns {Array} - array of colors for use in rendering 214 | */ 215 | Sparticles.prototype.getColorArray = function() { 216 | let colors = Array.isArray(this.settings.color) ? this.settings.color : [this.settings.color]; 217 | const isRandom = colors.some(c => c === "random"); 218 | 219 | if (isRandom) { 220 | for (let i = 0; i < this.settings.randomColorCount; i++) { 221 | colors[i] = this.settings.randomColor(i, this.settings.randomColorCount); 222 | } 223 | } 224 | 225 | return colors; 226 | }; 227 | 228 | /** 229 | * convert the input shape to an array if it isn't already 230 | * @returns {Array} - array of shapes for use in rendering 231 | */ 232 | Sparticles.prototype.getShapeArray = function() { 233 | let shapes = Array.isArray(this.settings.shape) ? this.settings.shape : [this.settings.shape]; 234 | const isRandom = shapes.some(c => c === "random"); 235 | 236 | if (isRandom) { 237 | shapes = ["square", "circle", "triangle"]; 238 | } 239 | 240 | return shapes; 241 | }; 242 | 243 | /** 244 | * convert the imageUrl option to an array if it isn't already 245 | * @returns {Array} - array of image urls for use in rendering 246 | */ 247 | Sparticles.prototype.getImageArray = function() { 248 | return Array.isArray(this.settings.imageUrl) ? this.settings.imageUrl : [this.settings.imageUrl]; 249 | }; 250 | 251 | /** 252 | * convert the input style to an array 253 | * @returns {Array} - array of styles for use in rendering 254 | */ 255 | Sparticles.prototype.getStyleArray = function() { 256 | let styles = this.settings.style; 257 | if (styles !== "fill" && styles !== "stroke") { 258 | styles = ["fill", "stroke"]; 259 | } else { 260 | styles = [styles]; 261 | } 262 | return styles; 263 | }; 264 | 265 | /** 266 | * set up the canvas and bind to a property for 267 | * access later on, append it to the DOM 268 | * @returns {HTMLCanvasElement} - the canvas element which was appended to DOM 269 | */ 270 | Sparticles.prototype.setupMainCanvas = function() { 271 | this.canvas = document.createElement("canvas"); 272 | this.canvas.setAttribute("class", "sparticles"); 273 | this.ctx = this.canvas.getContext("2d"); 274 | this.setCanvasSize(); 275 | this.el.appendChild(this.canvas); 276 | return this.canvas; 277 | }; 278 | 279 | /** 280 | * create a new offscreen canvas element for each color & shape 281 | * combination, so that we can reference it later during render 282 | * (huge performance gains here) 283 | * @param {Function} [callback] - function to execute after image loads 284 | * @returns {HTMLCanvasElement} - the created offscreen canvas 285 | */ 286 | Sparticles.prototype.setupOffscreenCanvasses = function(callback) { 287 | const colors = this.colors.filter((item, index) => this.colors.indexOf(item) === index); 288 | const shapes = this.shapes.filter((item, index) => this.shapes.indexOf(item) === index); 289 | const styles = this.styles.filter((item, index) => this.styles.indexOf(item) === index); 290 | const imageUrls = this.imageUrls.filter((item, index) => this.imageUrls.indexOf(item) === index); 291 | const imageCount = colors.length * imageUrls.length; 292 | const canvasCount = colors.length * shapes.length * styles.length; 293 | let imagesLoaded = 0; 294 | let canvassesCreated = 0; 295 | 296 | this.canvasses = this.canvasses || {}; 297 | 298 | colors.forEach(color => { 299 | this.canvasses[color] = this.canvasses[color] || {}; 300 | 301 | shapes.forEach(shape => { 302 | this.canvasses[color][shape] = this.canvasses[color][shape] || {}; 303 | 304 | if (shape === "image") { 305 | imageUrls.forEach((imageUrl, i) => { 306 | let image = new Image(); 307 | const imageCanvas = document.createElement("canvas"); 308 | this.canvasses[color][shape][imageUrl] = imageCanvas; 309 | 310 | image.onload = () => { 311 | imagesLoaded++; 312 | this.drawOffscreenCanvasForImage(image, color, imageCanvas); 313 | if (callback && imagesLoaded === imageCount) { 314 | callback(); 315 | } 316 | }; 317 | 318 | image.onerror = () => { 319 | console.error("failed to load source image: ", imageUrl); 320 | }; 321 | 322 | image.src = imageUrl; 323 | }); 324 | } else { 325 | styles.forEach(style => { 326 | const canvas = document.createElement("canvas"); 327 | this.canvasses[color][shape][style] = canvas; 328 | canvassesCreated++; 329 | 330 | this.drawOffscreenCanvas(shape, style, color, canvas); 331 | 332 | if (callback && canvassesCreated === canvasCount) { 333 | callback(); 334 | } 335 | }); 336 | } 337 | }); 338 | }); 339 | }; 340 | 341 | /** 342 | * return the size of the glow effect (shadowBlur) for each particle 343 | * @param {Number} size - the size of the particle 344 | * @returns {Number} - the size of the glow/shadow 345 | */ 346 | Sparticles.prototype.getGlowSize = function(size) { 347 | return this.settings.glow; 348 | }; 349 | 350 | /** 351 | * return the outline or stroke size of each particle 352 | * @param {Number} size - the size of the particle 353 | * @returns {Number} - the size of the outline/stroke 354 | */ 355 | Sparticles.prototype.getLineSize = function(size) { 356 | return clamp(size / 20, 1, 5); 357 | }; 358 | 359 | /** 360 | * return the offscreenCanvas size to generate for 361 | * @returns {Number} - the maxSize of the offscreen canvas 362 | */ 363 | Sparticles.prototype.getOffscreenCanvasSize = function() { 364 | return clamp(this.settings.maxSize, this.settings.minSize, this.settings.maxSize); 365 | }; 366 | 367 | /** 368 | * set the fill/stroke style (color & width) for each particle's offscreen canvas 369 | * @param {CanvasRenderingContext2D} ctx - the canvas context 370 | * @param {String} color - the color to fill/stroke with 371 | * @param {Number} lineSize - size/thickness of the stroke 372 | * @param {String} style - style (either "fill" or "stroke") 373 | */ 374 | Sparticles.prototype.renderStyle = function(ctx, color, lineSize, style) { 375 | if (style === "fill") { 376 | ctx.fillStyle = color; 377 | } else { 378 | ctx.lineWidth = lineSize; 379 | ctx.strokeStyle = color; 380 | } 381 | }; 382 | 383 | /** 384 | * set the shadowBlur (glow effect) for each particle's offscreen canvas 385 | * @param {CanvasRenderingContext2D} ctx - the canvas context 386 | * @param {String} color - the color to fill/stroke with 387 | * @param {Number} size - size of the shadow/glow 388 | */ 389 | Sparticles.prototype.renderGlow = function(ctx, color, size) { 390 | const glowSize = this.getGlowSize(size) / 2; 391 | ctx.shadowColor = color; 392 | ctx.shadowBlur = glowSize; 393 | }; 394 | 395 | /** 396 | * fill or stroke each particle's offscreen canvas depending on the given setting 397 | * @param {CanvasRenderingContext2D} ctx - the canvas context 398 | * @param {String} style - style (either "fill" or "stroke") 399 | */ 400 | Sparticles.prototype.renderColor = function(ctx, style, path) { 401 | if (style === "fill") { 402 | if (path) { 403 | ctx.fill(path); 404 | } else { 405 | ctx.fill(); 406 | } 407 | } else { 408 | if (path) { 409 | ctx.stroke(path); 410 | } else { 411 | ctx.stroke(); 412 | } 413 | } 414 | }; 415 | 416 | /** 417 | * pass-through the needed parameters to the offscreen canvas 418 | * draw function associated with the given shape 419 | * @param {String} shape - shape of the canvas to draw (eg: "circle") 420 | * @param {String} style - style (either "fill" or "stroke") 421 | * @param {String} color - the color to fill/stroke with 422 | * @param {HTMLCanvasElement} canvas - the canvas element 423 | * @returns {HTMLCanvasElement} - the created offscreen canvas 424 | */ 425 | Sparticles.prototype.drawOffscreenCanvas = function(shape, style, color, canvas) { 426 | return this.offScreenCanvas[shape].call(this, style, color, canvas); 427 | }; 428 | 429 | /** 430 | * object of shapes to draw 431 | */ 432 | Sparticles.prototype.offScreenCanvas = {}; 433 | 434 | /** 435 | * create, setup and render an offscreen canvas for a 436 | * Circle Particle of the given color 437 | * @param {String} style - style (either "fill" or "stroke") 438 | * @param {String} color - the color to fill/stroke with 439 | * @param {HTMLCanvasElement} canvas - the canvas element 440 | * @returns {HTMLCanvasElement} - the created offscreen canvas 441 | */ 442 | Sparticles.prototype.offScreenCanvas.circle = function(style, color, canvas) { 443 | const ctx = canvas.getContext("2d"); 444 | const size = this.getOffscreenCanvasSize(); 445 | const lineSize = this.getLineSize(size); 446 | const glowSize = this.getGlowSize(size); 447 | const canvasSize = size + lineSize * 2 + glowSize; 448 | const shapeSize = style === "stroke" ? size - lineSize : size; 449 | canvas.width = canvasSize; 450 | canvas.height = canvasSize; 451 | this.renderGlow(ctx, color, size); 452 | this.renderStyle(ctx, color, lineSize, style); 453 | ctx.beginPath(); 454 | ctx.ellipse(canvasSize / 2, canvasSize / 2, shapeSize / 2, shapeSize / 2, 0, 0, 360); 455 | this.renderColor(ctx, style); 456 | return canvas; 457 | }; 458 | 459 | /** 460 | * create, setup and render an offscreen canvas for a 461 | * Square Particle of the given color 462 | * @param {String} style - style (either "fill" or "stroke") 463 | * @param {String} color - the color to fill/stroke with 464 | * @param {HTMLCanvasElement} canvas - the canvas element 465 | * @returns {HTMLCanvasElement} - the created offscreen canvas 466 | */ 467 | Sparticles.prototype.offScreenCanvas.square = function(style, color, canvas) { 468 | const ctx = canvas.getContext("2d"); 469 | const size = this.getOffscreenCanvasSize(); 470 | const lineSize = this.getLineSize(size); 471 | const glowSize = this.getGlowSize(size); 472 | const canvasSize = size + lineSize * 2 + glowSize; 473 | const shapeSize = style === "stroke" ? size - lineSize : size; 474 | canvas.width = canvasSize; 475 | canvas.height = canvasSize; 476 | this.renderGlow(ctx, color, size); 477 | this.renderStyle(ctx, color, lineSize, style); 478 | ctx.beginPath(); 479 | ctx.rect(canvasSize / 2 - shapeSize / 2, canvasSize / 2 - shapeSize / 2, shapeSize, shapeSize); 480 | this.renderColor(ctx, style); 481 | return canvas; 482 | }; 483 | 484 | /** 485 | * create, setup and render an offscreen canvas for a 486 | * Line/Curve Particle of the given color 487 | * @param {String} style - style (either "fill" or "stroke") 488 | * @param {String} color - the color to fill/stroke with 489 | * @param {HTMLCanvasElement} canvas - the canvas element 490 | * @returns {HTMLCanvasElement} - the created offscreen canvas 491 | */ 492 | Sparticles.prototype.offScreenCanvas.line = function(style, color, canvas) { 493 | const ctx = canvas.getContext("2d"); 494 | const size = this.getOffscreenCanvasSize() * 1.5; 495 | const lineSize = this.getLineSize(size); 496 | const glowSize = this.getGlowSize(size); 497 | const canvasSize = size + lineSize * 2 + glowSize; 498 | const startx = canvasSize / 2 - size / 2; 499 | const starty = canvasSize / 2 - size / 2; 500 | canvas.width = canvasSize; 501 | canvas.height = canvasSize; 502 | this.renderGlow(ctx, color, size); 503 | ctx.lineWidth = lineSize; 504 | ctx.strokeStyle = color; 505 | ctx.beginPath(); 506 | ctx.moveTo(startx, starty); 507 | ctx.lineTo(startx + size, starty + size); 508 | ctx.stroke(); 509 | ctx.closePath(); 510 | return canvas; 511 | }; 512 | 513 | /** 514 | * create, setup and render an offscreen canvas for a 515 | * Triangle Particle of the given color 516 | * @param {String} style - style (either "fill" or "stroke") 517 | * @param {String} color - the color to fill/stroke with 518 | * @param {HTMLCanvasElement} canvas - the canvas element 519 | * @returns {HTMLCanvasElement} - the created offscreen canvas 520 | */ 521 | Sparticles.prototype.offScreenCanvas.triangle = function(style, color, canvas) { 522 | const ctx = canvas.getContext("2d"); 523 | const size = this.getOffscreenCanvasSize(); 524 | const lineSize = this.getLineSize(size); 525 | const glowSize = this.getGlowSize(size); 526 | const canvasSize = size + lineSize * 2 + glowSize; 527 | const shapeSize = style === "stroke" ? size - lineSize : size; 528 | const height = shapeSize * (Math.sqrt(3) / 2); 529 | const startx = canvasSize / 2; 530 | const starty = canvasSize / 2 - shapeSize / 2; 531 | canvas.width = canvasSize; 532 | canvas.height = canvasSize; 533 | this.renderGlow(ctx, color, size); 534 | this.renderStyle(ctx, color, lineSize, style); 535 | ctx.beginPath(); 536 | ctx.moveTo(startx, starty); 537 | ctx.lineTo(startx - shapeSize / 2, starty + height); 538 | ctx.lineTo(startx + shapeSize / 2, starty + height); 539 | ctx.closePath(); 540 | this.renderColor(ctx, style); 541 | return canvas; 542 | }; 543 | 544 | /** 545 | * create, setup and render an offscreen canvas for a 546 | * Diamond Sparkle Particle of the given color 547 | * @param {String} style - style (either "fill" or "stroke") 548 | * @param {String} color - the color to fill/stroke with 549 | * @param {HTMLCanvasElement} canvas - the canvas element 550 | * @returns {HTMLCanvasElement} - the created offscreen canvas 551 | */ 552 | Sparticles.prototype.offScreenCanvas.diamond = function(style, color, canvas) { 553 | const pathSize = 100; 554 | const path = new Path2D( 555 | "M43,83.74,48.63,99a1.46,1.46,0,0,0,2.74,0L57,83.74A45.09,45.09,0,0,1,83.74,57L99,51.37a1.46,1.46,0,0,0,0-2.74L83.74,43A45.11,45.11,0,0,1,57,16.26L51.37,1a1.46,1.46,0,0,0-2.74,0L43,16.26A45.11,45.11,0,0,1,16.26,43L1,48.63a1.46,1.46,0,0,0,0,2.74L16.26,57A45.09,45.09,0,0,1,43,83.74Z" 556 | ); 557 | const ctx = canvas.getContext("2d"); 558 | const size = this.getOffscreenCanvasSize(); 559 | const lineSize = this.getLineSize(size); 560 | const glowSize = this.getGlowSize(size); 561 | const canvasSize = size + lineSize * 2 + glowSize; 562 | const scale = canvasSize / ((pathSize + glowSize) * 1.1); 563 | canvas.width = canvasSize; 564 | canvas.height = canvasSize; 565 | this.renderGlow(ctx, color, size); 566 | this.renderStyle(ctx, color, lineSize / scale, style); 567 | ctx.scale(scale, scale); 568 | ctx.translate(pathSize * 0.05 + glowSize * 0.5, pathSize * 0.05 + glowSize * 0.5); 569 | this.renderColor(ctx, style, path); 570 | return canvas; 571 | }; 572 | 573 | /** 574 | * create, setup and render an offscreen canvas for a 575 | * Star Particle of the given color 576 | * @param {String} style - style (either "fill" or "stroke") 577 | * @param {String} color - the color to fill/stroke with 578 | * @param {HTMLCanvasElement} canvas - the canvas element 579 | * @returns {HTMLCanvasElement} - the created offscreen canvas 580 | */ 581 | Sparticles.prototype.offScreenCanvas.star = function(style, color, canvas) { 582 | const pathSize = 100; 583 | const path = new Path2D( 584 | "M99.86,36.45a2.94,2.94,0,0,0-2.37-2l-31-4.54L52.63,1.64a2.93,2.93,0,0,0-5.26,0L33.51,29.91l-31,4.54a3,3,0,0,0-2.37,2,3,3,0,0,0,.74,3l22.44,22L18,92.55A2.94,2.94,0,0,0,20.91,96a2.86,2.86,0,0,0,1.36-.34L50,81,77.73,95.66a2.91,2.91,0,0,0,3.08-.22A3,3,0,0,0,82,92.55l-5.3-31.07,22.44-22A3,3,0,0,0,99.86,36.45Z" 585 | ); 586 | const ctx = canvas.getContext("2d"); 587 | const size = this.getOffscreenCanvasSize(); 588 | const lineSize = this.getLineSize(size); 589 | const glowSize = this.getGlowSize(size); 590 | const canvasSize = size + lineSize * 2 + glowSize; 591 | const scale = canvasSize / ((pathSize + glowSize) * 1.1); 592 | canvas.width = canvasSize; 593 | canvas.height = canvasSize; 594 | ctx.scale(scale, scale); 595 | this.renderGlow(ctx, color, size); 596 | this.renderStyle(ctx, color, lineSize / scale, style); 597 | ctx.translate(pathSize * 0.05 + glowSize * 0.5, pathSize * 0.05 + glowSize * 0.5); 598 | this.renderColor(ctx, style, path); 599 | return canvas; 600 | }; 601 | 602 | /** 603 | * create, setup and render an offscreen canvas for a 604 | * Custom Image Particle of the given color 605 | * @param {HTMLImageElement} image - the image element that has loaded 606 | * @param {String} color - the color to fill/stroke with 607 | * @param {HTMLCanvasElement} canvas - the canvas element 608 | * @returns {HTMLCanvasElement} - the created offscreen canvas 609 | */ 610 | Sparticles.prototype.drawOffscreenCanvasForImage = function(image, color, canvas) { 611 | const size = image.width; 612 | const ctx = canvas.getContext("2d"); 613 | canvas.width = size; 614 | canvas.height = size; 615 | ctx.drawImage(image, 0, 0, size, size, 0, 0, size, size); 616 | ctx.globalCompositeOperation = "source-atop"; 617 | ctx.fillStyle = color; 618 | ctx.fillRect(0, 0, size, size); 619 | return canvas; 620 | }; 621 | 622 | /** 623 | * - wipe the canvas, 624 | * - update each sparticle, 625 | * - render each sparticle 626 | * - sort so that larger particles on top 627 | * @returns {Array} the array of Sparticle instances 628 | */ 629 | Sparticles.prototype.drawFrame = function() { 630 | this.ctx.clearRect(0, 0, this.width, this.height); 631 | for (let i = 0; i < this.sparticles.length; i++) { 632 | let sparticle = this.sparticles[i]; 633 | sparticle.update().render(this.canvasses); 634 | } 635 | this.sort(); 636 | return this.sparticles; 637 | }; 638 | 639 | export default Sparticles; 640 | -------------------------------------------------------------------------------- /example/svelte/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@^7.5.5": 6 | version "7.16.0" 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" 8 | integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== 9 | dependencies: 10 | "@babel/highlight" "^7.16.0" 11 | 12 | "@babel/helper-validator-identifier@^7.15.7": 13 | version "7.15.7" 14 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" 15 | integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== 16 | 17 | "@babel/highlight@^7.16.0": 18 | version "7.16.0" 19 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" 20 | integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== 21 | dependencies: 22 | "@babel/helper-validator-identifier" "^7.15.7" 23 | chalk "^2.0.0" 24 | js-tokens "^4.0.0" 25 | 26 | "@polka/url@^0.5.0": 27 | version "0.5.0" 28 | resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31" 29 | integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw== 30 | 31 | "@rollup/plugin-commonjs@^11.0.0": 32 | version "11.1.0" 33 | resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz#60636c7a722f54b41e419e1709df05c7234557ef" 34 | integrity sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA== 35 | dependencies: 36 | "@rollup/pluginutils" "^3.0.8" 37 | commondir "^1.0.1" 38 | estree-walker "^1.0.1" 39 | glob "^7.1.2" 40 | is-reference "^1.1.2" 41 | magic-string "^0.25.2" 42 | resolve "^1.11.0" 43 | 44 | "@rollup/plugin-node-resolve@^7.0.0": 45 | version "7.1.3" 46 | resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca" 47 | integrity sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q== 48 | dependencies: 49 | "@rollup/pluginutils" "^3.0.8" 50 | "@types/resolve" "0.0.8" 51 | builtin-modules "^3.1.0" 52 | is-module "^1.0.0" 53 | resolve "^1.14.2" 54 | 55 | "@rollup/pluginutils@^3.0.8": 56 | version "3.1.0" 57 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" 58 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== 59 | dependencies: 60 | "@types/estree" "0.0.39" 61 | estree-walker "^1.0.1" 62 | picomatch "^2.2.2" 63 | 64 | "@types/estree@*": 65 | version "0.0.50" 66 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" 67 | integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== 68 | 69 | "@types/estree@0.0.39": 70 | version "0.0.39" 71 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 72 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 73 | 74 | "@types/node@*": 75 | version "17.0.1" 76 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.1.tgz#88d501e84b6185f6489ecee4ba9e8fcec7f29bb2" 77 | integrity sha512-NXKvBVUzIbs6ylBwmOwHFkZS2EXCcjnqr8ZCRNaXBkHAf+3mn/rPcJxwrzuc6movh8fxQAsUUfYklJ/EG+hZqQ== 78 | 79 | "@types/resolve@0.0.8": 80 | version "0.0.8" 81 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" 82 | integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== 83 | dependencies: 84 | "@types/node" "*" 85 | 86 | acorn@^7.1.0: 87 | version "7.4.1" 88 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" 89 | integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== 90 | 91 | ansi-styles@^3.2.1: 92 | version "3.2.1" 93 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 94 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 95 | dependencies: 96 | color-convert "^1.9.0" 97 | 98 | anymatch@~3.1.2: 99 | version "3.1.2" 100 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 101 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 102 | dependencies: 103 | normalize-path "^3.0.0" 104 | picomatch "^2.0.4" 105 | 106 | balanced-match@^1.0.0: 107 | version "1.0.2" 108 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 109 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 110 | 111 | binary-extensions@^2.0.0: 112 | version "2.2.0" 113 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 114 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 115 | 116 | brace-expansion@^1.1.7: 117 | version "1.1.11" 118 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 119 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 120 | dependencies: 121 | balanced-match "^1.0.0" 122 | concat-map "0.0.1" 123 | 124 | braces@~3.0.2: 125 | version "3.0.2" 126 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 127 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 128 | dependencies: 129 | fill-range "^7.0.1" 130 | 131 | buffer-from@^1.0.0: 132 | version "1.1.2" 133 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 134 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 135 | 136 | builtin-modules@^3.1.0: 137 | version "3.2.0" 138 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" 139 | integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== 140 | 141 | chalk@^2.0.0: 142 | version "2.4.2" 143 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 144 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 145 | dependencies: 146 | ansi-styles "^3.2.1" 147 | escape-string-regexp "^1.0.5" 148 | supports-color "^5.3.0" 149 | 150 | chokidar@^3.5.0: 151 | version "3.5.2" 152 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" 153 | integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== 154 | dependencies: 155 | anymatch "~3.1.2" 156 | braces "~3.0.2" 157 | glob-parent "~5.1.2" 158 | is-binary-path "~2.1.0" 159 | is-glob "~4.0.1" 160 | normalize-path "~3.0.0" 161 | readdirp "~3.6.0" 162 | optionalDependencies: 163 | fsevents "~2.3.2" 164 | 165 | color-convert@^1.9.0: 166 | version "1.9.3" 167 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 168 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 169 | dependencies: 170 | color-name "1.1.3" 171 | 172 | color-name@1.1.3: 173 | version "1.1.3" 174 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 175 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 176 | 177 | commander@^2.20.0: 178 | version "2.20.3" 179 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 180 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 181 | 182 | commondir@^1.0.1: 183 | version "1.0.1" 184 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" 185 | integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= 186 | 187 | concat-map@0.0.1: 188 | version "0.0.1" 189 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 190 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 191 | 192 | console-clear@^1.1.0: 193 | version "1.1.1" 194 | resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7" 195 | integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ== 196 | 197 | escape-string-regexp@^1.0.5: 198 | version "1.0.5" 199 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 200 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 201 | 202 | estree-walker@^0.6.1: 203 | version "0.6.1" 204 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" 205 | integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== 206 | 207 | estree-walker@^1.0.1: 208 | version "1.0.1" 209 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" 210 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== 211 | 212 | fill-range@^7.0.1: 213 | version "7.0.1" 214 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 215 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 216 | dependencies: 217 | to-regex-range "^5.0.1" 218 | 219 | fs.realpath@^1.0.0: 220 | version "1.0.0" 221 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 222 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 223 | 224 | fsevents@~2.3.2: 225 | version "2.3.2" 226 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 227 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 228 | 229 | function-bind@^1.1.1: 230 | version "1.1.1" 231 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 232 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 233 | 234 | get-port@^3.2.0: 235 | version "3.2.0" 236 | resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" 237 | integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= 238 | 239 | glob-parent@~5.1.2: 240 | version "5.1.2" 241 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 242 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 243 | dependencies: 244 | is-glob "^4.0.1" 245 | 246 | glob@^7.1.2: 247 | version "7.2.0" 248 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" 249 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== 250 | dependencies: 251 | fs.realpath "^1.0.0" 252 | inflight "^1.0.4" 253 | inherits "2" 254 | minimatch "^3.0.4" 255 | once "^1.3.0" 256 | path-is-absolute "^1.0.0" 257 | 258 | has-flag@^3.0.0: 259 | version "3.0.0" 260 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 261 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 262 | 263 | has@^1.0.3: 264 | version "1.0.3" 265 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 266 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 267 | dependencies: 268 | function-bind "^1.1.1" 269 | 270 | inflight@^1.0.4: 271 | version "1.0.6" 272 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 273 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 274 | dependencies: 275 | once "^1.3.0" 276 | wrappy "1" 277 | 278 | inherits@2: 279 | version "2.0.4" 280 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 281 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 282 | 283 | is-binary-path@~2.1.0: 284 | version "2.1.0" 285 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 286 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 287 | dependencies: 288 | binary-extensions "^2.0.0" 289 | 290 | is-core-module@^2.2.0: 291 | version "2.8.0" 292 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" 293 | integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== 294 | dependencies: 295 | has "^1.0.3" 296 | 297 | is-extglob@^2.1.1: 298 | version "2.1.1" 299 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 300 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 301 | 302 | is-glob@^4.0.1, is-glob@~4.0.1: 303 | version "4.0.3" 304 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 305 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 306 | dependencies: 307 | is-extglob "^2.1.1" 308 | 309 | is-module@^1.0.0: 310 | version "1.0.0" 311 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" 312 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= 313 | 314 | is-number@^7.0.0: 315 | version "7.0.0" 316 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 317 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 318 | 319 | is-reference@^1.1.2: 320 | version "1.2.1" 321 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" 322 | integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== 323 | dependencies: 324 | "@types/estree" "*" 325 | 326 | jest-worker@^24.9.0: 327 | version "24.9.0" 328 | resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" 329 | integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== 330 | dependencies: 331 | merge-stream "^2.0.0" 332 | supports-color "^6.1.0" 333 | 334 | js-tokens@^4.0.0: 335 | version "4.0.0" 336 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 337 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 338 | 339 | kleur@^3.0.0: 340 | version "3.0.3" 341 | resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" 342 | integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== 343 | 344 | livereload-js@^3.3.1: 345 | version "3.3.2" 346 | resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-3.3.2.tgz#c88b009c6e466b15b91faa26fd7c99d620e12651" 347 | integrity sha512-w677WnINxFkuixAoUEXOStewzLYGI76XVag+0JWMMEyjJQKs0ibWZMxkTlB96Lm3EjZ7IeOxVziBEbtxVQqQZA== 348 | 349 | livereload@^0.9.1: 350 | version "0.9.3" 351 | resolved "https://registry.yarnpkg.com/livereload/-/livereload-0.9.3.tgz#a714816375ed52471408bede8b49b2ee6a0c55b1" 352 | integrity sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw== 353 | dependencies: 354 | chokidar "^3.5.0" 355 | livereload-js "^3.3.1" 356 | opts ">= 1.2.0" 357 | ws "^7.4.3" 358 | 359 | local-access@^1.0.1: 360 | version "1.1.0" 361 | resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.1.0.tgz#e007c76ba2ca83d5877ba1a125fc8dfe23ba4798" 362 | integrity sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw== 363 | 364 | magic-string@^0.25.2: 365 | version "0.25.7" 366 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" 367 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== 368 | dependencies: 369 | sourcemap-codec "^1.4.4" 370 | 371 | merge-stream@^2.0.0: 372 | version "2.0.0" 373 | resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" 374 | integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== 375 | 376 | mime@^2.3.1: 377 | version "2.6.0" 378 | resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" 379 | integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== 380 | 381 | minimatch@^3.0.4: 382 | version "3.0.4" 383 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 384 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 385 | dependencies: 386 | brace-expansion "^1.1.7" 387 | 388 | mri@^1.1.0: 389 | version "1.2.0" 390 | resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" 391 | integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== 392 | 393 | normalize-path@^3.0.0, normalize-path@~3.0.0: 394 | version "3.0.0" 395 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 396 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 397 | 398 | once@^1.3.0: 399 | version "1.4.0" 400 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 401 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 402 | dependencies: 403 | wrappy "1" 404 | 405 | "opts@>= 1.2.0": 406 | version "2.0.2" 407 | resolved "https://registry.yarnpkg.com/opts/-/opts-2.0.2.tgz#a17e189fbbfee171da559edd8a42423bc5993ce1" 408 | integrity sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg== 409 | 410 | path-is-absolute@^1.0.0: 411 | version "1.0.1" 412 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 413 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 414 | 415 | path-parse@^1.0.6: 416 | version "1.0.7" 417 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 418 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 419 | 420 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2: 421 | version "2.3.0" 422 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 423 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== 424 | 425 | randombytes@^2.1.0: 426 | version "2.1.0" 427 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 428 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 429 | dependencies: 430 | safe-buffer "^5.1.0" 431 | 432 | readdirp@~3.6.0: 433 | version "3.6.0" 434 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 435 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 436 | dependencies: 437 | picomatch "^2.2.1" 438 | 439 | require-relative@^0.8.7: 440 | version "0.8.7" 441 | resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" 442 | integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= 443 | 444 | resolve@^1.11.0, resolve@^1.14.2: 445 | version "1.20.0" 446 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" 447 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== 448 | dependencies: 449 | is-core-module "^2.2.0" 450 | path-parse "^1.0.6" 451 | 452 | rollup-plugin-livereload@^1.0.0: 453 | version "1.3.0" 454 | resolved "https://registry.yarnpkg.com/rollup-plugin-livereload/-/rollup-plugin-livereload-1.3.0.tgz#8da90df13df6502b9d982997d6ac871092f15fdd" 455 | integrity sha512-abyqXaB21+nFHo+vJULBqfzNx6zXABC19UyvqgDfdoxR/8pFAd041GO+GIUe8ZYC2DbuMUmioh1Lvbk14YLZgw== 456 | dependencies: 457 | livereload "^0.9.1" 458 | 459 | rollup-plugin-svelte@^5.0.3: 460 | version "5.2.3" 461 | resolved "https://registry.yarnpkg.com/rollup-plugin-svelte/-/rollup-plugin-svelte-5.2.3.tgz#efdc15e3e3fdd9b9f1100fdc14a8532b4e587bc8" 462 | integrity sha512-513vOht9A93OV7fvmpIq8mD1JFgTZ5LidmpULKM2Od9P1l8oI5KwvO32fwCnASuVJS1EkRfvCnS7vKQ8DF4srg== 463 | dependencies: 464 | require-relative "^0.8.7" 465 | rollup-pluginutils "^2.8.2" 466 | sourcemap-codec "^1.4.8" 467 | 468 | rollup-plugin-terser@^5.1.2: 469 | version "5.3.1" 470 | resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz#8c650062c22a8426c64268548957463bf981b413" 471 | integrity sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w== 472 | dependencies: 473 | "@babel/code-frame" "^7.5.5" 474 | jest-worker "^24.9.0" 475 | rollup-pluginutils "^2.8.2" 476 | serialize-javascript "^4.0.0" 477 | terser "^4.6.2" 478 | 479 | rollup-pluginutils@^2.8.2: 480 | version "2.8.2" 481 | resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" 482 | integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== 483 | dependencies: 484 | estree-walker "^0.6.1" 485 | 486 | rollup@^1.20.0: 487 | version "1.32.1" 488 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4" 489 | integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A== 490 | dependencies: 491 | "@types/estree" "*" 492 | "@types/node" "*" 493 | acorn "^7.1.0" 494 | 495 | sade@^1.4.0: 496 | version "1.7.4" 497 | resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691" 498 | integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA== 499 | dependencies: 500 | mri "^1.1.0" 501 | 502 | safe-buffer@^5.1.0: 503 | version "5.2.1" 504 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 505 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 506 | 507 | serialize-javascript@^4.0.0: 508 | version "4.0.0" 509 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" 510 | integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== 511 | dependencies: 512 | randombytes "^2.1.0" 513 | 514 | sirv-cli@^0.4.4: 515 | version "0.4.6" 516 | resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-0.4.6.tgz#c28ab20deb3b34637f5a60863dc350f055abca04" 517 | integrity sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww== 518 | dependencies: 519 | console-clear "^1.1.0" 520 | get-port "^3.2.0" 521 | kleur "^3.0.0" 522 | local-access "^1.0.1" 523 | sade "^1.4.0" 524 | sirv "^0.4.6" 525 | tinydate "^1.0.0" 526 | 527 | sirv@^0.4.6: 528 | version "0.4.6" 529 | resolved "https://registry.yarnpkg.com/sirv/-/sirv-0.4.6.tgz#185e44eb93d24009dd183b7494285c5180b81f22" 530 | integrity sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ== 531 | dependencies: 532 | "@polka/url" "^0.5.0" 533 | mime "^2.3.1" 534 | 535 | source-map-support@~0.5.12: 536 | version "0.5.21" 537 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 538 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 539 | dependencies: 540 | buffer-from "^1.0.0" 541 | source-map "^0.6.0" 542 | 543 | source-map@^0.6.0, source-map@~0.6.1: 544 | version "0.6.1" 545 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 546 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 547 | 548 | sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8: 549 | version "1.4.8" 550 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" 551 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== 552 | 553 | sparticles@../../: 554 | version "1.3.0" 555 | 556 | supports-color@^5.3.0: 557 | version "5.5.0" 558 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 559 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 560 | dependencies: 561 | has-flag "^3.0.0" 562 | 563 | supports-color@^6.1.0: 564 | version "6.1.0" 565 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" 566 | integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== 567 | dependencies: 568 | has-flag "^3.0.0" 569 | 570 | svelte@^3.0.0: 571 | version "3.44.3" 572 | resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.44.3.tgz#795b1ced6ed3da44969099e5061b850c93c95e9a" 573 | integrity sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg== 574 | 575 | terser@^4.6.2: 576 | version "4.8.0" 577 | resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" 578 | integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== 579 | dependencies: 580 | commander "^2.20.0" 581 | source-map "~0.6.1" 582 | source-map-support "~0.5.12" 583 | 584 | tinydate@^1.0.0: 585 | version "1.3.0" 586 | resolved "https://registry.yarnpkg.com/tinydate/-/tinydate-1.3.0.tgz#e6ca8e5a22b51bb4ea1c3a2a4fd1352dbd4c57fb" 587 | integrity sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w== 588 | 589 | to-regex-range@^5.0.1: 590 | version "5.0.1" 591 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 592 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 593 | dependencies: 594 | is-number "^7.0.0" 595 | 596 | wrappy@1: 597 | version "1.0.2" 598 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 599 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 600 | 601 | ws@^7.4.3: 602 | version "7.5.6" 603 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" 604 | integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== 605 | -------------------------------------------------------------------------------- /dist/sparticles.esm.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * Sparticles - Lightweight, High Performance Particles in Canvas 3 | * @version 1.3.1 4 | * @license MPL-2.0 5 | * @author simeydotme 6 | * @website http://sparticlesjs.dev 7 | * @repository https://github.com/simeydotme/sparticles.git 8 | */ 9 | 10 | function ownKeys(object, enumerableOnly) { 11 | var keys = Object.keys(object); 12 | 13 | if (Object.getOwnPropertySymbols) { 14 | var symbols = Object.getOwnPropertySymbols(object); 15 | 16 | if (enumerableOnly) { 17 | symbols = symbols.filter(function (sym) { 18 | return Object.getOwnPropertyDescriptor(object, sym).enumerable; 19 | }); 20 | } 21 | 22 | keys.push.apply(keys, symbols); 23 | } 24 | 25 | return keys; 26 | } 27 | 28 | function _objectSpread2(target) { 29 | for (var i = 1; i < arguments.length; i++) { 30 | var source = arguments[i] != null ? arguments[i] : {}; 31 | 32 | if (i % 2) { 33 | ownKeys(Object(source), true).forEach(function (key) { 34 | _defineProperty(target, key, source[key]); 35 | }); 36 | } else if (Object.getOwnPropertyDescriptors) { 37 | Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); 38 | } else { 39 | ownKeys(Object(source)).forEach(function (key) { 40 | Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); 41 | }); 42 | } 43 | } 44 | 45 | return target; 46 | } 47 | 48 | function _defineProperty(obj, key, value) { 49 | if (key in obj) { 50 | Object.defineProperty(obj, key, { 51 | value: value, 52 | enumerable: true, 53 | configurable: true, 54 | writable: true 55 | }); 56 | } else { 57 | obj[key] = value; 58 | } 59 | 60 | return obj; 61 | } 62 | 63 | /** 64 | * Limited Animation Frame method, to allow running 65 | * a given handler at the maximum desired frame rate. 66 | * inspired from https://gist.github.com/addyosmani/5434533 67 | * @param {Function} handler method to execute upon every frame 68 | * @param {Number} fps how many frames to render every second 69 | */ 70 | var AnimationFrame = function AnimationFrame() { 71 | var handler = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {}; 72 | var fps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60; 73 | this.fps = fps; 74 | this.handler = handler; 75 | var renderId = 0; 76 | /** 77 | * begin the animation loop which is assigned 78 | * to the instance in the constructor 79 | */ 80 | 81 | this.start = function () { 82 | var _this = this; 83 | 84 | if (!this.started) { 85 | var then = performance.now(); 86 | var interval = 1000 / this.fps; 87 | var tolerance = 0; 88 | 89 | var loop = function loop(now) { 90 | var delta = now - then; 91 | renderId = requestAnimationFrame(loop); 92 | 93 | if (delta >= interval - tolerance) { 94 | _this.handler(delta); 95 | 96 | then = now - delta % interval; 97 | } 98 | }; 99 | 100 | renderId = requestAnimationFrame(loop); 101 | this.started = true; 102 | } 103 | }; 104 | /** 105 | * stop the currently running animation loop 106 | */ 107 | 108 | 109 | this.stop = function () { 110 | cancelAnimationFrame(renderId); 111 | this.started = false; 112 | }; 113 | }; 114 | 115 | /** 116 | * return the cartesian x/y delta value from a degree 117 | * eg: 90 (→) = [1,0] 118 | * @param {Number} angle angle in degrees 119 | * @returns {Number[]} cartesian delta values 120 | */ 121 | var cartesian = function cartesian(angle) { 122 | return [Math.cos(radian(angle - 90)), Math.sin(radian(angle - 90))]; 123 | }; 124 | /** 125 | * clamp the input number to the min/max values 126 | * @param {Number} value value to clamp between min and max 127 | * @param {Number} min minimum value possible 128 | * @param {Number} max maximum value possible 129 | * @returns {Number} the input num clamped between min/max 130 | */ 131 | 132 | var clamp = function clamp(value) { 133 | var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; 134 | var max = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; 135 | return Math.max(min, Math.min(max, value)); 136 | }; 137 | /** 138 | * return the radian equivalent to a degree value 139 | * @param {Number} angle angle in degrees 140 | * @returns {Number} radian equivalent 141 | */ 142 | 143 | var radian = function radian(angle) { 144 | return angle * Math.PI / 180; 145 | }; 146 | /** 147 | * return random number between a min and max value 148 | * @param {Number} min minimum value 149 | * @param {Number} max maximum value 150 | * @param {Boolean} rounded should the result be rounded 151 | * @returns {Number} a random number between min and max 152 | */ 153 | 154 | var random = function random() { 155 | var min = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; 156 | var max = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; 157 | var value = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Math.random(); 158 | 159 | if (max <= min) { 160 | value = min; 161 | } else if ((min !== 0 || max !== 1) && max > min) { 162 | value = value * (max - min) + min; 163 | } 164 | 165 | return value; 166 | }; 167 | /** 168 | * return a random value from an array 169 | * @param {Array} array an array to get random value from 170 | * @returns {*} random value from array 171 | */ 172 | 173 | var randomArray = function randomArray(array) { 174 | return array[Math.floor(random(0, array.length))]; 175 | }; 176 | /** 177 | * return a random HSL colour string for use in random color effect 178 | * @returns {String} "hsl(100,100,80)" 179 | */ 180 | 181 | var randomHsl = function randomHsl() { 182 | var h = round(random(0, 360)); 183 | var s = round(random(90, 100)); 184 | var l = round(random(45, 85)); 185 | return "hsl(".concat(h, ",").concat(s, "%,").concat(l, "%)"); 186 | }; 187 | /** 188 | * return a boolean to pass a dice roll 189 | * @param {Number} odds a fraction to use as the probability, can be supplied as "1/2" 190 | * @returns {Boolean} 191 | */ 192 | 193 | var roll = function roll(odds) { 194 | return odds > random(); 195 | }; 196 | /** 197 | * round a number to the nearest integer value 198 | * @param {Number} value value to round to the nearest integer 199 | * @returns {Number} nearest integer 200 | */ 201 | 202 | var round = function round(value) { 203 | return 0.5 + value | 0; 204 | }; 205 | 206 | /** 207 | * Sparticle Constructor; 208 | * creates an individual particle for use in the Sparticles() class 209 | * @param {Object} parent - the parent Sparticles() instance this belongs to 210 | * @returns {Object} - reference to a new Sparticle instance 211 | */ 212 | 213 | var Sparticle = function Sparticle(parent) { 214 | if (parent) { 215 | this.canvas = parent.canvas; 216 | this.settings = parent.settings; 217 | this.colors = parent.colors; 218 | this.shapes = parent.shapes; 219 | this.images = parent.images; 220 | this.styles = parent.styles; 221 | this.ctx = parent.canvas.getContext("2d"); 222 | this.setup(); 223 | this.init(); 224 | } else { 225 | console.warn("Invalid parameters given to Sparticle()", arguments); 226 | } 227 | 228 | return this; 229 | }; 230 | /** 231 | * set up the particle with some random values 232 | * before it is initialised on the canvas 233 | * these values will randomize when the particle goes offscreen 234 | */ 235 | 236 | Sparticle.prototype.setup = function () { 237 | var _ = this.settings; 238 | this.frame = 0; 239 | this.frameoffset = round(random(0, 360)); 240 | this.size = round(random(_.minSize, _.maxSize)); 241 | this.da = this.getAlphaDelta(); 242 | this.dx = this.getDeltaX(); 243 | this.dy = this.getDeltaY(); 244 | this.dd = this.getDriftDelta(); 245 | this.dr = this.getRotationDelta(); 246 | this.color = this.getColor(); 247 | this.shape = this.getShape(); 248 | this.image = this.getImage(); 249 | this.style = this.getStyle(); 250 | this.rotation = _.rotate ? radian(random(0, 360)) : 0; 251 | this.vertical = _.direction > 150 && _.direction < 210 || _.direction > 330 && _.direction < 390 || _.direction > -30 && _.direction < 30; 252 | this.horizontal = _.direction > 60 && _.direction < 120 || _.direction > 240 && _.direction < 300; 253 | }; 254 | /** 255 | * initialise a particle with the default values from 256 | * the Sparticles instance settings. 257 | * these values do not change when the particle goes offscreen 258 | */ 259 | 260 | 261 | Sparticle.prototype.init = function () { 262 | var _ = this.settings; 263 | var canvas = this.canvas; 264 | this.alpha = 0; 265 | 266 | if (_.speed > 0 || _.alphaSpeed === 0) { 267 | this.alpha = random(_.minAlpha, _.maxAlpha); 268 | } 269 | 270 | if (_.bounce) { 271 | this.px = round(random(2, canvas.width - this.size - 2)); 272 | this.py = round(random(2, canvas.height - this.size - 2)); 273 | } else { 274 | this.px = round(random(-this.size * 2, canvas.width + this.size)); 275 | this.py = round(random(-this.size * 2, canvas.height + this.size)); 276 | } 277 | }; 278 | /** 279 | * reset the particle after it has gone off canvas. 280 | * this should be better than popping it from the array 281 | * and creating a new particle instance. 282 | */ 283 | 284 | 285 | Sparticle.prototype.reset = function () { 286 | // give the particle a new set of initial values 287 | this.setup(); // set the particle's Y position 288 | 289 | if (this.py < 0) { 290 | this.py = this.canvas.height + this.size * 2; 291 | } else if (this.py > this.canvas.height) { 292 | this.py = 0 - this.size * 2; 293 | } // set the particle's X position 294 | 295 | 296 | if (this.px < 0) { 297 | this.px = this.canvas.width + this.size * 2; 298 | } else if (this.px > this.canvas.width) { 299 | this.px = 0 - this.size * 2; 300 | } 301 | }; 302 | /** 303 | * bounce the particle off the edge of canvas 304 | * when it has touched 305 | */ 306 | 307 | 308 | Sparticle.prototype.bounce = function () { 309 | var _ = this.settings; 310 | var dir = _.direction; // reverse the particle's Y position 311 | 312 | if (this.py <= 0 || this.py + this.size >= this.canvas.height) { 313 | this.dy = -this.dy; 314 | 315 | if (this.horizontal) { 316 | this.dd = -this.dd; 317 | } 318 | } // reverse the particle's X position 319 | 320 | 321 | if (this.px <= 0 || this.px + this.size >= this.canvas.width) { 322 | this.dx = -this.dx; 323 | 324 | if (this.vertical) { 325 | this.dd = -this.dd; 326 | } 327 | } 328 | }; 329 | /** 330 | * check if the particle is off the canvas based 331 | * on it's current position 332 | * @returns {Boolean} is the particle completely off canvas 333 | */ 334 | 335 | 336 | Sparticle.prototype.isOffCanvas = function () { 337 | var topleft = 0 - this.size * 2; 338 | var bottom = this.canvas.height + this.size * 2; 339 | var right = this.canvas.width + this.size * 2; 340 | return this.px < topleft || this.px > right || this.py < topleft || this.py > bottom; 341 | }; 342 | /** 343 | * check if the particle is touching the canvas edge 344 | * @returns {Boolean} is the particle touching edge 345 | */ 346 | 347 | 348 | Sparticle.prototype.isTouchingEdge = function () { 349 | var topleft = 0; 350 | var bottom = this.canvas.height - this.size; 351 | var right = this.canvas.width - this.size; 352 | return this.px < topleft || this.px > right || this.py < topleft || this.py > bottom; 353 | }; 354 | /** 355 | * get a random color for the particle from the 356 | * array of colors set in the options object 357 | * @returns {String} - random color from color array 358 | */ 359 | 360 | 361 | Sparticle.prototype.getColor = function () { 362 | if (this.settings.color === "random") { 363 | return randomArray(this.colors); 364 | } else if (Array.isArray(this.settings.color)) { 365 | return randomArray(this.settings.color); 366 | } else { 367 | return this.settings.color; 368 | } 369 | }; 370 | /** 371 | * get a random shape for the particle from the 372 | * array of shapes set in the options object 373 | * @returns {String} - random shape from shape array 374 | */ 375 | 376 | 377 | Sparticle.prototype.getShape = function () { 378 | if (this.settings.shape === "random") { 379 | return randomArray(this.shapes); 380 | } else if (Array.isArray(this.settings.shape)) { 381 | return randomArray(this.settings.shape); 382 | } else { 383 | return this.settings.shape; 384 | } 385 | }; 386 | /** 387 | * get the image for the particle from the array 388 | * of possible image urls 389 | * @returns {String} - random imageUrl from imageUrl array 390 | */ 391 | 392 | 393 | Sparticle.prototype.getImage = function () { 394 | if (Array.isArray(this.settings.imageUrl)) { 395 | return randomArray(this.settings.imageUrl); 396 | } else { 397 | return this.settings.imageUrl; 398 | } 399 | }; 400 | /** 401 | * get the style of the particle, either "fill" or "stroke" 402 | * depending on the settings as fill/stroke/both 403 | * @returns {String} - either "fill" or "stroke" 404 | */ 405 | 406 | 407 | Sparticle.prototype.getStyle = function () { 408 | return randomArray(this.styles); 409 | }; 410 | /** 411 | * get a random delta (velocity) for the particle 412 | * based on the speed, and the parallax value (if applicable) 413 | * @returns {Number} - the velocity to be applied to the particle 414 | */ 415 | 416 | 417 | Sparticle.prototype.getDelta = function () { 418 | var baseDelta = this.settings.speed * 0.1; 419 | 420 | if (this.settings.speed && this.settings.parallax) { 421 | return baseDelta + this.size * this.settings.parallax / 50; 422 | } else { 423 | return baseDelta; 424 | } 425 | }; 426 | /** 427 | * get a random variable speed for use as a multiplier, 428 | * based on the values given in the settings object, this 429 | * can be positive or negative 430 | * @returns {Number} - a variable delta speed 431 | */ 432 | 433 | 434 | Sparticle.prototype.getDeltaVariance = function () { 435 | var v = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; 436 | var s = this.settings.speed || 10; 437 | 438 | if (v > 0) { 439 | return random(-v, v) * s / 100; 440 | } else { 441 | return 0; 442 | } 443 | }; 444 | /** 445 | * get a random delta on the X axis, taking in to account 446 | * the variance range in the settings object and the particle's 447 | * direction as a multiplier 448 | * @returns {Number} - the X delta to be applied to particle 449 | */ 450 | 451 | 452 | Sparticle.prototype.getDeltaX = function () { 453 | var d = this.getDelta(); 454 | var dv = this.getDeltaVariance(this.settings.xVariance); 455 | return cartesian(this.settings.direction)[0] * d + dv; 456 | }; 457 | /** 458 | * get a random delta on the Y axis, taking in to account 459 | * the variance range in the settings object and the particle's 460 | * direction as a multiplier 461 | * @returns {Number} - the Y delta to be applied to particle 462 | */ 463 | 464 | 465 | Sparticle.prototype.getDeltaY = function () { 466 | var d = this.getDelta(); 467 | var dv = this.getDeltaVariance(this.settings.yVariance); 468 | return cartesian(this.settings.direction)[1] * d + dv; 469 | }; 470 | /** 471 | * get a random delta for the alpha change over time from 472 | * between a positive and negative alpha variance value 473 | * @returns {Number} - the alpha delta to be applied to particle 474 | */ 475 | 476 | 477 | Sparticle.prototype.getAlphaDelta = function () { 478 | var variance = this.settings.alphaVariance; 479 | var a = random(1, variance + 1); 480 | 481 | if (roll(1 / 2)) { 482 | a = -a; 483 | } 484 | 485 | return a; 486 | }; 487 | /** 488 | * return a random drift value either positive or negative 489 | * @returns {Number} - the drift value 490 | */ 491 | 492 | 493 | Sparticle.prototype.getDriftDelta = function () { 494 | if (!this.settings.drift) { 495 | return 0; 496 | } else { 497 | return random(this.settings.drift - this.settings.drift / 2, this.settings.drift + this.settings.drift / 2); 498 | } 499 | }; 500 | /** 501 | * return a random rotation value either positive or negative 502 | * @returns {Number} - the rotation value 503 | */ 504 | 505 | 506 | Sparticle.prototype.getRotationDelta = function () { 507 | var r = 0; 508 | 509 | if (this.settings.rotate && this.settings.rotation) { 510 | r = radian(random(0.5, 1.5) * this.settings.rotation); 511 | 512 | if (roll(1 / 2)) { 513 | r = -r; 514 | } 515 | } 516 | 517 | return r; 518 | }; 519 | /** 520 | * progress the particle's frame number, as well 521 | * as the internal values for both the particle's 522 | * position and the particle's alpha. 523 | * @returns {Object} - reference to the current Sparticle instance 524 | */ 525 | 526 | 527 | Sparticle.prototype.update = function () { 528 | this.frame += 1; 529 | this.updatePosition(); 530 | this.updateAlpha(); 531 | return this; 532 | }; 533 | /** 534 | * progress the particle's alpha value depending on the 535 | * alphaSpeed and the twinkle setting 536 | * @returns {Number} - new alpha value of the particle 537 | */ 538 | 539 | 540 | Sparticle.prototype.updateAlpha = function () { 541 | if (this.settings.alphaSpeed > 0) { 542 | if (this.settings.twinkle) { 543 | this.alpha = this.updateTwinkle(); 544 | } else { 545 | this.alpha = this.updateFade(); 546 | } 547 | } 548 | 549 | return this.alpha; 550 | }; 551 | /** 552 | * progress the particle's alpha value according to 553 | * the fading effect 554 | * @returns {Number} - new alpha value of the particle 555 | */ 556 | 557 | 558 | Sparticle.prototype.updateFade = function () { 559 | var tick = this.da / 1000 * this.settings.alphaSpeed * 0.5; 560 | var alpha = this.alpha + tick; 561 | var over = this.da > 0 && alpha > this.settings.maxAlpha; 562 | var under = this.da < 0 && alpha < this.settings.minAlpha; // if the alpha is over or under the min or max values, 563 | // then we reverse the delta so that it can increase or 564 | // decrease in opacity in the opposite direction 565 | 566 | if (over || under) { 567 | this.da = -this.da; 568 | alpha = this.settings.maxAlpha; 569 | 570 | if (under) { 571 | alpha = this.settings.minAlpha; 572 | } 573 | } 574 | 575 | return alpha; 576 | }; 577 | /** 578 | * progress the particle's alpha value according to 579 | * the twinkle effect 580 | * @returns {Number} - new alpha value of the particle 581 | */ 582 | 583 | 584 | Sparticle.prototype.updateTwinkle = function () { 585 | var alpha = this.alpha; 586 | var delta = Math.abs(this.da); 587 | var over = alpha > this.settings.maxAlpha; 588 | var under = alpha < this.settings.minAlpha; 589 | var tick = delta / 1000 * this.settings.alphaSpeed * 0.5; 590 | var flickerOn = roll(1 / 30); 591 | var flickerOff = roll(1 / 30); // if the particle is resetting the twinkle effect, then 592 | // we simply want to quickly get back to max alpha 593 | // over a short period of time, otherwise just advance the tick 594 | 595 | if (this.resettingTwinkle) { 596 | alpha += tick * 5; 597 | } else if (flickerOn) { 598 | alpha += tick * 50; 599 | } else if (flickerOff) { 600 | alpha -= tick * 25; 601 | } else { 602 | alpha -= tick; 603 | } // once the alpha is under the min alpha value, then we need 604 | // to set the twinkle effect to reset, and once it is over 605 | // the max alpha, we stop resetting. 606 | 607 | 608 | if (under) { 609 | this.resettingTwinkle = true; 610 | alpha = this.settings.minAlpha; 611 | } else if (over) { 612 | this.resettingTwinkle = false; 613 | alpha = this.settings.maxAlpha; 614 | } 615 | 616 | return alpha; 617 | }; 618 | /** 619 | * progress the particle's position values, rotation and drift 620 | * according to the settings given 621 | */ 622 | 623 | 624 | Sparticle.prototype.updatePosition = function () { 625 | if (this.settings.bounce && this.isTouchingEdge()) { 626 | this.bounce(); 627 | } else if (this.isOffCanvas()) { 628 | this.reset(); 629 | return; 630 | } 631 | 632 | this.px += this.dx; 633 | this.py += this.dy; // drift must be applied after position x/y 634 | // as it modifies the values by wave function 635 | 636 | this.updateDrift(); 637 | this.updateRotation(); 638 | }; 639 | /** 640 | * progress the particle's rotation value according 641 | * to the settings given 642 | */ 643 | 644 | 645 | Sparticle.prototype.updateRotation = function () { 646 | if (this.settings.rotate && this.settings.rotation) { 647 | this.rotation += this.dr; 648 | } 649 | }; 650 | /** 651 | * progress the particle's drift value according 652 | * to the settings given 653 | */ 654 | 655 | 656 | Sparticle.prototype.updateDrift = function () { 657 | var _ = this.settings; 658 | var dir = _.direction; 659 | 660 | if (_.drift && _.speed) { 661 | if (this.vertical) { 662 | // apply HORIZONTAL drift ~ when "direction" is mostly vertical. 663 | this.px += cartesian(this.frame + this.frameoffset)[0] * this.dd / (this.getDelta() * 15); 664 | } else if (this.horizontal) { 665 | // apply VERTICAL drift ~ when "direction" is mostly horizontal. 666 | this.py += cartesian(this.frame + this.frameoffset)[1] * this.dd / (this.getDelta() * 15); 667 | } 668 | } 669 | }; 670 | 671 | Sparticle.prototype.render = function (canvasses) { 672 | var particleCanvas; 673 | 674 | if (this.shape !== "image") { 675 | particleCanvas = canvasses[this.color][this.shape][this.style]; 676 | } else { 677 | particleCanvas = canvasses[this.color][this.shape][this.image]; 678 | } 679 | 680 | var canvasSize = particleCanvas.width; 681 | var scale = this.size / canvasSize; 682 | var px = this.px / scale; 683 | var py = this.py / scale; 684 | this.ctx.globalAlpha = clamp(this.alpha, 0, 1); 685 | this.renderRotate(); 686 | this.ctx.transform(scale, 0, 0, scale, 0, 0); 687 | this.ctx.drawImage(particleCanvas, 0, 0, canvasSize, canvasSize, px, py, canvasSize, canvasSize); 688 | this.ctx.setTransform(1, 0, 0, 1, 0, 0); 689 | return this; 690 | }; 691 | 692 | Sparticle.prototype.renderRotate = function () { 693 | if (this.shape !== "circle" && this.settings.rotate) { 694 | var centerX = this.px + this.size / 2; 695 | var centerY = this.py + this.size / 2; 696 | this.ctx.translate(centerX, centerY); 697 | this.ctx.rotate(this.rotation); 698 | this.ctx.translate(-centerX, -centerY); 699 | } 700 | }; 701 | 702 | /** 703 | * Sparticles Constructor; 704 | * Create a , append to the given node, and start the particle effect 705 | * @param {HTMLElement} [node=document.body] - element to which canvas is appended to 706 | * @param {Object} [options={}] - settings to use for the particle effect 707 | * @param {String} [options.composition=source-over] - canvas globalCompositeOperation value for particles 708 | * @param {Number} [options.count=50] - number of particles on the canvas simultaneously 709 | * @param {Number} [options.speed=10] - default velocity of every particle 710 | * @param {Number} [options.parallax=1] - speed multiplier effect for larger particles (0 = none) 711 | * @param {Number} [options.direction=180] - default direction of particles in degrees (0 = ↑, 180 = ↓) 712 | * @param {Number} [options.xVariance=2] - random deviation of particles on x-axis from default direction 713 | * @param {Number} [options.yVariance=2] - random deviation of particles on y-axis from default direction 714 | * @param {Number} [options.rotate=true] - can particles rotate 715 | * @param {Number} [options.rotation=1] - default rotational speed for every particle 716 | * @param {Number} [options.alphaSpeed=10] - rate of change in alpha over time 717 | * @param {Number} [options.alphaVariance=1] - random deviation of alpha change 718 | * @param {Number} [options.minAlpha=0] - minumum alpha value of every particle 719 | * @param {Number} [options.maxAlpha=1] - maximum alpha value of every particle 720 | * @param {Number} [options.minSize=1] - minimum size of every particle 721 | * @param {Number} [options.maxSize=10] - maximum size of every particle 722 | * @param {Boolean} [options.bounce=false] - should the particles bounce off edge of canvas 723 | * @param {Number} [options.drift=1] - the "driftiness" of particles which have a horizontal/vertical direction 724 | * @param {Number} [options.glow=0] - the glow effect size of each particle 725 | * @param {Boolean} [options.twinkle=false] - particles to exhibit an alternative alpha transition as "twinkling" 726 | * @param {String} [options.style=fill] - fill style of particles (one of; "fill", "stroke" or "both") 727 | * @param {(String|String[])} [options.shape=circle] - shape of particles (any of; circle, square, triangle, diamond, line, image) or "random" 728 | * @param {(String|String[])} [options.imageUrl=] - if shape is "image", define an image url (can be data-uri, must be square (1:1 ratio)) 729 | * @param {(String|String[])} [options.color=random] - css color as string, or array of color strings (can also be "random") 730 | * @param {Function} [options.randomColor=randomHsl(index,total)] - a custom function for setting the random colors when color="random" 731 | * @param {Number} [options.randomColorCount=3] - the number of random colors to generate when color is "random" 732 | * @param {Number} [width] - the width of the canvas element 733 | * @param {Number} [height=width] - the height of the canvas element 734 | * @returns {Object} - reference to a new Sparticles instance 735 | */ 736 | 737 | var Sparticles = function Sparticles(node, options, width, height) { 738 | if (arguments.length >= 1 && !(arguments[0] instanceof HTMLElement)) { 739 | options = arguments[0]; 740 | width = arguments[1]; 741 | height = arguments[2]; 742 | node = undefined; 743 | } 744 | 745 | if (width && !height) { 746 | height = width; 747 | } 748 | 749 | var defaults = { 750 | alphaSpeed: 10, 751 | alphaVariance: 1, 752 | bounce: false, 753 | color: "random", 754 | randomColor: randomHsl, 755 | randomColorCount: 3, 756 | composition: "source-over", 757 | count: 50, 758 | direction: 180, 759 | drift: 1, 760 | glow: 0, 761 | imageUrl: "", 762 | maxAlpha: 1, 763 | maxSize: 10, 764 | minAlpha: 0, 765 | minSize: 1, 766 | parallax: 1, 767 | rotate: true, 768 | rotation: 1, 769 | shape: "circle", 770 | speed: 10, 771 | style: "fill", 772 | twinkle: false, 773 | xVariance: 2, 774 | yVariance: 2 775 | }; 776 | this.el = node || document.body; 777 | this.settings = _objectSpread2(_objectSpread2({}, defaults), options); 778 | this.resizable = !width && !height; 779 | this.width = this.resizable ? this.el.clientWidth : width; 780 | this.height = this.resizable ? this.el.clientHeight : height; 781 | /** 782 | * initialise the sparticles instance 783 | * @returns {Object} - reference to the Sparticles instance 784 | */ 785 | 786 | this.init = function () { 787 | var _this = this; 788 | 789 | this.sparticles = []; 790 | this.colors = this.getColorArray(); 791 | this.shapes = this.getShapeArray(); 792 | this.styles = this.getStyleArray(); 793 | this.imageUrls = this.getImageArray(); 794 | this.setupMainCanvas(); 795 | this.setupOffscreenCanvasses(function () { 796 | _this.createSparticles(); 797 | 798 | _this.start(); 799 | }); // defer to the default "handleEvent" handler 800 | // https://developer.mozilla.org/en-US/docs/Web/API/EventListener/handleEvent 801 | 802 | window.addEventListener("resize", this); 803 | return this; 804 | }; 805 | /** 806 | * handle event for screen resize; 807 | * debounce a canvas resize, 808 | * reset the particles 809 | */ 810 | 811 | 812 | this.handleEvent = function (event) { 813 | var _this2 = this; 814 | 815 | if (event.type === "resize") { 816 | clearTimeout(this.resizeTimer); 817 | this.resizeTimer = setTimeout(function () { 818 | if (_this2.resizable) { 819 | _this2.width = _this2.el.clientWidth; 820 | _this2.height = _this2.el.clientHeight; 821 | 822 | _this2.setCanvasSize().resetSparticles(); 823 | } 824 | }, 200); 825 | } 826 | }; 827 | /** 828 | * start/resume the sparticles animation 829 | * @returns {Object} - the Sparticle instance (for chaining) 830 | */ 831 | 832 | 833 | this.start = function () { 834 | var me = this; 835 | 836 | if (!this.loop) { 837 | this.loop = new AnimationFrame(function (t) { 838 | me.drawFrame(t); 839 | }); 840 | } 841 | 842 | this.loop.start(); 843 | return this; 844 | }; 845 | /** 846 | * stop/pause the sparticles animation 847 | * @returns {Object} - the Sparticle instance (for chaining) 848 | */ 849 | 850 | 851 | this.stop = function () { 852 | this.loop.stop(); 853 | return this; 854 | }; 855 | /** 856 | * destroy the current instance and free up some memory 857 | * @returns {Object} - the Sparticle instance (for chaining) 858 | */ 859 | 860 | 861 | this.destroy = function () { 862 | // stop the rendering and updating 863 | this.stop(); // remove the canvas element from the DOM 864 | 865 | this.el.removeChild(this.canvas); // remove the resize event for this instance 866 | 867 | window.removeEventListener("resize", this); // delete all the properties from the instance 868 | // to free up memory 869 | 870 | for (var prop in this) { 871 | if (this.hasOwnProperty(prop)) { 872 | delete this[prop]; 873 | } 874 | } 875 | 876 | return this; 877 | }; 878 | /** 879 | * set the canvas width and height 880 | * @param {Number} width - the width of the canvas 881 | * @param {Number} height - the height of the canvas 882 | * @returns {Object} - the Sparticle instance (for chaining) 883 | */ 884 | 885 | 886 | this.setCanvasSize = function (width, height) { 887 | if (width) { 888 | this.resizable = false; 889 | } 890 | 891 | this.width = width || this.width; 892 | this.height = height || this.height; 893 | this.canvas.width = this.width; 894 | this.canvas.height = this.height; 895 | return this; 896 | }; 897 | /** 898 | * create an array and populate it with new Sparticle instances. 899 | * @returns {Array} the array of Sparticle instances 900 | */ 901 | 902 | 903 | this.resetSparticles = this.createSparticles = function () { 904 | this.sparticles = []; 905 | this.ctx.globalCompositeOperation = this.settings.composition; 906 | 907 | for (var i = 0; i < this.settings.count; i++) { 908 | this.sparticles.push(new Sparticle(this, i)); 909 | } 910 | 911 | this.sort(); 912 | return this.sparticles; 913 | }; 914 | /** 915 | * sort the particle array by size so that parallax effect 916 | * doesn't appear to have slower/smaller particles in foreground 917 | */ 918 | 919 | 920 | this.sort = function () { 921 | if (this.settings.parallax) { 922 | this.sparticles.sort(function (a, b) { 923 | return a.size - b.size; 924 | }); 925 | } 926 | }; // initialise the sparticles, and return the instance. 927 | 928 | 929 | return this.init(); 930 | }; 931 | /** 932 | * convert the input color to an array if it isn't already 933 | * @returns {Array} - array of colors for use in rendering 934 | */ 935 | 936 | 937 | Sparticles.prototype.getColorArray = function () { 938 | var colors = Array.isArray(this.settings.color) ? this.settings.color : [this.settings.color]; 939 | var isRandom = colors.some(function (c) { 940 | return c === "random"; 941 | }); 942 | 943 | if (isRandom) { 944 | for (var i = 0; i < this.settings.randomColorCount; i++) { 945 | colors[i] = this.settings.randomColor(i, this.settings.randomColorCount); 946 | } 947 | } 948 | 949 | return colors; 950 | }; 951 | /** 952 | * convert the input shape to an array if it isn't already 953 | * @returns {Array} - array of shapes for use in rendering 954 | */ 955 | 956 | 957 | Sparticles.prototype.getShapeArray = function () { 958 | var shapes = Array.isArray(this.settings.shape) ? this.settings.shape : [this.settings.shape]; 959 | var isRandom = shapes.some(function (c) { 960 | return c === "random"; 961 | }); 962 | 963 | if (isRandom) { 964 | shapes = ["square", "circle", "triangle"]; 965 | } 966 | 967 | return shapes; 968 | }; 969 | /** 970 | * convert the imageUrl option to an array if it isn't already 971 | * @returns {Array} - array of image urls for use in rendering 972 | */ 973 | 974 | 975 | Sparticles.prototype.getImageArray = function () { 976 | return Array.isArray(this.settings.imageUrl) ? this.settings.imageUrl : [this.settings.imageUrl]; 977 | }; 978 | /** 979 | * convert the input style to an array 980 | * @returns {Array} - array of styles for use in rendering 981 | */ 982 | 983 | 984 | Sparticles.prototype.getStyleArray = function () { 985 | var styles = this.settings.style; 986 | 987 | if (styles !== "fill" && styles !== "stroke") { 988 | styles = ["fill", "stroke"]; 989 | } else { 990 | styles = [styles]; 991 | } 992 | 993 | return styles; 994 | }; 995 | /** 996 | * set up the canvas and bind to a property for 997 | * access later on, append it to the DOM 998 | * @returns {HTMLCanvasElement} - the canvas element which was appended to DOM 999 | */ 1000 | 1001 | 1002 | Sparticles.prototype.setupMainCanvas = function () { 1003 | this.canvas = document.createElement("canvas"); 1004 | this.canvas.setAttribute("class", "sparticles"); 1005 | this.ctx = this.canvas.getContext("2d"); 1006 | this.setCanvasSize(); 1007 | this.el.appendChild(this.canvas); 1008 | return this.canvas; 1009 | }; 1010 | /** 1011 | * create a new offscreen canvas element for each color & shape 1012 | * combination, so that we can reference it later during render 1013 | * (huge performance gains here) 1014 | * @param {Function} [callback] - function to execute after image loads 1015 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1016 | */ 1017 | 1018 | 1019 | Sparticles.prototype.setupOffscreenCanvasses = function (callback) { 1020 | var _this3 = this; 1021 | 1022 | var colors = this.colors.filter(function (item, index) { 1023 | return _this3.colors.indexOf(item) === index; 1024 | }); 1025 | var shapes = this.shapes.filter(function (item, index) { 1026 | return _this3.shapes.indexOf(item) === index; 1027 | }); 1028 | var styles = this.styles.filter(function (item, index) { 1029 | return _this3.styles.indexOf(item) === index; 1030 | }); 1031 | var imageUrls = this.imageUrls.filter(function (item, index) { 1032 | return _this3.imageUrls.indexOf(item) === index; 1033 | }); 1034 | var imageCount = colors.length * imageUrls.length; 1035 | var canvasCount = colors.length * shapes.length * styles.length; 1036 | var imagesLoaded = 0; 1037 | var canvassesCreated = 0; 1038 | this.canvasses = this.canvasses || {}; 1039 | colors.forEach(function (color) { 1040 | _this3.canvasses[color] = _this3.canvasses[color] || {}; 1041 | shapes.forEach(function (shape) { 1042 | _this3.canvasses[color][shape] = _this3.canvasses[color][shape] || {}; 1043 | 1044 | if (shape === "image") { 1045 | imageUrls.forEach(function (imageUrl, i) { 1046 | var image = new Image(); 1047 | var imageCanvas = document.createElement("canvas"); 1048 | _this3.canvasses[color][shape][imageUrl] = imageCanvas; 1049 | 1050 | image.onload = function () { 1051 | imagesLoaded++; 1052 | 1053 | _this3.drawOffscreenCanvasForImage(image, color, imageCanvas); 1054 | 1055 | if (callback && imagesLoaded === imageCount) { 1056 | callback(); 1057 | } 1058 | }; 1059 | 1060 | image.onerror = function () { 1061 | console.error("failed to load source image: ", imageUrl); 1062 | }; 1063 | 1064 | image.src = imageUrl; 1065 | }); 1066 | } else { 1067 | styles.forEach(function (style) { 1068 | var canvas = document.createElement("canvas"); 1069 | _this3.canvasses[color][shape][style] = canvas; 1070 | canvassesCreated++; 1071 | 1072 | _this3.drawOffscreenCanvas(shape, style, color, canvas); 1073 | 1074 | if (callback && canvassesCreated === canvasCount) { 1075 | callback(); 1076 | } 1077 | }); 1078 | } 1079 | }); 1080 | }); 1081 | }; 1082 | /** 1083 | * return the size of the glow effect (shadowBlur) for each particle 1084 | * @param {Number} size - the size of the particle 1085 | * @returns {Number} - the size of the glow/shadow 1086 | */ 1087 | 1088 | 1089 | Sparticles.prototype.getGlowSize = function (size) { 1090 | return this.settings.glow; 1091 | }; 1092 | /** 1093 | * return the outline or stroke size of each particle 1094 | * @param {Number} size - the size of the particle 1095 | * @returns {Number} - the size of the outline/stroke 1096 | */ 1097 | 1098 | 1099 | Sparticles.prototype.getLineSize = function (size) { 1100 | return clamp(size / 20, 1, 5); 1101 | }; 1102 | /** 1103 | * return the offscreenCanvas size to generate for 1104 | * @returns {Number} - the maxSize of the offscreen canvas 1105 | */ 1106 | 1107 | 1108 | Sparticles.prototype.getOffscreenCanvasSize = function () { 1109 | return clamp(this.settings.maxSize, this.settings.minSize, this.settings.maxSize); 1110 | }; 1111 | /** 1112 | * set the fill/stroke style (color & width) for each particle's offscreen canvas 1113 | * @param {CanvasRenderingContext2D} ctx - the canvas context 1114 | * @param {String} color - the color to fill/stroke with 1115 | * @param {Number} lineSize - size/thickness of the stroke 1116 | * @param {String} style - style (either "fill" or "stroke") 1117 | */ 1118 | 1119 | 1120 | Sparticles.prototype.renderStyle = function (ctx, color, lineSize, style) { 1121 | if (style === "fill") { 1122 | ctx.fillStyle = color; 1123 | } else { 1124 | ctx.lineWidth = lineSize; 1125 | ctx.strokeStyle = color; 1126 | } 1127 | }; 1128 | /** 1129 | * set the shadowBlur (glow effect) for each particle's offscreen canvas 1130 | * @param {CanvasRenderingContext2D} ctx - the canvas context 1131 | * @param {String} color - the color to fill/stroke with 1132 | * @param {Number} size - size of the shadow/glow 1133 | */ 1134 | 1135 | 1136 | Sparticles.prototype.renderGlow = function (ctx, color, size) { 1137 | var glowSize = this.getGlowSize(size) / 2; 1138 | ctx.shadowColor = color; 1139 | ctx.shadowBlur = glowSize; 1140 | }; 1141 | /** 1142 | * fill or stroke each particle's offscreen canvas depending on the given setting 1143 | * @param {CanvasRenderingContext2D} ctx - the canvas context 1144 | * @param {String} style - style (either "fill" or "stroke") 1145 | */ 1146 | 1147 | 1148 | Sparticles.prototype.renderColor = function (ctx, style, path) { 1149 | if (style === "fill") { 1150 | if (path) { 1151 | ctx.fill(path); 1152 | } else { 1153 | ctx.fill(); 1154 | } 1155 | } else { 1156 | if (path) { 1157 | ctx.stroke(path); 1158 | } else { 1159 | ctx.stroke(); 1160 | } 1161 | } 1162 | }; 1163 | /** 1164 | * pass-through the needed parameters to the offscreen canvas 1165 | * draw function associated with the given shape 1166 | * @param {String} shape - shape of the canvas to draw (eg: "circle") 1167 | * @param {String} style - style (either "fill" or "stroke") 1168 | * @param {String} color - the color to fill/stroke with 1169 | * @param {HTMLCanvasElement} canvas - the canvas element 1170 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1171 | */ 1172 | 1173 | 1174 | Sparticles.prototype.drawOffscreenCanvas = function (shape, style, color, canvas) { 1175 | return this.offScreenCanvas[shape].call(this, style, color, canvas); 1176 | }; 1177 | /** 1178 | * object of shapes to draw 1179 | */ 1180 | 1181 | 1182 | Sparticles.prototype.offScreenCanvas = {}; 1183 | /** 1184 | * create, setup and render an offscreen canvas for a 1185 | * Circle Particle of the given color 1186 | * @param {String} style - style (either "fill" or "stroke") 1187 | * @param {String} color - the color to fill/stroke with 1188 | * @param {HTMLCanvasElement} canvas - the canvas element 1189 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1190 | */ 1191 | 1192 | Sparticles.prototype.offScreenCanvas.circle = function (style, color, canvas) { 1193 | var ctx = canvas.getContext("2d"); 1194 | var size = this.getOffscreenCanvasSize(); 1195 | var lineSize = this.getLineSize(size); 1196 | var glowSize = this.getGlowSize(size); 1197 | var canvasSize = size + lineSize * 2 + glowSize; 1198 | var shapeSize = style === "stroke" ? size - lineSize : size; 1199 | canvas.width = canvasSize; 1200 | canvas.height = canvasSize; 1201 | this.renderGlow(ctx, color, size); 1202 | this.renderStyle(ctx, color, lineSize, style); 1203 | ctx.beginPath(); 1204 | ctx.ellipse(canvasSize / 2, canvasSize / 2, shapeSize / 2, shapeSize / 2, 0, 0, 360); 1205 | this.renderColor(ctx, style); 1206 | return canvas; 1207 | }; 1208 | /** 1209 | * create, setup and render an offscreen canvas for a 1210 | * Square Particle of the given color 1211 | * @param {String} style - style (either "fill" or "stroke") 1212 | * @param {String} color - the color to fill/stroke with 1213 | * @param {HTMLCanvasElement} canvas - the canvas element 1214 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1215 | */ 1216 | 1217 | 1218 | Sparticles.prototype.offScreenCanvas.square = function (style, color, canvas) { 1219 | var ctx = canvas.getContext("2d"); 1220 | var size = this.getOffscreenCanvasSize(); 1221 | var lineSize = this.getLineSize(size); 1222 | var glowSize = this.getGlowSize(size); 1223 | var canvasSize = size + lineSize * 2 + glowSize; 1224 | var shapeSize = style === "stroke" ? size - lineSize : size; 1225 | canvas.width = canvasSize; 1226 | canvas.height = canvasSize; 1227 | this.renderGlow(ctx, color, size); 1228 | this.renderStyle(ctx, color, lineSize, style); 1229 | ctx.beginPath(); 1230 | ctx.rect(canvasSize / 2 - shapeSize / 2, canvasSize / 2 - shapeSize / 2, shapeSize, shapeSize); 1231 | this.renderColor(ctx, style); 1232 | return canvas; 1233 | }; 1234 | /** 1235 | * create, setup and render an offscreen canvas for a 1236 | * Line/Curve Particle of the given color 1237 | * @param {String} style - style (either "fill" or "stroke") 1238 | * @param {String} color - the color to fill/stroke with 1239 | * @param {HTMLCanvasElement} canvas - the canvas element 1240 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1241 | */ 1242 | 1243 | 1244 | Sparticles.prototype.offScreenCanvas.line = function (style, color, canvas) { 1245 | var ctx = canvas.getContext("2d"); 1246 | var size = this.getOffscreenCanvasSize() * 1.5; 1247 | var lineSize = this.getLineSize(size); 1248 | var glowSize = this.getGlowSize(size); 1249 | var canvasSize = size + lineSize * 2 + glowSize; 1250 | var startx = canvasSize / 2 - size / 2; 1251 | var starty = canvasSize / 2 - size / 2; 1252 | canvas.width = canvasSize; 1253 | canvas.height = canvasSize; 1254 | this.renderGlow(ctx, color, size); 1255 | ctx.lineWidth = lineSize; 1256 | ctx.strokeStyle = color; 1257 | ctx.beginPath(); 1258 | ctx.moveTo(startx, starty); 1259 | ctx.lineTo(startx + size, starty + size); 1260 | ctx.stroke(); 1261 | ctx.closePath(); 1262 | return canvas; 1263 | }; 1264 | /** 1265 | * create, setup and render an offscreen canvas for a 1266 | * Triangle Particle of the given color 1267 | * @param {String} style - style (either "fill" or "stroke") 1268 | * @param {String} color - the color to fill/stroke with 1269 | * @param {HTMLCanvasElement} canvas - the canvas element 1270 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1271 | */ 1272 | 1273 | 1274 | Sparticles.prototype.offScreenCanvas.triangle = function (style, color, canvas) { 1275 | var ctx = canvas.getContext("2d"); 1276 | var size = this.getOffscreenCanvasSize(); 1277 | var lineSize = this.getLineSize(size); 1278 | var glowSize = this.getGlowSize(size); 1279 | var canvasSize = size + lineSize * 2 + glowSize; 1280 | var shapeSize = style === "stroke" ? size - lineSize : size; 1281 | var height = shapeSize * (Math.sqrt(3) / 2); 1282 | var startx = canvasSize / 2; 1283 | var starty = canvasSize / 2 - shapeSize / 2; 1284 | canvas.width = canvasSize; 1285 | canvas.height = canvasSize; 1286 | this.renderGlow(ctx, color, size); 1287 | this.renderStyle(ctx, color, lineSize, style); 1288 | ctx.beginPath(); 1289 | ctx.moveTo(startx, starty); 1290 | ctx.lineTo(startx - shapeSize / 2, starty + height); 1291 | ctx.lineTo(startx + shapeSize / 2, starty + height); 1292 | ctx.closePath(); 1293 | this.renderColor(ctx, style); 1294 | return canvas; 1295 | }; 1296 | /** 1297 | * create, setup and render an offscreen canvas for a 1298 | * Diamond Sparkle Particle of the given color 1299 | * @param {String} style - style (either "fill" or "stroke") 1300 | * @param {String} color - the color to fill/stroke with 1301 | * @param {HTMLCanvasElement} canvas - the canvas element 1302 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1303 | */ 1304 | 1305 | 1306 | Sparticles.prototype.offScreenCanvas.diamond = function (style, color, canvas) { 1307 | var pathSize = 100; 1308 | var path = new Path2D("M43,83.74,48.63,99a1.46,1.46,0,0,0,2.74,0L57,83.74A45.09,45.09,0,0,1,83.74,57L99,51.37a1.46,1.46,0,0,0,0-2.74L83.74,43A45.11,45.11,0,0,1,57,16.26L51.37,1a1.46,1.46,0,0,0-2.74,0L43,16.26A45.11,45.11,0,0,1,16.26,43L1,48.63a1.46,1.46,0,0,0,0,2.74L16.26,57A45.09,45.09,0,0,1,43,83.74Z"); 1309 | var ctx = canvas.getContext("2d"); 1310 | var size = this.getOffscreenCanvasSize(); 1311 | var lineSize = this.getLineSize(size); 1312 | var glowSize = this.getGlowSize(size); 1313 | var canvasSize = size + lineSize * 2 + glowSize; 1314 | var scale = canvasSize / ((pathSize + glowSize) * 1.1); 1315 | canvas.width = canvasSize; 1316 | canvas.height = canvasSize; 1317 | this.renderGlow(ctx, color, size); 1318 | this.renderStyle(ctx, color, lineSize / scale, style); 1319 | ctx.scale(scale, scale); 1320 | ctx.translate(pathSize * 0.05 + glowSize * 0.5, pathSize * 0.05 + glowSize * 0.5); 1321 | this.renderColor(ctx, style, path); 1322 | return canvas; 1323 | }; 1324 | /** 1325 | * create, setup and render an offscreen canvas for a 1326 | * Star Particle of the given color 1327 | * @param {String} style - style (either "fill" or "stroke") 1328 | * @param {String} color - the color to fill/stroke with 1329 | * @param {HTMLCanvasElement} canvas - the canvas element 1330 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1331 | */ 1332 | 1333 | 1334 | Sparticles.prototype.offScreenCanvas.star = function (style, color, canvas) { 1335 | var pathSize = 100; 1336 | var path = new Path2D("M99.86,36.45a2.94,2.94,0,0,0-2.37-2l-31-4.54L52.63,1.64a2.93,2.93,0,0,0-5.26,0L33.51,29.91l-31,4.54a3,3,0,0,0-2.37,2,3,3,0,0,0,.74,3l22.44,22L18,92.55A2.94,2.94,0,0,0,20.91,96a2.86,2.86,0,0,0,1.36-.34L50,81,77.73,95.66a2.91,2.91,0,0,0,3.08-.22A3,3,0,0,0,82,92.55l-5.3-31.07,22.44-22A3,3,0,0,0,99.86,36.45Z"); 1337 | var ctx = canvas.getContext("2d"); 1338 | var size = this.getOffscreenCanvasSize(); 1339 | var lineSize = this.getLineSize(size); 1340 | var glowSize = this.getGlowSize(size); 1341 | var canvasSize = size + lineSize * 2 + glowSize; 1342 | var scale = canvasSize / ((pathSize + glowSize) * 1.1); 1343 | canvas.width = canvasSize; 1344 | canvas.height = canvasSize; 1345 | ctx.scale(scale, scale); 1346 | this.renderGlow(ctx, color, size); 1347 | this.renderStyle(ctx, color, lineSize / scale, style); 1348 | ctx.translate(pathSize * 0.05 + glowSize * 0.5, pathSize * 0.05 + glowSize * 0.5); 1349 | this.renderColor(ctx, style, path); 1350 | return canvas; 1351 | }; 1352 | /** 1353 | * create, setup and render an offscreen canvas for a 1354 | * Custom Image Particle of the given color 1355 | * @param {HTMLImageElement} image - the image element that has loaded 1356 | * @param {String} color - the color to fill/stroke with 1357 | * @param {HTMLCanvasElement} canvas - the canvas element 1358 | * @returns {HTMLCanvasElement} - the created offscreen canvas 1359 | */ 1360 | 1361 | 1362 | Sparticles.prototype.drawOffscreenCanvasForImage = function (image, color, canvas) { 1363 | var size = image.width; 1364 | var ctx = canvas.getContext("2d"); 1365 | canvas.width = size; 1366 | canvas.height = size; 1367 | ctx.drawImage(image, 0, 0, size, size, 0, 0, size, size); 1368 | ctx.globalCompositeOperation = "source-atop"; 1369 | ctx.fillStyle = color; 1370 | ctx.fillRect(0, 0, size, size); 1371 | return canvas; 1372 | }; 1373 | /** 1374 | * - wipe the canvas, 1375 | * - update each sparticle, 1376 | * - render each sparticle 1377 | * - sort so that larger particles on top 1378 | * @returns {Array} the array of Sparticle instances 1379 | */ 1380 | 1381 | 1382 | Sparticles.prototype.drawFrame = function () { 1383 | this.ctx.clearRect(0, 0, this.width, this.height); 1384 | 1385 | for (var i = 0; i < this.sparticles.length; i++) { 1386 | var sparticle = this.sparticles[i]; 1387 | sparticle.update().render(this.canvasses); 1388 | } 1389 | 1390 | this.sort(); 1391 | return this.sparticles; 1392 | }; 1393 | 1394 | export default Sparticles; 1395 | --------------------------------------------------------------------------------