├── .gitattributes
├── .gitignore
├── .npmignore
├── .travis.yml
├── .yo-rc.json
├── README.MD
├── gulpfile.js
├── package-lock.json
├── package.json
├── src
├── index.ts
├── lib
│ ├── CanvasManager.ts
│ ├── Particle.ts
│ ├── ParticleInteraction.ts
│ ├── ParticlesManager.ts
│ ├── index.ts
│ ├── interfaces.ts
│ └── utils.ts
├── package.json
├── particles.component.ts
├── particles.directive.ts
└── tsconfig.es5.json
├── tools
└── gulp
│ └── inline-resources.js
├── tsconfig.json
└── tslint.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 |
5 | # TypeScript
6 | src/*.js
7 | src/*.map
8 | src/*.d.ts
9 |
10 | # JetBrains
11 | .idea
12 | .project
13 | .settings
14 | .idea/*
15 | *.iml
16 |
17 | # VS Code
18 | .vscode/*
19 | .vs/
20 |
21 | # Windows
22 | Thumbs.db
23 | Desktop.ini
24 |
25 | # Mac
26 | .DS_Store
27 | **/.DS_Store
28 |
29 | # Ngc generated files
30 | **/*.ngfactory.ts
31 |
32 | # Build files
33 | dist/*
34 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 | docs/*
5 | # DO NOT IGNORE TYPESCRIPT FILES FOR NPM
6 | # TypeScript
7 | # *.js
8 | # *.map
9 | # *.d.ts
10 |
11 | # JetBrains
12 | .idea
13 | .project
14 | .settings
15 | .idea/*
16 | *.iml
17 |
18 | # VS Code
19 | .vscode/*
20 |
21 | # Windows
22 | Thumbs.db
23 | Desktop.ini
24 |
25 | # Mac
26 | .DS_Store
27 | **/.DS_Store
28 |
29 | # Ngc generated files
30 | **/*.ngfactory.ts
31 |
32 | # Library files
33 | src/*
34 | build/*
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | node_js:
4 | - '4.2.1'
5 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-angular2-library": {
3 | "promptValues": {
4 | "gitRepositoryUrl": "https://github.com/ryuKKu-/angular-particle"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # angular-particle
2 |
3 | Implementation of [particle.js](https://github.com/VincentGarreau/particles.js/) with TypeScript for Angular2/4. Inspired by [react-particles-js](https://github.com/Wufe/react-particles-js)
4 |
5 | ## Installation
6 |
7 | To install this library, run:
8 |
9 | ```bash
10 | $ npm install angular-particle --save
11 | ```
12 |
13 | ## How to use
14 |
15 | ```typescript
16 | // Import ParticlesModule
17 | import { ParticlesModule } from 'angular-particle';
18 |
19 | @NgModule({
20 | declarations: [
21 | ...
22 | ],
23 | imports: [
24 | ...
25 | ParticlesModule
26 | ],
27 | providers: [],
28 | bootstrap: []
29 | })
30 | export class AppModule { }
31 | ```
32 |
33 | And just use the component in your HTML
34 |
35 | ```html
36 |
37 | ```
38 |
39 | Parameters configuration can be found [here](http://vincentgarreau.com/particles.js/). If you don't provide any parameters, default one are used.
40 |
41 |
42 | ## Properties
43 |
44 | | Property | Type | Definition |
45 | | -------- | ------ | --------------------------------------- |
46 | | params | object | The parameters for particle.js |
47 | | style | object | The style of the canvas container |
48 | | width | number | The width of the canvas element (in %) |
49 | | height | number | The height of the canvas element (in %) |
50 |
51 |
52 | ## Example
53 |
54 | ```typescript
55 |
56 | @Component({
57 | selector: 'app-root',
58 | templateUrl: './app.component.html',
59 | styleUrls: ['./app.component.css']
60 | })
61 | export class AppComponent implements OnInit {
62 | myStyle: object = {};
63 | myParams: object = {};
64 | width: number = 100;
65 | height: number = 100;
66 |
67 | ngOnInit() {
68 | this.myStyle = {
69 | 'position': 'fixed',
70 | 'width': '100%',
71 | 'height': '100%',
72 | 'z-index': -1,
73 | 'top': 0,
74 | 'left': 0,
75 | 'right': 0,
76 | 'bottom': 0,
77 | };
78 |
79 | this.myParams = {
80 | particles: {
81 | number: {
82 | value: 200,
83 | },
84 | color: {
85 | value: '#ff0000'
86 | },
87 | shape: {
88 | type: 'triangle',
89 | },
90 | }
91 | };
92 | }
93 | }
94 | ```
95 |
96 | ```html
97 |
98 | ```
99 |
100 |
101 | ## License
102 |
103 | MIT © [Luc Raymond](mailto:ryukku.raymond@gmail.com)
104 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | var gulp = require('gulp'),
3 | path = require('path'),
4 | ngc = require('@angular/compiler-cli/src/main').main,
5 | rollup = require('gulp-rollup'),
6 | rename = require('gulp-rename'),
7 | del = require('del'),
8 | runSequence = require('run-sequence'),
9 | inlineResources = require('./tools/gulp/inline-resources');
10 |
11 | const rootFolder = path.join(__dirname);
12 | const srcFolder = path.join(rootFolder, 'src');
13 | const tmpFolder = path.join(rootFolder, '.tmp');
14 | const buildFolder = path.join(rootFolder, 'build');
15 | const distFolder = path.join(rootFolder, 'dist');
16 |
17 | /**
18 | * 1. Delete /dist folder
19 | */
20 | gulp.task('clean:dist', function () {
21 |
22 | // Delete contents but not dist folder to avoid broken npm links
23 | // when dist directory is removed while npm link references it.
24 | return deleteFolders([distFolder + '/**', '!' + distFolder]);
25 | });
26 |
27 | /**
28 | * 2. Clone the /src folder into /.tmp. If an npm link inside /src has been made,
29 | * then it's likely that a node_modules folder exists. Ignore this folder
30 | * when copying to /.tmp.
31 | */
32 | gulp.task('copy:source', function () {
33 | return gulp.src([`${srcFolder}/**/*`, `!${srcFolder}/node_modules`])
34 | .pipe(gulp.dest(tmpFolder));
35 | });
36 |
37 | /**
38 | * 3. Inline template (.html) and style (.css) files into the the component .ts files.
39 | * We do this on the /.tmp folder to avoid editing the original /src files
40 | */
41 | gulp.task('inline-resources', function () {
42 | return Promise.resolve()
43 | .then(() => inlineResources(tmpFolder));
44 | });
45 |
46 |
47 | /**
48 | * 4. Run the Angular compiler, ngc, on the /.tmp folder. This will output all
49 | * compiled modules to the /build folder.
50 | */
51 | gulp.task('ngc', function () {
52 | return ngc({
53 | project: `${tmpFolder}/tsconfig.es5.json`
54 | })
55 | .then((exitCode) => {
56 | if (exitCode === 1) {
57 | // This error is caught in the 'compile' task by the runSequence method callback
58 | // so that when ngc fails to compile, the whole compile process stops running
59 | throw new Error('ngc compilation failed');
60 | }
61 | });
62 | });
63 |
64 | /**
65 | * 5. Run rollup inside the /build folder to generate our Flat ES module and place the
66 | * generated file into the /dist folder
67 | */
68 | gulp.task('rollup:fesm', function () {
69 | return gulp.src(`${buildFolder}/**/*.js`)
70 | // transform the files here.
71 | .pipe(rollup({
72 |
73 | // Bundle's entry point
74 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry
75 | entry: `${buildFolder}/index.js`,
76 |
77 | // Allow mixing of hypothetical and actual files. "Actual" files can be files
78 | // accessed by Rollup or produced by plugins further down the chain.
79 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system
80 | // when subdirectories are used in the `src` directory.
81 | allowRealFiles: true,
82 |
83 | // A list of IDs of modules that should remain external to the bundle
84 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external
85 | external: [
86 | '@angular/core',
87 | '@angular/common'
88 | ],
89 |
90 | // Format of generated bundle
91 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format
92 | format: 'es'
93 | }))
94 | .pipe(gulp.dest(distFolder));
95 | });
96 |
97 | /**
98 | * 6. Run rollup inside the /build folder to generate our UMD module and place the
99 | * generated file into the /dist folder
100 | */
101 | gulp.task('rollup:umd', function () {
102 | return gulp.src(`${buildFolder}/**/*.js`)
103 | // transform the files here.
104 | .pipe(rollup({
105 |
106 | // Bundle's entry point
107 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#entry
108 | entry: `${buildFolder}/index.js`,
109 |
110 | // Allow mixing of hypothetical and actual files. "Actual" files can be files
111 | // accessed by Rollup or produced by plugins further down the chain.
112 | // This prevents errors like: 'path/file' does not exist in the hypothetical file system
113 | // when subdirectories are used in the `src` directory.
114 | allowRealFiles: true,
115 |
116 | // A list of IDs of modules that should remain external to the bundle
117 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external
118 | external: [
119 | '@angular/core',
120 | '@angular/common'
121 | ],
122 |
123 | // Format of generated bundle
124 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#format
125 | format: 'umd',
126 |
127 | // Export mode to use
128 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#exports
129 | exports: 'named',
130 |
131 | // The name to use for the module for UMD/IIFE bundles
132 | // (required for bundles with exports)
133 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#modulename
134 | moduleName: 'angular-particle',
135 |
136 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals
137 | globals: {
138 | typescript: 'ts'
139 | }
140 |
141 | }))
142 | .pipe(rename('angular-particle.umd.js'))
143 | .pipe(gulp.dest(distFolder));
144 | });
145 |
146 | /**
147 | * 7. Copy all the files from /build to /dist, except .js files. We ignore all .js from /build
148 | * because with don't need individual modules anymore, just the Flat ES module generated
149 | * on step 5.
150 | */
151 | gulp.task('copy:build', function () {
152 | return gulp.src([`${buildFolder}/**/*`, `!${buildFolder}/**/*.js`])
153 | .pipe(gulp.dest(distFolder));
154 | });
155 |
156 | /**
157 | * 8. Copy package.json from /src to /dist
158 | */
159 | gulp.task('copy:manifest', function () {
160 | return gulp.src([`${srcFolder}/package.json`])
161 | .pipe(gulp.dest(distFolder));
162 | });
163 |
164 | /**
165 | * 9. Copy README.md from / to /dist
166 | */
167 | gulp.task('copy:readme', function () {
168 | return gulp.src([path.join(rootFolder, 'README.MD')])
169 | .pipe(gulp.dest(distFolder));
170 | });
171 |
172 | /**
173 | * 10. Delete /.tmp folder
174 | */
175 | gulp.task('clean:tmp', function () {
176 | return deleteFolders([tmpFolder]);
177 | });
178 |
179 | /**
180 | * 11. Delete /build folder
181 | */
182 | gulp.task('clean:build', function () {
183 | return deleteFolders([buildFolder]);
184 | });
185 |
186 | gulp.task('compile', function () {
187 | runSequence(
188 | 'clean:dist',
189 | 'copy:source',
190 | 'inline-resources',
191 | 'ngc',
192 | 'rollup:fesm',
193 | 'rollup:umd',
194 | 'copy:build',
195 | 'copy:manifest',
196 | 'copy:readme',
197 | 'clean:build',
198 | 'clean:tmp',
199 | function (err) {
200 | if (err) {
201 | console.log('ERROR:', err.message);
202 | deleteFolders([distFolder, tmpFolder, buildFolder]);
203 | } else {
204 | console.log('Compilation finished succesfully');
205 | }
206 | });
207 | });
208 |
209 | /**
210 | * Watch for any change in the /src folder and compile files
211 | */
212 | gulp.task('watch', function () {
213 | gulp.watch(`${srcFolder}/**/*`, ['compile']);
214 | });
215 |
216 | gulp.task('clean', ['clean:dist', 'clean:tmp', 'clean:build']);
217 |
218 | gulp.task('build', ['clean', 'compile']);
219 | gulp.task('build:watch', ['build', 'watch']);
220 | gulp.task('default', ['build:watch']);
221 |
222 | /**
223 | * Deletes the specified folder
224 | */
225 | function deleteFolders(folders) {
226 | return del(folders);
227 | }
228 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-particle",
3 | "version": "1.0.4",
4 | "scripts": {
5 | "build": "gulp build",
6 | "build:watch": "gulp",
7 | "docs": "npm run docs:build",
8 | "docs:build": "compodoc -p tsconfig.json -n angular-particle -d docs --hideGenerator",
9 | "docs:serve": "npm run docs:build -- -s",
10 | "docs:watch": "npm run docs:build -- -s -w",
11 | "lint": "tslint --type-check --project tsconfig.json src/**/*.ts",
12 | "test": "tsc && karma start"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/ryuKKu-/angular-particle"
17 | },
18 | "author": {
19 | "name": "Luc Raymond",
20 | "email": "ryukku.raymond@gmail.com"
21 | },
22 | "keywords": [
23 | "angular2",
24 | "angular4",
25 | "angular",
26 | "particlesjs",
27 | "particle",
28 | "typescript",
29 | "ng2",
30 | "particles"
31 | ],
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/ryuKKu-/angular-particle/issues"
35 | },
36 | "devDependencies": {
37 | "@angular/common": "^7.1.0",
38 | "@angular/compiler": "^7.1.0",
39 | "@angular/compiler-cli": "^7.1.0",
40 | "@angular/core": "^7.1.0",
41 | "@angular/platform-browser": "^7.1.0",
42 | "@angular/platform-browser-dynamic": "^7.1.0",
43 | "@compodoc/compodoc": "^1.1.6",
44 | "@types/jasmine": "3.3.0",
45 | "@types/node": "~10.12.10",
46 | "codelyzer": "~4.5.0",
47 | "core-js": "^2.5.7",
48 | "del": "^3.0.0",
49 | "gulp": "^3.9.1",
50 | "gulp-rename": "^1.4.0",
51 | "gulp-rollup": "^2.16.2",
52 | "jasmine-core": "~3.3.0",
53 | "jasmine-spec-reporter": "~4.2.1",
54 | "karma": "~3.1.1",
55 | "karma-chrome-launcher": "~2.2.0",
56 | "karma-cli": "~1.0.1",
57 | "karma-coverage-istanbul-reporter": "^2.0.4",
58 | "karma-jasmine": "~2.0.1",
59 | "karma-jasmine-html-reporter": "^1.4.0",
60 | "node-sass": "^4.10.0",
61 | "node-sass-tilde-importer": "^1.0.2",
62 | "node-watch": "^0.5.9",
63 | "protractor": "~5.4.1",
64 | "rollup": "^0.67.3",
65 | "run-sequence": "^2.2.1",
66 | "rxjs": "^6.3.3",
67 | "ts-node": "~7.0.1",
68 | "tslint": "~5.11.0",
69 | "typescript": "~3.1.6",
70 | "zone.js": "^0.8.26"
71 | },
72 | "engines": {
73 | "node": ">=6.0.0"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { ParticlesComponent } from './particles.component';
5 | import { ParticlesDirective } from './particles.directive';
6 |
7 | export * from './particles.component';
8 | export * from './particles.directive';
9 |
10 | @NgModule({
11 | imports: [
12 | CommonModule
13 | ],
14 | declarations: [
15 | ParticlesComponent,
16 | ParticlesDirective
17 | ],
18 | exports: [
19 | ParticlesComponent,
20 | ParticlesDirective
21 | ]
22 | })
23 | export class ParticlesModule { }
--------------------------------------------------------------------------------
/src/lib/CanvasManager.ts:
--------------------------------------------------------------------------------
1 | import { ParticlesManager, ICanvasParams, IParams, ITmpParams, hexToRgb } from './index';
2 |
3 | export class CanvasManager {
4 | public particlesManager: ParticlesManager;
5 |
6 | constructor(private _canvasParams: ICanvasParams, private _params: IParams, private _tmpParams: ITmpParams) {
7 | this._onWindowResize = this._onWindowResize.bind(this);
8 |
9 | this._retinaInit();
10 | this._canvasSize();
11 |
12 | this.particlesManager = new ParticlesManager(this._canvasParams, this._params, this._tmpParams);
13 | this.particlesManager.particlesCreate();
14 |
15 | this._densityAutoParticles();
16 |
17 | let { particles } = this._params;
18 | particles.line_linked.color_rgb_line = hexToRgb(particles.line_linked.color);
19 | }
20 |
21 | public cancelAnimation(): void {
22 | if (!this._tmpParams.drawAnimFrame) {
23 | return;
24 | }
25 | cancelAnimationFrame(this._tmpParams.drawAnimFrame);
26 | this._tmpParams.drawAnimFrame = null;
27 | }
28 |
29 | public draw(): void {
30 | let { particles } = this._params;
31 |
32 | if (particles.shape.type == 'image') {
33 | if (this._tmpParams.img_type == 'svg') {
34 | if (this._tmpParams.count_svg >= particles.number.value) {
35 | this.particlesManager.particlesDraw();
36 | if (!particles.move.enable) {
37 | cancelAnimationFrame(this._tmpParams.drawAnimFrame);
38 | } else {
39 | this._tmpParams.drawAnimFrame = requestAnimationFrame(this.draw.bind(this));
40 | }
41 | } else {
42 | if (!this._tmpParams.img_error) {
43 | this._tmpParams.drawAnimFrame = requestAnimationFrame(this.draw.bind(this));
44 | }
45 | }
46 | } else {
47 | if (this._tmpParams.img_obj != undefined) {
48 | this.particlesManager.particlesDraw();
49 | if (!particles.move.enable) {
50 | cancelAnimationFrame(this._tmpParams.drawAnimFrame);
51 | } else {
52 | this._tmpParams.drawAnimFrame = requestAnimationFrame(this.draw.bind(this));
53 | }
54 | } else {
55 | if (!this._tmpParams.img_error) {
56 | this._tmpParams.drawAnimFrame = requestAnimationFrame(this.draw.bind(this));
57 | }
58 | }
59 | }
60 | } else {
61 | this.particlesManager.particlesDraw();
62 |
63 | if (!particles.move.enable) {
64 | cancelAnimationFrame(this._tmpParams.drawAnimFrame);
65 | } else {
66 | this._tmpParams.drawAnimFrame = requestAnimationFrame(this.draw.bind(this));
67 | }
68 | }
69 | }
70 |
71 | private _densityAutoParticles(): void {
72 | let { particles } = this._params;
73 |
74 | if (particles.number.density.enable) {
75 | let area: number = this._canvasParams.el.width * this._canvasParams.el.height / 1000;
76 |
77 | if (this._tmpParams.retina) {
78 | area = area / (this._canvasParams.pxratio * 2);
79 | }
80 |
81 | let nb_particles: number = area * particles.number.value / particles.number.density.value_area;
82 |
83 | let missing_particles: number = particles.array.length - nb_particles;
84 |
85 | if (missing_particles < 0) {
86 | this.particlesManager.pushParticles(Math.abs(missing_particles));
87 | } else {
88 | this.particlesManager.removeParticles(missing_particles);
89 | }
90 | }
91 | }
92 |
93 | private _retinaInit(): void {
94 | if (this._params.retina_detect && window.devicePixelRatio > 1) {
95 | this._canvasParams.pxratio = window.devicePixelRatio;
96 | this._tmpParams.retina = true;
97 |
98 | this._canvasParams.width = this._canvasParams.el.offsetWidth * this._canvasParams.pxratio;
99 | this._canvasParams.height = this._canvasParams.el.offsetHeight * this._canvasParams.pxratio;
100 |
101 | this._params.particles.size.value = this._tmpParams.obj.size_value * this._canvasParams.pxratio;
102 | this._params.particles.size.anim.speed = this._tmpParams.obj.size_anim_speed * this._canvasParams.pxratio;
103 | this._params.particles.move.speed = this._tmpParams.obj.move_speed * this._canvasParams.pxratio;
104 | this._params.particles.line_linked.distance = this._tmpParams.obj.line_linked_distance * this._canvasParams.pxratio;
105 | this._params.interactivity.modes.grab.distance = this._tmpParams.obj.mode_grab_distance * this._canvasParams.pxratio;
106 | this._params.interactivity.modes.bubble.distance = this._tmpParams.obj.mode_bubble_distance * this._canvasParams.pxratio;
107 | this._params.particles.line_linked.width = this._tmpParams.obj.line_linked_width * this._canvasParams.pxratio;
108 | this._params.interactivity.modes.bubble.size = this._tmpParams.obj.mode_bubble_size * this._canvasParams.pxratio;
109 | this._params.interactivity.modes.repulse.distance = this._tmpParams.obj.mode_repulse_distance * this._canvasParams.pxratio;
110 |
111 | } else {
112 | this._canvasParams.pxratio = 1;
113 | this._tmpParams.retina = false;
114 | }
115 | }
116 |
117 | private _canvasClear(): void {
118 | this._canvasParams.ctx.clearRect(0, 0, this._canvasParams.width, this._canvasParams.height);
119 | }
120 |
121 | private _canvasPaint(): void {
122 | this._canvasParams.ctx.fillRect(0, 0, this._canvasParams.width, this._canvasParams.height);
123 | }
124 |
125 | private _canvasSize(): void {
126 | this._canvasParams.el.width = this._canvasParams.width;
127 | this._canvasParams.el.height = this._canvasParams.height;
128 |
129 | if (this._params && this._params.interactivity.events.resize) {
130 | window.addEventListener('resize', this._onWindowResize);
131 | }
132 | }
133 |
134 | private _onWindowResize(): void {
135 | this._canvasParams.width = this._canvasParams.el.offsetWidth;
136 | this._canvasParams.height = this._canvasParams.el.offsetHeight;
137 |
138 | if (this._tmpParams.retina) {
139 | this._canvasParams.width *= this._canvasParams.pxratio;
140 | this._canvasParams.height *= this._canvasParams.pxratio;
141 | }
142 |
143 | this._canvasParams.el.width = this._canvasParams.width;
144 | this._canvasParams.el.height = this._canvasParams.height;
145 |
146 | if (!this._params.particles.move.enable) {
147 | this.particlesManager.particlesEmpty();
148 | this.particlesManager.particlesCreate();
149 | this.particlesManager.particlesDraw();
150 | this._densityAutoParticles();
151 | }
152 |
153 | this._densityAutoParticles();
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/lib/Particle.ts:
--------------------------------------------------------------------------------
1 | import { IParams, ICanvasParams, ITmpParams, hexToRgb, getColor, createSvgImg } from './index';
2 |
3 | export class Particle {
4 | radius: number;
5 | radius_bubble: number;
6 | size_status: boolean;
7 | vs: number;
8 |
9 | x: number;
10 | y: number;
11 | color: any;
12 |
13 | opacity: number;
14 | opacity_bubble: number;
15 | opacity_status: boolean;
16 | vo: number;
17 |
18 | vx: number;
19 | vy: number;
20 |
21 | vx_i: number;
22 | vy_i: number;
23 |
24 | shape: string;
25 |
26 | img: { src: string; ratio: number; loaded?: boolean; obj?: any; };
27 |
28 | constructor(private _canvasParams: ICanvasParams, private _params: IParams, private _tmpParams: ITmpParams, color?: any, opacity?: any, position?: { x: number; y: number; }) {
29 | this._setupSize();
30 | this._setupPosition(position);
31 | this._setupColor(color);
32 | this._setupOpacity();
33 | this._setupAnimation();
34 | }
35 |
36 | private _setupSize(): void {
37 | this.radius = (this._params.particles.size.random ? Math.random() : 1) * this._params.particles.size.value;
38 | if (this._params.particles.size.anim.enable) {
39 | this.size_status = false;
40 | this.vs = this._params.particles.size.anim.speed / 100;
41 | if (!this._params.particles.size.anim.sync)
42 | this.vs = this.vs * Math.random();
43 | }
44 | }
45 |
46 | private _setupPosition(position?: { x: number; y: number; }): void {
47 | this.x = position ? position.x : Math.random() * this._canvasParams.width;
48 | this.y = position ? position.y : Math.random() * this._canvasParams.height;
49 |
50 | if (this.x > this._canvasParams.width - this.radius * 2) {
51 | this.x = this.x - this.radius;
52 | } else if (this.x < this.radius * 2) {
53 | this.x = this.x + this.radius;
54 | }
55 | if (this.y > this._canvasParams.height - this.radius * 2) {
56 | this.y = this.y - this.radius;
57 | } else if (this.y < this.radius * 2) {
58 | this.y = this.y + this.radius;
59 | }
60 |
61 | if (this._params.particles.move.bounce) {
62 | this._checkOverlap(this, position);
63 | }
64 | }
65 |
66 | private _checkOverlap(p1: Particle, position?: { x: number; y: number; }): void {
67 | let { particles } = this._params;
68 |
69 | particles.array.forEach((particle: Particle) => {
70 | let p2: Particle = particle;
71 |
72 | let dx: number = p1.x - p2.x;
73 | let dy: number = p1.y - p2.y;
74 | let dist: number = Math.sqrt(dx * dx + dy * dy);
75 |
76 | if (dist <= p1.radius + p2.radius) {
77 | p1.x = position ? position.x : Math.random() * this._canvasParams.width;
78 | p1.y = position ? position.y : Math.random() * this._canvasParams.height;
79 | this._checkOverlap(p1);
80 | }
81 | });
82 | }
83 |
84 | private _setupColor(color?: any) {
85 | this.color = getColor(color.value);
86 | }
87 |
88 | private _setupOpacity(): void {
89 | this.opacity = (this._params.particles.opacity.random ? Math.random() : 1) * this._params.particles.opacity.value;
90 | if (this._params.particles.opacity.anim.enable) {
91 | this.opacity_status = false;
92 | this.vo = this._params.particles.opacity.anim.speed / 100;
93 | if (!this._params.particles.opacity.anim.sync) {
94 | this.vo = this.vo * Math.random();
95 | }
96 | }
97 | }
98 |
99 | private _setupAnimation(): void {
100 | let velbase: { x: number; y: number; } = null;
101 | switch (this._params.particles.move.direction) {
102 | case 'top':
103 | velbase = { x: 0, y: -1 };
104 | break;
105 | case 'top-right':
106 | velbase = { x: 0.5, y: -0.5 };
107 | break;
108 | case 'right':
109 | velbase = { x: 1, y: 0 };
110 | break;
111 | case 'bottom-right':
112 | velbase = { x: 0.5, y: 0.5 };
113 | break;
114 | case 'bottom':
115 | velbase = { x: 0, y: 1 };
116 | break;
117 | case 'bottom-left':
118 | velbase = { x: -0.5, y: 1 };
119 | break;
120 | case 'left':
121 | velbase = { x: -1, y: 0 };
122 | break;
123 | case 'top-left':
124 | velbase = { x: -0.5, y: -0.5 };
125 | break;
126 | default:
127 | velbase = { x: 0, y: 0 };
128 | break;
129 | }
130 |
131 | if (this._params.particles.move.straight) {
132 | this.vx = velbase.x;
133 | this.vy = velbase.y;
134 | if (this._params.particles.move.random) {
135 | this.vx = this.vx * (Math.random());
136 | this.vy = this.vy * (Math.random());
137 | }
138 | } else {
139 | this.vx = velbase.x + Math.random() - 0.5;
140 | this.vy = velbase.y + Math.random() - 0.5;
141 | }
142 |
143 | this.vx_i = this.vx;
144 | this.vy_i = this.vy;
145 |
146 | let shape_type: any = this._params.particles.shape.type;
147 |
148 | if (typeof (shape_type) == 'object') {
149 | if (shape_type instanceof Array) {
150 | let shape_selected: string = shape_type[Math.floor(Math.random() * shape_type.length)];
151 | this.shape = shape_selected;
152 | }
153 | } else {
154 | this.shape = shape_type;
155 | }
156 |
157 | if (this.shape == 'image') {
158 | let sh: any = this._params.particles.shape;
159 | this.img = {
160 | src: sh.image.src,
161 | ratio: sh.image.width / sh.image.height
162 | };
163 |
164 | if (!this.img.ratio)
165 | this.img.ratio = 1;
166 | if (this._tmpParams.img_type == 'svg' && this._tmpParams.source_svg != undefined) {
167 | createSvgImg(this, this._tmpParams);
168 | if (this._tmpParams.pushing) {
169 | this.img.loaded = false;
170 | }
171 | }
172 | }
173 | }
174 |
175 | private _drawShape(c: CanvasRenderingContext2D, startX: number, startY: number, sideLength: number, sideCountNumerator: number, sideCountDenominator: number): void {
176 | let sideCount: number = sideCountNumerator * sideCountDenominator;
177 | let decimalSides: number = sideCountNumerator / sideCountDenominator;
178 | let interiorAngleDegrees: number = (180 * (decimalSides - 2)) / decimalSides;
179 | let interiorAngle: number = Math.PI - Math.PI * interiorAngleDegrees / 180;
180 |
181 | c.save();
182 | c.beginPath();
183 | c.translate(startX, startY);
184 | c.moveTo(0, 0);
185 |
186 | for (let i = 0; i < sideCount; i++) {
187 | c.lineTo(sideLength, 0);
188 | c.translate(sideLength, 0);
189 | c.rotate(interiorAngle);
190 | }
191 |
192 | c.fill();
193 | c.restore();
194 | }
195 |
196 | public draw(): void {
197 | let { particles } = this._params;
198 |
199 | let radius: number;
200 | if (this.radius_bubble != undefined) {
201 | radius = this.radius_bubble;
202 | } else {
203 | radius = this.radius;
204 | }
205 |
206 | let opacity: number;
207 | if (this.opacity_bubble != undefined) {
208 | opacity = this.opacity_bubble;
209 | } else {
210 | opacity = this.opacity;
211 | }
212 |
213 | let color_value: string;
214 |
215 | if (this.color.rgb) {
216 | let { r, g, b } = this.color.rgb;
217 | color_value = `rgba( ${r}, ${g}, ${b}, ${opacity} )`;
218 | } else {
219 | let { h, s, l } = this.color.hsl;
220 | color_value = `hsla( ${h}, ${s}, ${l}, ${opacity} )`;
221 | }
222 |
223 | this._canvasParams.ctx.fillStyle = color_value;
224 | this._canvasParams.ctx.beginPath();
225 |
226 | switch (this.shape) {
227 | case 'circle':
228 | this._canvasParams.ctx.arc(this.x, this.y, radius, 0, Math.PI * 2, false);
229 | break;
230 |
231 | case 'edge':
232 | this._canvasParams.ctx.rect(this.x - radius, this.y - radius, radius * 2, radius * 2);
233 | break;
234 |
235 | case 'triangle':
236 | this._drawShape(this._canvasParams.ctx, this.x - radius, this.y + radius / 1.66, radius * 2, 3, 2);
237 | break;
238 |
239 | case 'polygon':
240 | this._drawShape(
241 | this._canvasParams.ctx,
242 | this.x - radius / (this._params.particles.shape.polygon.nb_sides / 3.5),
243 | this.y - radius / (2.66 / 3.5),
244 | radius * 2.66 / (this._params.particles.shape.polygon.nb_sides / 3),
245 | this._params.particles.shape.polygon.nb_sides,
246 | 1
247 | );
248 | break;
249 |
250 | case 'star':
251 | this._drawShape(
252 | this._canvasParams.ctx,
253 | this.x - radius * 2 / (this._params.particles.shape.polygon.nb_sides / 4),
254 | this.y - radius / (2 * 2.66 / 3.5),
255 | radius * 2 * 2.66 / (this._params.particles.shape.polygon.nb_sides / 3),
256 | this._params.particles.shape.polygon.nb_sides,
257 | 2
258 | );
259 | break;
260 |
261 | case 'image':
262 | let draw: (img_obj: any) => void =
263 | (img_obj) => {
264 | this._canvasParams.ctx.drawImage(
265 | img_obj,
266 | this.x - radius,
267 | this.y - radius,
268 | radius * 2,
269 | radius * 2 / this.img.ratio
270 | );
271 | };
272 | let img_obj: any;
273 |
274 | if (this._tmpParams.img_type == 'svg') {
275 | img_obj = this.img.obj;
276 | } else {
277 | img_obj = this._tmpParams.img_obj;
278 | }
279 |
280 | if (img_obj)
281 | draw(img_obj);
282 | break;
283 | }
284 |
285 | this._canvasParams.ctx.closePath();
286 |
287 | if (this._params.particles.shape.stroke.width > 0) {
288 | this._canvasParams.ctx.strokeStyle = this._params.particles.shape.stroke.color;
289 | this._canvasParams.ctx.lineWidth = this._params.particles.shape.stroke.width;
290 | this._canvasParams.ctx.stroke();
291 | }
292 |
293 | this._canvasParams.ctx.fill();
294 | }
295 | }
--------------------------------------------------------------------------------
/src/lib/ParticleInteraction.ts:
--------------------------------------------------------------------------------
1 | import { Particle, IParams, ICanvasParams } from './index';
2 |
3 | export class ParticleInteraction {
4 | constructor() { }
5 |
6 | linkParticles(p1: Particle, p2: Particle, params: IParams, canvasParams: ICanvasParams): void {
7 | let dx: number = p1.x - p2.x;
8 | let dy: number = p1.y - p2.y;
9 | let dist: number = Math.sqrt(dx * dx + dy * dy);
10 | let { line_linked } = params.particles;
11 |
12 | if (dist <= params.particles.line_linked.distance) {
13 | let opacity_line: number = params.particles.line_linked.opacity - (dist / (1 / params.particles.line_linked.opacity)) / params.particles.line_linked.distance;
14 | if (opacity_line > 0) {
15 | let color_line: any = params.particles.line_linked.color_rgb_line;
16 | let { r, g, b } = color_line;
17 | canvasParams.ctx.save();
18 | canvasParams.ctx.strokeStyle = `rgba( ${r}, ${g}, ${b}, ${opacity_line} )`;
19 | canvasParams.ctx.lineWidth = params.particles.line_linked.width;
20 |
21 | canvasParams.ctx.beginPath();
22 | if (line_linked.shadow.enable) {
23 | canvasParams.ctx.shadowBlur = line_linked.shadow.blur;
24 | canvasParams.ctx.shadowColor = line_linked.shadow.color;
25 | }
26 |
27 | canvasParams.ctx.moveTo(p1.x, p1.y);
28 | canvasParams.ctx.lineTo(p2.x, p2.y);
29 | canvasParams.ctx.stroke();
30 | canvasParams.ctx.closePath();
31 | canvasParams.ctx.restore();
32 | }
33 | }
34 | }
35 |
36 | attractParticles(p1: Particle, p2: Particle, params: IParams): void {
37 | let dx: number = p1.x - p2.x;
38 | let dy: number = p1.y - p2.y;
39 | let dist: number = Math.sqrt(dx * dx + dy * dy);
40 |
41 | if (dist <= params.particles.line_linked.distance) {
42 | let ax = dx / (params.particles.move.attract.rotateX * 1000);
43 | let ay = dy / (params.particles.move.attract.rotateY * 1000);
44 |
45 | p1.vx -= ax;
46 | p1.vy -= ay;
47 |
48 | p2.vx += ax;
49 | p2.vy += ay;
50 | }
51 | }
52 |
53 | bounceParticles(p1: Particle, p2: Particle): void {
54 | let dx: number = p1.x - p2.x;
55 | let dy: number = p1.y - p2.y;
56 | let dist: number = Math.sqrt(dx * dx + dy * dy);
57 | let dist_p: number = p1.radius + p2.radius;
58 |
59 | if (dist <= dist_p) {
60 | p1.vx = -p1.vx;
61 | p1.vy = -p1.vy;
62 | p2.vx = -p2.vx;
63 | p2.vy = -p2.vy;
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/src/lib/ParticlesManager.ts:
--------------------------------------------------------------------------------
1 | import { Particle, ParticleInteraction, IParams, ICanvasParams, IMouseParams, ITmpParams, isInArray, clamp } from './index';
2 |
3 | export class ParticlesManager {
4 | private _interaction: ParticleInteraction;
5 |
6 | constructor(private _canvasParams: ICanvasParams, private _params: IParams, private _tmpParams: ITmpParams) {
7 | this._interaction = new ParticleInteraction();
8 | }
9 |
10 | public particlesCreate(): void {
11 | let { color, opacity } = this._params.particles;
12 | for (let i = 0; i < this._params.particles.number.value; i++) {
13 | this._params.particles.array.push(new Particle(this._canvasParams, this._params, this._tmpParams, color, opacity.value));
14 | }
15 | }
16 |
17 | private _particlesUpdate(): void {
18 | type Pos = {
19 | x_left: number;
20 | x_right: number;
21 | y_top: number;
22 | y_bottom: number;
23 | };
24 |
25 | this._params.particles.array.forEach((particle: Particle, i: number) => {
26 | if (this._params.particles.move.enable) {
27 | let ms = this._params.particles.move.speed / 2;
28 | particle.x += particle.vx * ms;
29 | particle.y += particle.vy * ms;
30 | }
31 |
32 | if (this._params.particles.opacity.anim.enable) {
33 | if (particle.opacity_status == true) {
34 | if (particle.opacity >= this._params.particles.opacity.value)
35 | particle.opacity_status = false;
36 | particle.opacity += particle.vo;
37 | } else {
38 | if (particle.opacity <= this._params.particles.opacity.anim.opacity_min)
39 | particle.opacity_status = true;
40 | particle.opacity -= particle.vo;
41 | }
42 | if (particle.opacity < 0)
43 | particle.opacity = 0;
44 | }
45 |
46 | if (this._params.particles.size.anim.enable) {
47 | if (particle.size_status == true) {
48 | if (particle.radius >= this._params.particles.size.value)
49 | particle.size_status = false;
50 | particle.radius += particle.vs;
51 | } else {
52 | if (particle.radius <= this._params.particles.size.anim.size_min)
53 | particle.size_status = true;
54 | particle.radius -= particle.vs;
55 | }
56 | if (particle.radius < 0)
57 | particle.radius = 0;
58 | }
59 |
60 | let new_pos: Pos;
61 |
62 | if (this._params.particles.move.out_mode == 'bounce') {
63 | new_pos = {
64 | x_left: particle.radius,
65 | x_right: this._canvasParams.width,
66 | y_top: particle.radius,
67 | y_bottom: this._canvasParams.height
68 | };
69 | } else {
70 | new_pos = {
71 | x_left: -particle.radius,
72 | x_right: this._canvasParams.width + particle.radius,
73 | y_top: -particle.radius,
74 | y_bottom: this._canvasParams.height + particle.radius
75 | };
76 | }
77 |
78 | if (particle.x - particle.radius > this._canvasParams.width) {
79 | particle.x = new_pos.x_left;
80 | particle.y = Math.random() * this._canvasParams.height;
81 | } else if (particle.x + particle.radius < 0) {
82 | particle.x = new_pos.x_right;
83 | particle.y = Math.random() * this._canvasParams.height;
84 | }
85 |
86 | if (particle.y - particle.radius > this._canvasParams.height) {
87 | particle.y = new_pos.y_top;
88 | particle.x = Math.random() * this._canvasParams.width;
89 | } else if (particle.y + particle.radius < 0) {
90 | particle.y = new_pos.y_bottom;
91 | particle.x = Math.random() * this._canvasParams.width;
92 | }
93 |
94 | switch (this._params.particles.move.out_mode) {
95 | case 'bounce':
96 | if (particle.x + particle.radius > this._canvasParams.width)
97 | particle.vx = -particle.vx;
98 | else if (particle.x - particle.radius < 0)
99 | particle.vx = -particle.vx;
100 | if (particle.y + particle.radius > this._canvasParams.height)
101 | particle.vy = -particle.vy;
102 | else if (particle.y - particle.radius < 0)
103 | particle.vy = -particle.vy;
104 | break;
105 | }
106 |
107 | if (isInArray('grab', this._params.interactivity.events.onhover.mode)) {
108 | this._grabParticle(particle);
109 | }
110 |
111 | if (isInArray('bubble', this._params.interactivity.events.onhover.mode) ||
112 | isInArray('bubble', this._params.interactivity.events.onclick.mode)) {
113 | this._bubbleParticle(particle);
114 | }
115 |
116 | if (isInArray('repulse', this._params.interactivity.events.onhover.mode) ||
117 | isInArray('repulse', this._params.interactivity.events.onclick.mode)) {
118 | this._repulseParticle(particle);
119 | }
120 |
121 | if (this._params.particles.line_linked.enable || this._params.particles.move.attract.enable) {
122 | for (let j = i + 1; j < this._params.particles.array.length; j++) {
123 | let link = this._params.particles.array[j];
124 |
125 | if (this._params.particles.line_linked.enable)
126 | this._interaction.linkParticles(particle, link, this._params, this._canvasParams);
127 |
128 | if (this._params.particles.move.attract.enable)
129 | this._interaction.attractParticles(particle, link, this._params);
130 |
131 | if (this._params.particles.move.bounce)
132 | this._interaction.bounceParticles(particle, link);
133 | }
134 | }
135 | });
136 | }
137 |
138 | public particlesDraw(): void {
139 | this._canvasParams.ctx.clearRect(0, 0, this._canvasParams.width, this._canvasParams.height);
140 | this._particlesUpdate();
141 |
142 | this._params.particles.array.forEach((particle: Particle) => {
143 | particle.draw();
144 | });
145 | }
146 |
147 | public particlesEmpty(): void {
148 | this._params.particles.array = [];
149 | }
150 |
151 | public removeParticles(nb: number): void {
152 | this._params.particles.array.splice(0, nb);
153 |
154 | if (!this._params.particles.move.enable) {
155 | this.particlesDraw();
156 | }
157 | }
158 |
159 | public pushParticles(nb: number, pos?: IMouseParams): void {
160 | this._tmpParams.pushing = true;
161 |
162 | for (let i = 0; i < nb; i++) {
163 | this._params.particles.array.push(
164 | new Particle(
165 | this._canvasParams,
166 | this._params,
167 | this._tmpParams,
168 | this._params.particles.color,
169 | this._params.particles.opacity.value,
170 | {
171 | x: pos ? pos.pos_x : Math.random() * this._canvasParams.width,
172 | y: pos ? pos.pos_y : Math.random() * this._canvasParams.height
173 | })
174 | );
175 |
176 | if (i == nb - 1) {
177 | if (!this._params.particles.move.enable) {
178 | this.particlesDraw();
179 | }
180 | this._tmpParams.pushing = false;
181 | }
182 | }
183 | }
184 |
185 | private _bubbleParticle(particle: Particle) {
186 | if (this._params.interactivity.events.onhover.enable &&
187 | isInArray('bubble', this._params.interactivity.events.onhover.mode)) {
188 |
189 | let dx_mouse: number = particle.x - this._params.interactivity.mouse.pos_x;
190 | let dy_mouse: number = particle.y - this._params.interactivity.mouse.pos_y;
191 | let dist_mouse: number = Math.sqrt(dx_mouse * dx_mouse + dy_mouse * dy_mouse);
192 | let ratio: number = 1 - dist_mouse / this._params.interactivity.modes.bubble.distance;
193 |
194 | let init: () => void =
195 | () => {
196 | particle.opacity_bubble = particle.opacity;
197 | particle.radius_bubble = particle.radius;
198 | };
199 |
200 | if (dist_mouse <= this._params.interactivity.modes.bubble.distance) {
201 | if (ratio >= 0 && this._params.interactivity.status == 'mousemove') {
202 |
203 | if (this._params.interactivity.modes.bubble.size != this._params.particles.size.value) {
204 | if (this._params.interactivity.modes.bubble.size > this._params.particles.size.value) {
205 | let size: number = particle.radius + (this._params.interactivity.modes.bubble.size * ratio);
206 | if (size >= 0) {
207 | particle.radius_bubble = size;
208 | }
209 | } else {
210 | let dif: number = particle.radius - this._params.interactivity.modes.bubble.size;
211 | let size: number = particle.radius - (dif * ratio);
212 | if (size > 0) {
213 | particle.radius_bubble = size;
214 | } else {
215 | particle.radius_bubble = 0;
216 | }
217 | }
218 | }
219 |
220 | if (this._params.interactivity.modes.bubble.opacity != this._params.particles.opacity.value) {
221 | if (this._params.interactivity.modes.bubble.opacity > this._params.particles.opacity.value) {
222 | let opacity: number = this._params.interactivity.modes.bubble.opacity * ratio;
223 | if (opacity > particle.opacity && opacity <= this._params.interactivity.modes.bubble.opacity) {
224 | particle.opacity_bubble = opacity;
225 | }
226 | } else {
227 | let opacity: number = particle.opacity - (this._params.particles.opacity.value - this._params.interactivity.modes.bubble.opacity) * ratio;
228 | if (opacity < particle.opacity && opacity >= this._params.interactivity.modes.bubble.opacity) {
229 | particle.opacity_bubble = opacity;
230 | }
231 | }
232 | }
233 | }
234 | } else {
235 | init();
236 | }
237 |
238 | if (this._params.interactivity.status == 'mouseleave') {
239 | init();
240 | }
241 |
242 | } else if (this._params.interactivity.events.onclick.enable &&
243 | isInArray('bubble', this._params.interactivity.events.onclick.mode)) {
244 |
245 | if (this._tmpParams.bubble_clicking) {
246 | let dx_mouse: number = particle.x - this._params.interactivity.mouse.click_pos_x;
247 | let dy_mouse: number = particle.y - this._params.interactivity.mouse.click_pos_y;
248 | let dist_mouse: number = Math.sqrt(dx_mouse * dx_mouse + dy_mouse * dy_mouse);
249 | let time_spent: number = (new Date().getTime() - this._params.interactivity.mouse.click_time) / 1000;
250 |
251 | if (time_spent > this._params.interactivity.modes.bubble.duration) {
252 | this._tmpParams.bubble_duration_end = true;
253 | }
254 |
255 | if (time_spent > this._params.interactivity.modes.bubble.duration * 2) {
256 | this._tmpParams.bubble_clicking = false;
257 | this._tmpParams.bubble_duration_end = false;
258 | }
259 |
260 | let process: any = (bubble_param: any, particles_param: any, p_obj_bubble: any, p_obj: any, id: any) => {
261 | if (bubble_param != particles_param) {
262 | if (!this._tmpParams.bubble_duration_end) {
263 | if (dist_mouse <= this._params.interactivity.modes.bubble.distance) {
264 | let obj: any;
265 | if (p_obj_bubble != undefined) {
266 | obj = p_obj_bubble;
267 | } else {
268 | obj = p_obj;
269 | }
270 | if (obj != bubble_param) {
271 | let value: any = p_obj - (time_spent * (p_obj - bubble_param) / this._params.interactivity.modes.bubble.duration);
272 | if (id == 'size')
273 | particle.radius_bubble = value;
274 | if (id == 'opacity')
275 | particle.opacity_bubble = value;
276 | }
277 | } else {
278 | if (id == 'size')
279 | particle.radius_bubble = undefined;
280 | if (id == 'opacity')
281 | particle.opacity_bubble = undefined;
282 | }
283 | } else {
284 | if (p_obj_bubble != undefined) {
285 | let value_tmp: any = p_obj - (time_spent * (p_obj - bubble_param) / this._params.interactivity.modes.bubble.duration);
286 | let dif: any = bubble_param - value_tmp;
287 | let value: any = bubble_param + dif;
288 | if (id == 'size')
289 | particle.radius_bubble = value;
290 | if (id == 'opacity')
291 | particle.opacity_bubble = value;
292 | }
293 | }
294 | }
295 | };
296 |
297 | if (this._tmpParams.bubble_clicking) {
298 | process(this._params.interactivity.modes.bubble.size, this._params.particles.size.value, particle.radius_bubble, particle.radius, 'size');
299 | process(this._params.interactivity.modes.bubble.opacity, this._params.particles.opacity.value, particle.opacity_bubble, particle.opacity, 'opacity');
300 | }
301 | }
302 | }
303 | }
304 |
305 | private _repulseParticle(particle: Particle) {
306 | if (this._params.interactivity.events.onhover.enable &&
307 | isInArray('repulse', this._params.interactivity.events.onhover.mode) &&
308 | this._params.interactivity.status == 'mousemove') {
309 |
310 | let dx_mouse: number = particle.x - this._params.interactivity.mouse.pos_x;
311 | let dy_mouse: number = particle.y - this._params.interactivity.mouse.pos_y;
312 | let dist_mouse: number = Math.sqrt(dx_mouse * dx_mouse + dy_mouse * dy_mouse);
313 |
314 | let normVec: any = { x: dx_mouse / dist_mouse, y: dy_mouse / dist_mouse };
315 | let repulseRadius: number = this._params.interactivity.modes.repulse.distance;
316 | let velocity: number = 100;
317 | let repulseFactor: number = clamp((1 / repulseRadius) * (-1 * Math.pow(dist_mouse / repulseRadius, 2) + 1) * repulseRadius * velocity, 0, 50);
318 |
319 | let pos = {
320 | x: particle.x + normVec.x * repulseFactor,
321 | y: particle.y + normVec.y * repulseFactor
322 | }
323 |
324 | if (this._params.particles.move.out_mode == 'bounce') {
325 | if (pos.x - particle.radius > 0 && pos.x + particle.radius < this._canvasParams.width)
326 | particle.x = pos.x;
327 | if (pos.y - particle.radius > 0 && pos.y + particle.radius < this._canvasParams.height)
328 | particle.y = pos.y;
329 | } else {
330 | particle.x = pos.x;
331 | particle.y = pos.y;
332 | }
333 |
334 | } else if (this._params.interactivity.events.onclick.enable &&
335 | isInArray('repulse', this._params.interactivity.events.onclick.mode)) {
336 |
337 | if (!this._tmpParams.repulse_finish) {
338 | this._tmpParams.repulse_count++;
339 | if (this._tmpParams.repulse_count == this._params.particles.array.length)
340 | this._tmpParams.repulse_finish = true;
341 | }
342 |
343 | if (this._tmpParams.repulse_clicking) {
344 |
345 | let repulseRadius: number = Math.pow(this._params.interactivity.modes.repulse.distance / 6, 3);
346 |
347 | let dx: number = this._params.interactivity.mouse.click_pos_x - particle.x;
348 | let dy: number = this._params.interactivity.mouse.click_pos_y - particle.y;
349 | let d: number = dx * dx + dy * dy;
350 |
351 | let force: number = -repulseRadius / d * 1;
352 |
353 | let process: () => void =
354 | () => {
355 | let f: number = Math.atan2(dy, dx);
356 | particle.vx = force * Math.cos(f);
357 | particle.vy = force * Math.sin(f);
358 | if (this._params.particles.move.out_mode == 'bounce') {
359 | let pos: {
360 | x: number;
361 | y: number;
362 | } = {
363 | x: particle.x + particle.vx,
364 | y: particle.y + particle.vy
365 | }
366 | if (pos.x + particle.radius > this._canvasParams.width)
367 | particle.vx = -particle.vx;
368 | else if (pos.x - particle.radius < 0)
369 | particle.vx = -particle.vx;
370 | if (pos.y + particle.radius > this._canvasParams.height)
371 | particle.vy = -particle.vy;
372 | else if (pos.y - particle.radius < 0)
373 | particle.vy = -particle.vy;
374 | }
375 | };
376 |
377 | if (d <= repulseRadius) {
378 | process();
379 | }
380 | } else {
381 | if (this._tmpParams.repulse_clicking == false) {
382 | particle.vx = particle.vx_i;
383 | particle.vy = particle.vy_i;
384 | }
385 | }
386 | }
387 | }
388 |
389 | private _grabParticle(particle: Particle): void {
390 | let { interactivity, particles } = this._params;
391 |
392 | if (interactivity.events.onhover.enable &&
393 | interactivity.status == 'mousemove') {
394 |
395 | let dx_mouse: number = particle.x - interactivity.mouse.pos_x;
396 | let dy_mouse: number = particle.y - interactivity.mouse.pos_y;
397 | let dist_mouse: number = Math.sqrt(dx_mouse * dx_mouse + dy_mouse * dy_mouse);
398 |
399 | if (dist_mouse <= interactivity.modes.grab.distance) {
400 |
401 | let { grab } = interactivity.modes;
402 |
403 | let opacity_line: number = grab.line_linked.opacity - (dist_mouse / (1 / grab.line_linked.opacity)) / grab.distance;
404 |
405 | if (opacity_line > 0) {
406 | let color_line: {
407 | r: number;
408 | g: number;
409 | b: number;
410 | } = particles.line_linked.color_rgb_line;
411 |
412 | let { r, g, b } = color_line;
413 | this._canvasParams.ctx.strokeStyle = `rgba( ${r}, ${g}, ${b}, ${opacity_line} )`;
414 | this._canvasParams.ctx.lineWidth = particles.line_linked.width;
415 |
416 | this._canvasParams.ctx.beginPath();
417 | this._canvasParams.ctx.moveTo(particle.x, particle.y);
418 | this._canvasParams.ctx.lineTo(interactivity.mouse.pos_x, interactivity.mouse.pos_y);
419 | this._canvasParams.ctx.stroke();
420 | this._canvasParams.ctx.closePath();
421 | }
422 | }
423 | }
424 | }
425 | }
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export * from './interfaces';
2 | export * from './utils';
3 | export { CanvasManager } from './CanvasManager';
4 | export { ParticlesManager } from './ParticlesManager';
5 | export { Particle } from './Particle';
6 | export { ParticleInteraction } from './ParticleInteraction';
--------------------------------------------------------------------------------
/src/lib/interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface ICanvasParams {
2 | el: HTMLCanvasElement;
3 | width: number;
4 | height: number;
5 | pxratio?: number;
6 | ctx?: CanvasRenderingContext2D;
7 | }
8 |
9 | export interface ITmpParams {
10 | obj?: {
11 | size_value: number;
12 | size_anim_speed: number;
13 | move_speed: number;
14 | line_linked_distance: number;
15 | line_linked_width: number;
16 | mode_grab_distance: number;
17 | mode_bubble_distance: number;
18 | mode_bubble_size: number;
19 | mode_repulse_distance: number;
20 | };
21 | retina?: boolean;
22 | img_type?: string;
23 | img_obj?: any;
24 | img_error?: any;
25 | source_svg?: any;
26 | count_svg?: number;
27 | pushing?: any;
28 | bubble_clicking?: boolean;
29 | bubble_duration_end?: boolean;
30 | repulse_clicking?: boolean;
31 | repulse_finish?: boolean;
32 | repulse_count?: number;
33 | checkAnimFrame?: any;
34 | drawAnimFrame?: any;
35 | };
36 |
37 | export interface IMouseParams {
38 | pos_x?: number;
39 | pos_y?: number;
40 | click_pos_x?: number;
41 | click_pos_y?: number;
42 | click_time?: number;
43 | }
44 |
45 | export interface IParams {
46 | particles: {
47 | number: {
48 | value: number;
49 | density: {
50 | enable: boolean;
51 | value_area: number;
52 | }
53 | };
54 | color: {
55 | value: any;
56 | };
57 | shape: {
58 | type: string | string[];
59 | stroke: {
60 | width: number;
61 | color: any;
62 | },
63 | polygon: {
64 | nb_sides: number;
65 | },
66 | image: {
67 | src: string;
68 | width: number;
69 | height: number;
70 | }
71 | };
72 | opacity: {
73 | value: number;
74 | random: boolean;
75 | anim: {
76 | enable: boolean;
77 | speed: number;
78 | opacity_min: number;
79 | sync: boolean;
80 | }
81 | };
82 | size: {
83 | value: number;
84 | random: boolean;
85 | anim: {
86 | enable: boolean;
87 | speed: number;
88 | size_min: number;
89 | sync: boolean;
90 | }
91 | };
92 | line_linked: {
93 | enable: boolean;
94 | distance: number;
95 | color: any;
96 | opacity: number;
97 | width: number;
98 | color_rgb_line?: any;
99 | shadow: {
100 | enable: boolean;
101 | blur: number;
102 | color: string;
103 | };
104 | };
105 | move: {
106 | enable: boolean;
107 | speed: number;
108 | direction: string;
109 | random: boolean;
110 | straight: boolean;
111 | out_mode: string;
112 | bounce: boolean;
113 | attract: {
114 | enable: boolean;
115 | rotateX: number;
116 | rotateY: number;
117 | }
118 | };
119 | array: any[];
120 | };
121 | interactivity: {
122 | el?: EventTarget;
123 | status?: string;
124 | detect_on: string;
125 | events: {
126 | onhover: {
127 | enable: boolean;
128 | mode: string | string[];
129 | };
130 | onclick: {
131 | enable: boolean;
132 | mode: string | string[];
133 | };
134 | resize: boolean;
135 | };
136 | modes: {
137 | grab: {
138 | distance: number;
139 | line_linked: {
140 | opacity: number;
141 | }
142 | };
143 | bubble: {
144 | distance: number;
145 | size: number;
146 | duration: number;
147 | opacity?: number;
148 | };
149 | repulse: {
150 | distance: number;
151 | duration: number;
152 | };
153 | push: {
154 | particles_nb: number;
155 | };
156 | remove: {
157 | particles_nb: number;
158 | };
159 | };
160 | mouse?: IMouseParams;
161 | };
162 | retina_detect: boolean;
163 | }
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { IParams, ITmpParams, Particle } from './index';
2 |
3 | export type RGB = {
4 | r: number;
5 | g: number;
6 | b: number;
7 | };
8 |
9 | export type HSL = {
10 | h: number;
11 | s: number;
12 | l: number;
13 | };
14 |
15 | export const hexToRgb: (hex: string) => RGB =
16 | (hex) => {
17 | let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
18 | hex = hex.replace(shorthandRegex, (m, r, g, b) => {
19 | return r + r + g + g + b + b;
20 | });
21 | let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
22 | return result ? {
23 | r: parseInt(result[1], 16),
24 | g: parseInt(result[2], 16),
25 | b: parseInt(result[3], 16)
26 | } : null;
27 | };
28 |
29 | export const clamp: (number: number, min: number, max: number) => number =
30 | (number, min, max) => {
31 | return Math.min(Math.max(number, min), max);
32 | };
33 |
34 | export const isInArray: (value: any, array: any) => boolean =
35 | (value, array) => {
36 | return array.indexOf(value) > -1;
37 | };
38 |
39 | export const deepExtend: (destination: any, source: any) => any =
40 | function (destination, source) {
41 | for (let property in source) {
42 | if (source[property] &&
43 | source[property].constructor &&
44 | source[property].constructor === Object) {
45 | destination[property] = destination[property] || {};
46 | deepExtend(destination[property], source[property]);
47 | } else {
48 | destination[property] = source[property];
49 | }
50 | }
51 | return destination;
52 | };
53 |
54 | export const getColor: (colorObject: any) => { rgb?: RGB, hsl?: HSL } =
55 | (colorObject) => {
56 | let color: { rgb?: RGB, hsl?: HSL } = {};
57 | if (typeof (colorObject) == 'object') {
58 | if (colorObject instanceof Array) {
59 | let selectedColor: string = colorObject[Math.floor(Math.random() * colorObject.length)];
60 | color.rgb = hexToRgb(selectedColor);
61 | } else {
62 | let { r, g, b } = colorObject;
63 | if (r !== undefined && g !== undefined && b !== undefined) {
64 | color.rgb = { r, g, b };
65 | } else {
66 | let { h, s, l } = colorObject;
67 | if (h !== undefined && g !== undefined && b !== undefined) {
68 | color.hsl = { h, s, l };
69 | }
70 | }
71 | }
72 | } else if (colorObject == 'random') {
73 | color.rgb = {
74 | r: (Math.floor(Math.random() * 255) + 1),
75 | g: (Math.floor(Math.random() * 255) + 1),
76 | b: (Math.floor(Math.random() * 255) + 1)
77 | }
78 | } else if (typeof (colorObject) == 'string') {
79 | color.rgb = hexToRgb(colorObject);
80 | }
81 | return color;
82 | };
83 |
84 | export const getDefaultParams: () => IParams =
85 | () => {
86 | return {
87 | particles: {
88 | number: {
89 | value: 100,
90 | density: {
91 | enable: true,
92 | value_area: 800
93 | }
94 | },
95 | color: {
96 | value: '#FFF'
97 | },
98 | shape: {
99 | type: 'circle',
100 | stroke: {
101 | width: 0,
102 | color: '#000000'
103 | },
104 | polygon: {
105 | nb_sides: 5
106 | },
107 | image: {
108 | src: '',
109 | width: 100,
110 | height: 100
111 | }
112 | },
113 | opacity: {
114 | value: 0.5,
115 | random: false,
116 | anim: {
117 | enable: true,
118 | speed: 1,
119 | opacity_min: 0.1,
120 | sync: false
121 | }
122 | },
123 | size: {
124 | value: 3,
125 | random: true,
126 | anim: {
127 | enable: false,
128 | speed: 40,
129 | size_min: 0,
130 | sync: false
131 | }
132 | },
133 | line_linked: {
134 | enable: true,
135 | distance: 150,
136 | color: '#FFF',
137 | opacity: 0.6,
138 | width: 1,
139 | shadow: {
140 | enable: false,
141 | blur: 5,
142 | color: 'lime'
143 | }
144 | },
145 | move: {
146 | enable: true,
147 | speed: 3,
148 | direction: 'none',
149 | random: false,
150 | straight: false,
151 | out_mode: 'out',
152 | bounce: true,
153 | attract: {
154 | enable: false,
155 | rotateX: 3000,
156 | rotateY: 3000
157 | }
158 | },
159 | array: []
160 | },
161 | interactivity: {
162 | detect_on: 'canvas',
163 | events: {
164 | onhover: {
165 | enable: true,
166 | mode: 'grab'
167 | },
168 | onclick: {
169 | enable: true,
170 | mode: 'push'
171 | },
172 | resize: true
173 | },
174 | modes: {
175 | grab: {
176 | distance: 200,
177 | line_linked: {
178 | opacity: 1
179 | }
180 | },
181 | bubble: {
182 | distance: 200,
183 | size: 80,
184 | duration: 0.4
185 | },
186 | repulse: {
187 | distance: 200,
188 | duration: 0.4
189 | },
190 | push: {
191 | particles_nb: 4
192 | },
193 | remove: {
194 | particles_nb: 2
195 | }
196 | },
197 | mouse: {}
198 | },
199 | retina_detect: true
200 | }
201 | };
202 |
203 |
204 | export function loadImg(params: IParams, tmp: ITmpParams) {
205 | let { particles } = params;
206 |
207 | tmp.img_error = undefined;
208 |
209 | if (particles.shape.type == 'image' && particles.shape.image.src != '') {
210 | if (tmp.img_type == 'svg') {
211 | let xhr: XMLHttpRequest = new XMLHttpRequest();
212 | xhr.open('GET', particles.shape.image.src);
213 | xhr.onreadystatechange = (data: any) => {
214 | if (xhr.readyState == 4) {
215 | if (xhr.status == 200) {
216 | tmp.source_svg = data.currentTarget.response;
217 | if (tmp.source_svg == undefined) {
218 | let check: any;
219 | tmp.checkAnimFrame = requestAnimationFrame(check);
220 | }
221 | } else {
222 | tmp.img_error = true;
223 | throw "Error : image not found";
224 | }
225 | }
226 | };
227 | xhr.send();
228 | } else {
229 | let img: HTMLImageElement = new Image();
230 | img.addEventListener('load', () => {
231 | tmp.img_obj = img;
232 | cancelAnimationFrame(tmp.checkAnimFrame);
233 | });
234 | img.src = particles.shape.image.src;
235 | }
236 | } else {
237 | tmp.img_error = true;
238 | throw "Error : no image.src";
239 | }
240 | }
241 |
242 | export function createSvgImg(particle: Particle, tmp: ITmpParams): void {
243 | let svgXml: string = tmp.source_svg;
244 | let rgbHex: RegExp = /#([0-9A-F]{3,6})/gi;
245 | let coloredSvgXml: string = svgXml.replace(rgbHex, (m, r, g, b) => {
246 | let color_value: string;
247 | if (particle.color.rgb) {
248 | let { r, g, b } = particle.color.rgb;
249 | color_value = `rgba( ${r}, ${g}, ${b}, ${particle.opacity} )`;
250 | } else {
251 | let { h, s, l } = particle.color.hsl;
252 | color_value = `rgba( ${h}, ${s}, ${l}, ${particle.opacity} )`;
253 | }
254 | return color_value;
255 | });
256 |
257 | let svg: Blob = new Blob([coloredSvgXml], {
258 | type: 'image/svg+xml;charset=utf-8'
259 | });
260 |
261 | let DOMURL: any = window.URL || window;
262 | let url: any = DOMURL.createObjectURL(svg);
263 |
264 | let img = new Image();
265 | img.addEventListener('load', () => {
266 | particle.img.obj = img;
267 | particle.img.loaded = true;
268 | DOMURL.revokeObjectURL(url);
269 | tmp.count_svg++;
270 | });
271 | img.src = url;
272 | }
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-particle",
3 | "version": "1.0.4",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/ryuKKu-/angular-particle"
7 | },
8 | "author": {
9 | "name": "Luc Raymond",
10 | "email": "ryukku.raymond@gmail.com"
11 | },
12 | "keywords": [
13 | "angular2",
14 | "angular4",
15 | "angular",
16 | "particlesjs",
17 | "particle",
18 | "typescript",
19 | "ng2",
20 | "particles"
21 | ],
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/ryuKKu-/angular-particle/issues"
25 | },
26 | "module": "angular-particle.js",
27 | "typings": "angular-particle.d.ts",
28 | "peerDependencies": {
29 | "@angular/core": "^4.0.0",
30 | "rxjs": "^5.1.0",
31 | "zone.js": "^0.8.4"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/particles.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 | import { IParams } from './lib/index';
3 |
4 | @Component({
5 | selector: 'particles',
6 | template: `
7 |
8 |
9 |
10 | `
11 | })
12 | export class ParticlesComponent {
13 |
14 | @Input() width: number = 100;
15 | @Input() height: number = 100;
16 | @Input() params: IParams;
17 | @Input() style: Object = {};
18 |
19 | constructor() { }
20 | }
--------------------------------------------------------------------------------
/src/particles.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, AfterViewInit, HostListener, Input, OnDestroy } from "@angular/core";
2 | import { CanvasManager, ICanvasParams, IParams, ITmpParams, getDefaultParams, isInArray, deepExtend, loadImg } from './lib/index';
3 |
4 | @Directive({
5 | selector: '[d-particles]'
6 | })
7 | export class ParticlesDirective implements AfterViewInit, OnDestroy {
8 | @Input() set params(value: IParams) {
9 | let defaultParams: IParams = getDefaultParams();
10 | this._params = deepExtend(defaultParams, value);
11 | }
12 |
13 | constructor(private el: ElementRef) { }
14 |
15 | private _canvasParams: ICanvasParams;
16 | private _params: IParams;
17 | private _tmpParams: ITmpParams = {};
18 | private _canvasManager: CanvasManager;
19 |
20 | ngOnDestroy(): void {
21 | if (!this._canvasManager) {
22 | return;
23 | }
24 | this._canvasManager.cancelAnimation();
25 | }
26 |
27 | ngAfterViewInit(): void {
28 | this._canvasParams = {
29 | el: this.el.nativeElement,
30 | ctx: this.el.nativeElement.getContext('2d'),
31 | width: this.el.nativeElement.offsetWidth,
32 | height: this.el.nativeElement.offsetHeight
33 | };
34 |
35 | this._tmpParams.obj = {
36 | size_value: this._params.particles.size.value,
37 | size_anim_speed: this._params.particles.size.anim.speed,
38 | move_speed: this._params.particles.move.speed,
39 | line_linked_distance: this._params.particles.line_linked.distance,
40 | line_linked_width: this._params.particles.line_linked.width,
41 | mode_grab_distance: this._params.interactivity.modes.grab.distance,
42 | mode_bubble_distance: this._params.interactivity.modes.bubble.distance,
43 | mode_bubble_size: this._params.interactivity.modes.bubble.size,
44 | mode_repulse_distance: this._params.interactivity.modes.repulse.distance
45 | };
46 |
47 | this._params.interactivity.el = (this._params.interactivity.detect_on == 'window') ? window : this._canvasParams.el;
48 |
49 | if (isInArray('image', this._params.particles.shape.type)) {
50 | this._tmpParams.img_type = this._params.particles.shape.image.src.substr(this._params.particles.shape.image.src.length - 3);
51 | loadImg(this._params, this._tmpParams);
52 | }
53 |
54 | this._canvasManager = new CanvasManager(this._canvasParams, this._params, this._tmpParams);
55 | this._canvasManager.draw();
56 | }
57 |
58 | /**
59 | * Mouse move event
60 | * @param event
61 | */
62 | @HostListener('mousemove', ['$event']) onMouseMove(event) {
63 | let { interactivity } = this._params;
64 |
65 | if (interactivity.events.onhover.enable ||
66 | interactivity.events.onclick.enable) {
67 |
68 | let pos: {
69 | x: number;
70 | y: number;
71 | };
72 |
73 | if (interactivity.el == window) {
74 | pos = {
75 | x: event.clientX,
76 | y: event.clientY
77 | };
78 | } else {
79 | pos = {
80 | x: event.offsetX || event.clientX,
81 | y: event.offsetY || event.clientY
82 | };
83 | }
84 |
85 | interactivity.mouse.pos_x = pos.x;
86 | interactivity.mouse.pos_y = pos.y;
87 |
88 | if (this._tmpParams.retina) {
89 | interactivity.mouse.pos_x *= this._canvasParams.pxratio;
90 | interactivity.mouse.pos_y *= this._canvasParams.pxratio;
91 | }
92 |
93 | interactivity.status = 'mousemove';
94 | }
95 | }
96 |
97 | /**
98 | * Mouse leave event
99 | */
100 | @HostListener('mouseleave') onMouseLeave() {
101 | let { interactivity } = this._params;
102 |
103 | if (interactivity.events.onhover.enable ||
104 | interactivity.events.onclick.enable) {
105 |
106 | interactivity.mouse.pos_x = null;
107 | interactivity.mouse.pos_y = null;
108 | interactivity.status = 'mouseleave';
109 | }
110 | }
111 |
112 | /**
113 | * Click event
114 | */
115 | @HostListener('click') onClick() {
116 | let { interactivity, particles } = this._params;
117 |
118 | if (interactivity.events.onclick.enable) {
119 | interactivity.mouse.click_pos_x = interactivity.mouse.pos_x;
120 | interactivity.mouse.click_pos_y = interactivity.mouse.pos_y;
121 | interactivity.mouse.click_time = new Date().getTime();
122 |
123 | switch (interactivity.events.onclick.mode) {
124 | case 'push':
125 | if (particles.move.enable) {
126 | this._canvasManager.particlesManager.pushParticles(interactivity.modes.push.particles_nb, interactivity.mouse);
127 | } else {
128 | if (interactivity.modes.push.particles_nb == 1) {
129 | this._canvasManager.particlesManager.pushParticles(interactivity.modes.push.particles_nb, interactivity.mouse);
130 | } else if (interactivity.modes.push.particles_nb > 1) {
131 | this._canvasManager.particlesManager.pushParticles(interactivity.modes.push.particles_nb);
132 | }
133 | }
134 | break;
135 |
136 | case 'remove':
137 | this._canvasManager.particlesManager.removeParticles(interactivity.modes.remove.particles_nb);
138 | break;
139 |
140 | case 'bubble':
141 | this._tmpParams.bubble_clicking = true;
142 | break;
143 |
144 | case 'repulse':
145 | this._tmpParams.repulse_clicking = true;
146 | this._tmpParams.repulse_count = 0;
147 | this._tmpParams.repulse_finish = false;
148 | setTimeout(() => {
149 | this._tmpParams.repulse_clicking = false;
150 | }, interactivity.modes.repulse.duration * 1000);
151 | break;
152 | }
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "module": "es2015",
5 | "target": "es5",
6 | "baseUrl": ".",
7 | "stripInternal": true,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "moduleResolution": "node",
11 | "outDir": "../build",
12 | "rootDir": ".",
13 | "lib": [
14 | "es2015",
15 | "dom"
16 | ],
17 | "skipLibCheck": true,
18 | "types": []
19 | },
20 | "angularCompilerOptions": {
21 | "annotateForClosureCompiler": true,
22 | "strictMetadataEmit": true,
23 | "skipTemplateCodegen": true,
24 | "flatModuleOutFile": "angular-particle.js",
25 | "flatModuleId": "angular-particle"
26 | },
27 | "files": [
28 | "./index.ts"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/tools/gulp/inline-resources.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // https://github.com/filipesilva/angular-quickstart-lib/blob/master/inline-resources.js
3 | 'use strict';
4 |
5 | const fs = require('fs');
6 | const path = require('path');
7 | const glob = require('glob');
8 | const sass = require('node-sass');
9 | const tildeImporter = require('node-sass-tilde-importer');
10 |
11 | /**
12 | * Simple Promiseify function that takes a Node API and return a version that supports promises.
13 | * We use promises instead of synchronized functions to make the process less I/O bound and
14 | * faster. It also simplifies the code.
15 | */
16 | function promiseify(fn) {
17 | return function () {
18 | const args = [].slice.call(arguments, 0);
19 | return new Promise((resolve, reject) => {
20 | fn.apply(this, args.concat([function (err, value) {
21 | if (err) {
22 | reject(err);
23 | } else {
24 | resolve(value);
25 | }
26 | }]));
27 | });
28 | };
29 | }
30 |
31 | const readFile = promiseify(fs.readFile);
32 | const writeFile = promiseify(fs.writeFile);
33 |
34 | /**
35 | * Inline resources in a tsc/ngc compilation.
36 | * @param projectPath {string} Path to the project.
37 | */
38 | function inlineResources(projectPath) {
39 |
40 | // Match only TypeScript files in projectPath.
41 | const files = glob.sync('**/*.ts', {cwd: projectPath});
42 |
43 | // For each file, inline the templates and styles under it and write the new file.
44 | return Promise.all(files.map(filePath => {
45 | const fullFilePath = path.join(projectPath, filePath);
46 | return readFile(fullFilePath, 'utf-8')
47 | .then(content => inlineResourcesFromString(content, url => {
48 | // Resolve the template url.
49 | return path.join(path.dirname(fullFilePath), url);
50 | }))
51 | .then(content => writeFile(fullFilePath, content))
52 | .catch(err => {
53 | console.error('An error occured: ', err);
54 | });
55 | }));
56 | }
57 |
58 | /**
59 | * Inline resources from a string content.
60 | * @param content {string} The source file's content.
61 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
62 | * @returns {string} The content with resources inlined.
63 | */
64 | function inlineResourcesFromString(content, urlResolver) {
65 | // Curry through the inlining functions.
66 | return [
67 | inlineTemplate,
68 | inlineStyle,
69 | removeModuleId
70 | ].reduce((content, fn) => fn(content, urlResolver), content);
71 | }
72 |
73 | /**
74 | * Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and
75 | * replace with `template: ...` (with the content of the file included).
76 | * @param content {string} The source file's content.
77 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
78 | * @return {string} The content with all templates inlined.
79 | */
80 | function inlineTemplate(content, urlResolver) {
81 | return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) {
82 | const templateFile = urlResolver(templateUrl);
83 | const templateContent = fs.readFileSync(templateFile, 'utf-8');
84 | const shortenedTemplate = templateContent
85 | .replace(/([\n\r]\s*)+/gm, ' ')
86 | .replace(/"/g, '\\"');
87 | return `template: "${shortenedTemplate}"`;
88 | });
89 | }
90 |
91 |
92 | /**
93 | * Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and
94 | * replace with `styles: [...]` (with the content of the file included).
95 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
96 | * @param content {string} The source file's content.
97 | * @return {string} The content with all styles inlined.
98 | */
99 | function inlineStyle(content, urlResolver) {
100 | return content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) {
101 | const urls = eval(styleUrls);
102 | return 'styles: ['
103 | + urls.map(styleUrl => {
104 | const styleFile = urlResolver(styleUrl);
105 | const originContent = fs.readFileSync(styleFile, 'utf-8');
106 | const styleContent = styleFile.endsWith('.scss') ? buildSass(originContent, styleFile) : originContent;
107 | const shortenedStyle = styleContent
108 | .replace(/([\n\r]\s*)+/gm, ' ')
109 | .replace(/"/g, '\\"');
110 | return `"${shortenedStyle}"`;
111 | })
112 | .join(',\n')
113 | + ']';
114 | });
115 | }
116 |
117 | /**
118 | * build sass content to css
119 | * @param content {string} the css content
120 | * @param sourceFile {string} the scss file sourceFile
121 | * @return {string} the generated css, empty string if error occured
122 | */
123 | function buildSass(content, sourceFile) {
124 | try {
125 | const result = sass.renderSync({
126 | data: content,
127 | file: sourceFile,
128 | importer: tildeImporter
129 | });
130 | return result.css.toString()
131 | } catch (e) {
132 | console.error('\x1b[41m');
133 | console.error('at ' + sourceFile + ':' + e.line + ":" + e.column);
134 | console.error(e.formatted);
135 | console.error('\x1b[0m');
136 | return "";
137 | }
138 | }
139 |
140 | /**
141 | * Remove every mention of `moduleId: module.id`.
142 | * @param content {string} The source file's content.
143 | * @returns {string} The content with all moduleId: mentions removed.
144 | */
145 | function removeModuleId(content) {
146 | return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, '');
147 | }
148 |
149 | module.exports = inlineResources;
150 | module.exports.inlineResourcesFromString = inlineResourcesFromString;
151 |
152 | // Run inlineResources if module is being called directly from the CLI with arguments.
153 | if (require.main === module && process.argv.length > 2) {
154 | console.log('Inlining resources from project:', process.argv[2]);
155 | return inlineResources(process.argv[2]);
156 | }
157 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./src",
4 | "experimentalDecorators": true,
5 | "moduleResolution": "node",
6 | "rootDir": "./src",
7 | "lib": [
8 | "es2015",
9 | "dom"
10 | ],
11 | "skipLibCheck": true,
12 | "types": []
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "class-name": true,
7 | "comment-format": [
8 | true,
9 | "check-space"
10 | ],
11 | "curly": true,
12 | "eofline": true,
13 | "forin": true,
14 | "indent": [
15 | true,
16 | "spaces"
17 | ],
18 | "label-position": true,
19 | "max-line-length": [
20 | true,
21 | 140
22 | ],
23 | "member-access": false,
24 | "member-ordering": [
25 | true,
26 | "static-before-instance",
27 | "variables-before-functions"
28 | ],
29 | "no-arg": true,
30 | "no-bitwise": true,
31 | "no-console": [
32 | true,
33 | "debug",
34 | "info",
35 | "time",
36 | "timeEnd",
37 | "trace"
38 | ],
39 | "no-construct": true,
40 | "no-debugger": true,
41 | "no-duplicate-variable": true,
42 | "no-empty": false,
43 | "no-eval": true,
44 | "no-inferrable-types": true,
45 | "no-shadowed-variable": true,
46 | "no-string-literal": false,
47 | "no-switch-case-fall-through": true,
48 | "no-trailing-whitespace": true,
49 | "no-unused-expression": true,
50 | "no-unused-variable": true,
51 | "no-use-before-declare": true,
52 | "no-var-keyword": true,
53 | "object-literal-sort-keys": false,
54 | "one-line": [
55 | true,
56 | "check-open-brace",
57 | "check-catch",
58 | "check-else",
59 | "check-whitespace"
60 | ],
61 | "quotemark": [
62 | true,
63 | "single"
64 | ],
65 | "radix": true,
66 | "semicolon": [
67 | "always"
68 | ],
69 | "triple-equals": [
70 | true,
71 | "allow-null-check"
72 | ],
73 | "typedef-whitespace": [
74 | true,
75 | {
76 | "call-signature": "nospace",
77 | "index-signature": "nospace",
78 | "parameter": "nospace",
79 | "property-declaration": "nospace",
80 | "variable-declaration": "nospace"
81 | }
82 | ],
83 | "variable-name": false,
84 | "whitespace": [
85 | true,
86 | "check-branch",
87 | "check-decl",
88 | "check-operator",
89 | "check-separator",
90 | "check-type"
91 | ],
92 | "directive-selector": [true, "attribute", "", "camelCase"],
93 | "component-selector": [true, "element", "", "kebab-case"],
94 | "use-input-property-decorator": true,
95 | "use-output-property-decorator": true,
96 | "use-host-property-decorator": true,
97 | "no-input-rename": true,
98 | "no-output-rename": true,
99 | "use-life-cycle-interface": true,
100 | "use-pipe-transform-interface": true,
101 | "component-class-suffix": true,
102 | "directive-class-suffix": true
103 | }
104 | }
105 |
--------------------------------------------------------------------------------