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": "",