diff --git a/core/editor/src/Sections/Options/OptionsEditor.ts b/core/editor/src/Sections/Options/OptionsEditor.ts index 512a6487be7..c33373e8b5e 100644 --- a/core/editor/src/Sections/Options/OptionsEditor.ts +++ b/core/editor/src/Sections/Options/OptionsEditor.ts @@ -50,6 +50,12 @@ export class OptionsEditor extends EditorBase { this.group.addProperty("pauseOnBlur", "Pause on Blur", EditorType.boolean).change(async () => { await particles.refresh(); }); + + this.group + .addProperty("pauseOnOutsideViewport", "Pause on Outside Viewport", EditorType.boolean) + .change(async () => { + await particles.refresh(); + }); } private addBackground(): void { diff --git a/core/main/markdown/Options.md b/core/main/markdown/Options.md index efca134b548..f358a5cdc44 100644 --- a/core/main/markdown/Options.md +++ b/core/main/markdown/Options.md @@ -1,28 +1,29 @@ # **_Options_** -| property | option type | example | notes | -| ----------------- | ------------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `autoPlay` | `boolean` | `true` / `false` | | -| `background` | `object` | | See Background options {@link IBackground | here} | -| `backgroundMask` | `object` | | See Background Mask options {@link IBackgroundMask | here} | -| `backgroundMode` | `object` | | See Background Mode options {@link IBackgroundMode | here} | -| `detectRetina` | `boolean` | `true` / `false` | Replaces the old `retina_detect` property | -| `fpsLimit` | `number` | `30` | _Defaults to `30`_, replaces the old `fps_limit` property | -| `infection` | `object` | | See Infection options {@link IInfection | here} | -| `interactivity` | `object` | | See Interactivity options {@link IInteractivity | here} | -| `manualParticles` | `array` | | An array of Manual Particles object. See Manual Particles documentation {@link IManualParticle | here} | -| `motion` | `object` | | See Motion options {@link IMotion | here} | -| `particles` | `object` | | See Particles options {@link IParticles | here} | -| `pauseOnBlur` | `boolean` | `true` / `false` | Pauses the animations when the page isn't on foreground | -| `preset` | `string` / `array` | `"basic"`
`[ "basic", "60fps" ]` | You can use this property to load one or more presets for focusing on important properties and not all config. You can find presets on `npm` [here](https://www.npmjs.com/search?q=tsparticles-preset) | -| `themes` | `array` | | It's an array of Theme object, you can see the structure {@link ITheme | here } | +| property | option type | example | notes | +| ------------------------ | ------------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `autoPlay` | `boolean` | `true` / `false` | | +| `background` | `object` | | See Background options {@link IBackground | here} | +| `backgroundMask` | `object` | | See Background Mask options {@link IBackgroundMask | here} | +| `backgroundMode` | `object` | | See Background Mode options {@link IBackgroundMode | here} | +| `detectRetina` | `boolean` | `true` / `false` | Replaces the old `retina_detect` property | +| `fpsLimit` | `number` | `30` | _Defaults to `30`_, replaces the old `fps_limit` property | +| `infection` | `object` | | See Infection options {@link IInfection | here} | +| `interactivity` | `object` | | See Interactivity options {@link IInteractivity | here} | +| `manualParticles` | `array` | | An array of Manual Particles object. See Manual Particles documentation {@link IManualParticle | here} | +| `motion` | `object` | | See Motion options {@link IMotion | here} | +| `particles` | `object` | | See Particles options {@link IParticles | here} | +| `pauseOnBlur` | `boolean` | `true` / `false` | Pauses the animations when the page isn't on foreground | +| `pauseOnOutsideViewport` | `boolean` | `true` / `false` | Pauses the animations when the element is out of the viewport | +| `preset` | `string` / `array` | `"basic"`
`[ "basic", "60fps" ]` | You can use this property to load one or more presets for focusing on important properties and not all config. You can find presets on `npm` [here](https://www.npmjs.com/search?q=tsparticles-preset) | +| `themes` | `array` | | It's an array of Theme object, you can see the structure {@link ITheme | here } | ## Plugins These options are not used by slim bundle -| property | option type | example | notes | -| ------------- | ------------------ | ------- | -------------------------------------------------- | -| `absorbers` | `object` / `array` | | See Absorbers options {@link IAbsorber | here} | -| `emitters` | `object` / `array` | | See Emitter options {@link IEmitter | here} | -| `polygonMask` | `object` | | See Particles options {@link IPolygonMask | here} | +| property | option type | example | notes | +| ------------- | ------------------ | ------- | ----------------------------------------- | +| `absorbers` | `object` / `array` | | See Absorbers options {@link IAbsorber | here} | +| `emitters` | `object` / `array` | | See Emitter options {@link IEmitter | here} | +| `polygonMask` | `object` | | See Particles options {@link IPolygonMask | here} | diff --git a/core/main/schema/options.schema.json b/core/main/schema/options.schema.json index 0119b2ff9c8..f001783dc5a 100644 --- a/core/main/schema/options.schema.json +++ b/core/main/schema/options.schema.json @@ -2646,6 +2646,10 @@ "description": "Enables or disabled the animation on window blur", "type": "boolean" }, + "pauseOnOutsideViewport": { + "description": "Enables or disabled the animation on when the element is outside of the viewport", + "type": "boolean" + }, "preset": { "anyOf": [ { diff --git a/core/main/src/Core/Container.ts b/core/main/src/Core/Container.ts index b040b016bf1..846523b5dc6 100644 --- a/core/main/src/Core/Container.ts +++ b/core/main/src/Core/Container.ts @@ -79,6 +79,8 @@ export class Container { private readonly eventListeners; + private readonly intersectionObserver; + /** * This is the core class, create an instance to have a new working particles manager * @constructor @@ -155,6 +157,10 @@ export class Container { /* ---------- tsParticles - start ------------ */ this.eventListeners = new EventListeners(this); + + if (typeof IntersectionObserver !== "undefined" && IntersectionObserver) { + this.intersectionObserver = new window.IntersectionObserver((entries) => this.intersectionManager(entries)); + } } /** @@ -362,6 +368,9 @@ export class Container { this.particles.clear(); this.canvas.clear(); + if (this.interactivity.element instanceof HTMLElement && this.intersectionObserver) + this.intersectionObserver.observe(this.interactivity.element); + for (const [, plugin] of this.plugins) { if (plugin.stop) { plugin.stop(); @@ -402,6 +411,9 @@ export class Container { this.eventListeners.addListeners(); + if (this.interactivity.element instanceof HTMLElement && this.intersectionObserver) + this.intersectionObserver.observe(this.interactivity.element); + for (const [, plugin] of this.plugins) { if (plugin.startAsync !== undefined) { await plugin.startAsync(); @@ -457,4 +469,17 @@ export class Container { this.density = (canvas.width * canvas.height) / (densityOptions.factor * pxRatio * pxRatio * densityOptions.area); } + + private intersectionManager(entries: IntersectionObserverEntry[]) { + if (this.options.pauseOnOutsideViewport) + for (const entry of entries) { + if (entry.target === this.interactivity.element) { + if (entry.isIntersecting) { + this.play(); + } else { + this.pause(); + } + } + } + } } diff --git a/core/main/src/Options/Classes/Options.ts b/core/main/src/Options/Classes/Options.ts index b67414b7415..330cbe63d8a 100644 --- a/core/main/src/Options/Classes/Options.ts +++ b/core/main/src/Options/Classes/Options.ts @@ -61,6 +61,7 @@ export class Options implements IOptions, IOptionLoader { public motion; public particles; public pauseOnBlur; + public pauseOnOutsideViewport; public preset?: string | string[]; public themes: Theme[]; @@ -77,6 +78,7 @@ export class Options implements IOptions, IOptionLoader { this.motion = new Motion(); this.particles = new Particles(); this.pauseOnBlur = true; + this.pauseOnOutsideViewport = true; this.themes = []; } @@ -119,6 +121,10 @@ export class Options implements IOptions, IOptionLoader { this.pauseOnBlur = data.pauseOnBlur; } + if (data.pauseOnOutsideViewport !== undefined) { + this.pauseOnOutsideViewport = data.pauseOnOutsideViewport; + } + this.background.load(data.background); this.backgroundMode.load(data.backgroundMode); this.backgroundMask.load(data.backgroundMask); diff --git a/core/main/src/Options/Interfaces/IOptions.ts b/core/main/src/Options/Interfaces/IOptions.ts index d31add198a9..bc4652dceb2 100644 --- a/core/main/src/Options/Interfaces/IOptions.ts +++ b/core/main/src/Options/Interfaces/IOptions.ts @@ -81,6 +81,11 @@ export interface IOptions { */ pauseOnBlur: boolean; + /** + * Enable or disabled the animation if the element is outside the viewport + */ + pauseOnOutsideViewport: boolean; + /** * This property will be used to add specified presets to the options */ diff --git a/core/main/tests/Options.ts b/core/main/tests/Options.ts index 7705118dbbf..8a20f2cda85 100644 --- a/core/main/tests/Options.ts +++ b/core/main/tests/Options.ts @@ -154,6 +154,9 @@ describe("Options tests", () => { /* pause on blur */ expect(options.pauseOnBlur).to.be.true; + + /** pause on Element Outside Viewport*/ + expect(options.pauseOnOutsideViewport).to.be.true; }); it("check default preset options", () => { diff --git a/demo/jquery/public/presets/fontawesome.json b/demo/jquery/public/presets/fontawesome.json index 42188e8388b..0161d5e4543 100644 --- a/demo/jquery/public/presets/fontawesome.json +++ b/demo/jquery/public/presets/fontawesome.json @@ -134,18 +134,14 @@ "fill": true, "font": "Font Awesome 5 Brands", "style": "", - "value": [ - "\uf179" - ], + "value": ["\uf179"], "weight": "400" }, { "fill": true, "font": "Font Awesome 5 Free", "style": "", - "value": [ - "\uf5d1" - ], + "value": ["\uf5d1"], "weight": "900" } ], @@ -199,6 +195,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "config_demo": { "hide_card": false, "background_color": "#0d47a1", @@ -207,4 +204,4 @@ "background_repeat": "no-repeat", "background_size": "cover" } -} \ No newline at end of file +} diff --git a/demo/jquery/public/presets/trail.json b/demo/jquery/public/presets/trail.json index 2f2da80ef60..d8ac93f337e 100644 --- a/demo/jquery/public/presets/trail.json +++ b/demo/jquery/public/presets/trail.json @@ -111,6 +111,7 @@ }, "retina_detect": true, "pauseOnBlur": false, + "pauseOnOutsideViewport": true, "config_demo": { "hide_card": true, "background_color": "#0d47a1", @@ -119,4 +120,4 @@ "background_repeat": "no-repeat", "background_size": "cover" } -} \ No newline at end of file +} diff --git a/demo/main/public/presets/fontawesome.json b/demo/main/public/presets/fontawesome.json index 0220afac9ab..21707d3115d 100644 --- a/demo/main/public/presets/fontawesome.json +++ b/demo/main/public/presets/fontawesome.json @@ -133,18 +133,14 @@ "fill": true, "font": "Font Awesome 5 Brands", "style": "", - "value": [ - "\uf179" - ], + "value": ["\uf179"], "weight": "400" }, { "fill": true, "font": "Font Awesome 5 Free", "style": "", - "value": [ - "\uf5d1" - ], + "value": ["\uf5d1"], "weight": "900" } ], @@ -198,6 +194,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "background": { "color": "#0d47a1", "image": "", diff --git a/demo/main/public/presets/reactBubbles.json b/demo/main/public/presets/reactBubbles.json index a3330943796..756727256ef 100644 --- a/demo/main/public/presets/reactBubbles.json +++ b/demo/main/public/presets/reactBubbles.json @@ -224,6 +224,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "background": { "color": "#0d47a1", "image": "", diff --git a/demo/main/public/presets/reactMultipleImages.json b/demo/main/public/presets/reactMultipleImages.json index 11ec209b6bd..c216d7849e9 100644 --- a/demo/main/public/presets/reactMultipleImages.json +++ b/demo/main/public/presets/reactMultipleImages.json @@ -176,10 +176,7 @@ "fill": true, "sides": 5 }, - "type": [ - "image", - "circle" - ], + "type": ["image", "circle"], "custom": {} }, "size": { @@ -245,6 +242,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "background": { "color": "#0d47a1", "image": "", diff --git a/demo/main/public/presets/reactNightSky.json b/demo/main/public/presets/reactNightSky.json index 7edc1bb1833..c148b11da65 100644 --- a/demo/main/public/presets/reactNightSky.json +++ b/demo/main/public/presets/reactNightSky.json @@ -224,6 +224,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "background": { "color": "#0d47a1", "image": "", diff --git a/demo/main/public/presets/reactPolygonMask.json b/demo/main/public/presets/reactPolygonMask.json index 4d5f1e75a44..2df71203692 100644 --- a/demo/main/public/presets/reactPolygonMask.json +++ b/demo/main/public/presets/reactPolygonMask.json @@ -224,6 +224,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "background": { "color": "#0d47a1", "image": "", diff --git a/demo/main/public/presets/reactSimple.json b/demo/main/public/presets/reactSimple.json index 05988fec988..baa2d894483 100644 --- a/demo/main/public/presets/reactSimple.json +++ b/demo/main/public/presets/reactSimple.json @@ -224,6 +224,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "background": { "color": "#0d47a1", "image": "", diff --git a/demo/main/public/presets/reactSnow.json b/demo/main/public/presets/reactSnow.json index fcef68cb129..f776e428a80 100644 --- a/demo/main/public/presets/reactSnow.json +++ b/demo/main/public/presets/reactSnow.json @@ -224,6 +224,7 @@ "enable": false }, "pauseOnBlur": true, + "pauseOnOutsideViewport": true, "background": { "color": "#0d47a1", "image": "",