Skip to content

Commit d7c1ef4

Browse files
authored
Merge pull request tsparticles#5892 from tsparticles/v4
Ribbons Website update
2 parents 3440786 + 105abb9 commit d7c1ef4

26 files changed

Lines changed: 3271 additions & 149 deletions

.planning/handovers/MCP_GENERATE_CODE_PLAN.md

Lines changed: 936 additions & 0 deletions
Large diffs are not rendered by default.

engine/src/initEngine.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,16 @@ import { Engine } from "./Core/Engine.js";
55
*/
66
export function initEngine(): Engine {
77
/**
8-
* The exposed tsParticles instance
8+
* Reuse the existing global engine instance if present (e.g. when multiple
9+
* CDN bundle scripts each inline `@tsparticles/engine`). In v5 the global
10+
* singleton will be removed and this guard can be dropped.
911
*/
12+
13+
const existing = globalThis.tsParticles as Engine | undefined;
14+
15+
if (existing?.pluginManager) {
16+
return existing;
17+
}
18+
1019
return new Engine();
1120
}

pnpm-lock.yaml

Lines changed: 309 additions & 100 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

websites/confetti/CHANGELOG.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
77

88
**Note:** Version bump only for package @tsparticles/confetti-website
99

10-
11-
12-
13-
1410
## [4.3.1](https://github.com/tsparticles/tsparticles/compare/v4.3.0...v4.3.1) (2026-07-01)
1511

1612
**Note:** Version bump only for package @tsparticles/confetti-website

websites/confetti/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"homepage": "https://confetti.js.org",
3232
"dependencies": {
3333
"@tsparticles/confetti": "workspace:*",
34+
"@tsparticles/ribbons": "workspace:*",
3435
"ace-builds": "^1.44.0",
3536
"js-beautify": "^1.15.4"
3637
},

websites/confetti/src/main.js

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { confetti } from '@tsparticles/confetti';
2+
import { ribbons } from '@tsparticles/ribbons';
23
import './style.css';
34
import './cookie-consent.js';
45
import ace from 'ace-builds';
@@ -16,9 +17,20 @@ ace.config.setModuleUrl('ace/mode/html_worker', htmlWorkerUrl);
1617
ace.config.setModuleUrl('ace/mode/css_worker', cssWorkerUrl);
1718

1819
window.confetti = confetti;
20+
window.ribbons = ribbons;
1921

2022
const editors = [];
2123

24+
let activeIntervals = [];
25+
let activeTimeouts = [];
26+
27+
function cleanupActiveEffects() {
28+
activeIntervals.forEach(clearInterval);
29+
activeIntervals = [];
30+
activeTimeouts.forEach(clearTimeout);
31+
activeTimeouts = [];
32+
}
33+
2234
const sharePlatformTemplates = {
2335
facebook: (url) => `https://www.facebook.com/sharer/sharer.php?u=${url}`,
2436
x: (url, text) => `https://x.com/intent/tweet?url=${url}&text=${text}`,
@@ -568,6 +580,8 @@ const modes = [
568580
},
569581
],
570582
fn: function () {
583+
cleanupActiveEffects();
584+
571585
const duration = 15 * 1000,
572586
animationEnd = Date.now() + duration,
573587
defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
@@ -598,6 +612,8 @@ const modes = [
598612
})
599613
);
600614
}, 250);
615+
616+
activeIntervals.push(interval);
601617
},
602618
},
603619

@@ -768,6 +784,65 @@ const modes = [
768784
})();
769785
},
770786
},
787+
788+
{
789+
id: 'confetti-ribbons',
790+
name: 'Confetti + Ribbons',
791+
description: [
792+
{
793+
cssClass: '',
794+
text: 'Combine confetti with ribbons for a double celebration effect. Confetti rains from above while ribbons flow across the screen — perfect for product launches, milestones, and holiday greetings.',
795+
},
796+
{
797+
cssClass: 'center',
798+
text: 'Double the celebration!',
799+
},
800+
],
801+
fn: function () {
802+
cleanupActiveEffects();
803+
804+
const duration = 6000;
805+
const animationEnd = Date.now() + duration;
806+
807+
const confettiInterval = setInterval(function () {
808+
if (Date.now() >= animationEnd) {
809+
return clearInterval(confettiInterval);
810+
}
811+
812+
confetti({
813+
particleCount: 8,
814+
angle: 90,
815+
spread: 70,
816+
origin: { x: Math.random(), y: 0 },
817+
gravity: 1.2,
818+
ticks: 0,
819+
colors: ['#FFD700', '#FF69B4', '#00CED1', '#FF4500'],
820+
});
821+
}, 50);
822+
823+
activeIntervals.push(confettiInterval);
824+
825+
const ribbonStartTimeout = setTimeout(function () {
826+
ribbons({
827+
colors: ['#FF4500', '#FFD700', '#FF69B4', '#00CED1'],
828+
});
829+
830+
const ribbonsInterval = setInterval(function () {
831+
if (Date.now() >= animationEnd) {
832+
return clearInterval(ribbonsInterval);
833+
}
834+
835+
ribbons({
836+
colors: ['#FF4500', '#FFD700', '#FF69B4', '#00CED1'],
837+
});
838+
}, 2000);
839+
840+
activeIntervals.push(ribbonsInterval);
841+
}, 2000);
842+
843+
activeTimeouts.push(ribbonStartTimeout);
844+
},
845+
},
771846
];
772847

773848
function renderModes(modes) {
@@ -842,7 +917,14 @@ function getCode(name) {
842917
}
843918

844919
document.addEventListener('DOMContentLoaded', async () => {
845-
await confetti.init();
920+
// IMPORTANT: All tsParticles plugins (confetti, ribbons, etc.) must be registered
921+
// BEFORE the engine is initialized via engine.load(). Once engine.load() runs
922+
// (triggered by any confetti() or ribbons() call), PluginManager.init() is called
923+
// and any subsequent pluginManager.register() will throw:
924+
// "Register plugins can only be done before calling tsParticles.load()"
925+
// We register both confetti and ribbons upfront to guarantee all plugins are
926+
// available regardless of which demo the user runs first.
927+
await Promise.all([confetti.init(), ribbons.init()]);
846928

847929
updateShareLinks();
848930
updateShareOrder();
@@ -906,6 +988,8 @@ document.addEventListener('DOMContentLoaded', async () => {
906988
ev.preventDefault();
907989
}
908990

991+
cleanupActiveEffects();
992+
909993
try {
910994
eval(editor.getValue());
911995
} catch (err) {

websites/ribbons/CHANGELOG.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
77

88
**Note:** Version bump only for package @tsparticles/ribbons-website
99

10-
11-
12-
13-
1410
## [4.3.1](https://github.com/tsparticles/tsparticles/compare/v4.3.0...v4.3.1) (2026-07-01)
1511

1612
**Note:** Version bump only for package @tsparticles/ribbons-website

websites/ribbons/index.html

Lines changed: 109 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<meta
77
name="description"
8-
content="tsParticles - Easily create highly customizable ribbon animations and use them as animated backgrounds for your website. Ready to use components available for React, Vue.js (2.x and 3.x), Angular, Svelte, jQuery, Preact, Inferno."
8+
content="Create stunning ribbon animations with tsParticles. Lightweight, customizable ribbon effects for any website. Supports React, Vue, Angular, Svelte. Free and open-source."
99
/>
1010
<meta name="author" content="Matteo Bruni" />
1111
<meta
@@ -111,6 +111,7 @@
111111

112112
<header>
113113
<div class="header-left">
114+
<a class="header-link" href="/about.html">About</a>
114115
<a class="header-link" href="/blog/index.html">Blog</a>
115116
<a class="header-link" href="/cookie-policy.html">Cookie</a>
116117
<a class="header-link" href="/privacy-policy.html">Privacy</a>
@@ -247,6 +248,112 @@
247248

248249
<h1>tsParticles Ribbons</h1>
249250

251+
<div class="intro-section">
252+
<p class="intro-lead">
253+
Create stunning, flowing ribbon animations for your website with just a few lines of
254+
JavaScript. tsParticles Ribbons adds beautiful, physics-driven ribbon effects that enhance
255+
your site's visual appeal without slowing it down.
256+
</p>
257+
<p class="intro-sub">
258+
Whether you're building a celebration effect, an interactive background, or a subtle ambient
259+
animation, ribbons provide an elegant solution that works everywhere — no plugins, no flash,
260+
just pure HTML5 Canvas.
261+
</p>
262+
</div>
263+
264+
<div class="features-section">
265+
<h2>Why Choose tsParticles Ribbons?</h2>
266+
<div class="features-grid">
267+
<div class="feature-card">
268+
<h3>Lightweight &amp; Fast</h3>
269+
<p>
270+
The entire ribbons bundle weighs just a few kilobytes gzipped. Animations run at 60fps
271+
using hardware-accelerated Canvas rendering, ensuring smooth performance even on mobile
272+
devices.
273+
</p>
274+
</div>
275+
<div class="feature-card">
276+
<h3>Zero Dependencies</h3>
277+
<p>
278+
Use it with a simple script tag or install via npm. No jQuery, no React, no build tools
279+
required — though it integrates seamlessly with all major frameworks.
280+
</p>
281+
</div>
282+
<div class="feature-card">
283+
<h3>Fully Customizable</h3>
284+
<p>
285+
Control colors, physics, positioning, and timing. Create anything from subtle background
286+
effects to dramatic celebration bursts with just a few configuration options.
287+
</p>
288+
</div>
289+
<div class="feature-card">
290+
<h3>Cross-Browser Compatible</h3>
291+
<p>
292+
Works in all modern browsers including Chrome, Firefox, Safari, and Edge. Graceful
293+
fallback for older browsers — your site stays functional everywhere.
294+
</p>
295+
</div>
296+
<div class="feature-card">
297+
<h3>Framework Support</h3>
298+
<p>
299+
Official packages available for React, Vue.js, Angular, and Svelte. One-line integration
300+
with dedicated components that handle canvas lifecycle automatically.
301+
</p>
302+
</div>
303+
<div class="feature-card">
304+
<h3>Open Source</h3>
305+
<p>
306+
MIT licensed and backed by the tsParticles community. Full source code available on
307+
GitHub with comprehensive documentation and examples.
308+
</p>
309+
</div>
310+
</div>
311+
</div>
312+
313+
<div class="usecases-section">
314+
<h2>Perfect For</h2>
315+
<ul class="usecases-list">
316+
<li>
317+
<strong>Product launches &amp; announcements:</strong> Celebrate new releases with
318+
eye-catching ribbon bursts that draw attention to your message.
319+
</li>
320+
<li>
321+
<strong>Holiday &amp; seasonal themes:</strong> Add festive ribbon animations for
322+
holidays, sales events, or special occasions.
323+
</li>
324+
<li>
325+
<strong>Interactive backgrounds:</strong> Create engaging ambient effects that respond to
326+
user interactions without distracting from your content.
327+
</li>
328+
<li>
329+
<strong>Gamification &amp; rewards:</strong> Trigger ribbon animations when users complete
330+
actions, earn achievements, or reach milestones.
331+
</li>
332+
<li>
333+
<strong>Portfolio &amp; creative sites:</strong> Add a unique visual element that makes
334+
your portfolio stand out from the crowd.
335+
</li>
336+
<li>
337+
<strong>E-commerce &amp; conversions:</strong> Highlight special offers, discount reveals,
338+
or successful purchases with celebratory effects.
339+
</li>
340+
</ul>
341+
</div>
342+
343+
<div class="quickstart-section">
344+
<h2>Quick Start</h2>
345+
<p>Add ribbons to any website in under a minute. Include the script and call one function:</p>
346+
<pre><code>&lt;script src="https://cdn.jsdelivr.net/npm/@tsparticles/ribbons@latest/tsparticles.ribbons.bundle.min.js"&gt;&lt;/script&gt;
347+
&lt;script&gt;
348+
ribbons();
349+
&lt;/script&gt;</code></pre>
350+
<p>
351+
That's it! Ribbons will fall from random positions across the top of your page. Want to
352+
customize? Check out the interactive examples below, or read the
353+
<a href="/blog/customization-guide.html">customization guide</a>.
354+
</p>
355+
</div>
356+
250357
<div class="share-menu-wrapper">
251358
<details class="share-menu">
252359
<summary class="share-menu-trigger" aria-label="Share this page">
@@ -388,27 +495,6 @@ <h1>tsParticles Ribbons</h1>
388495
</details>
389496
</div>
390497

391-
<div>
392-
<div class="container">
393-
<div class="html-group">
394-
<div class="flex-rows">
395-
<div class="left">
396-
<h2><a href="#usage" id="usage" class="anchor">Usage</a></h2>
397-
</div>
398-
<div class="description">
399-
<p>
400-
First of all include the script in your page, only if using in plain HTML/JS pages:
401-
</p>
402-
</div>
403-
</div>
404-
<div class="editor">
405-
&lt;script
406-
src="https://cdn.jsdelivr.net/npm/@tsparticles/ribbons@__RIBBONS_VERSION__/tsparticles.ribbons.bundle.min.js"&gt;&lt;/script&gt;
407-
</div>
408-
</div>
409-
</div>
410-
</div>
411-
412498
<div id="ribbons-modes"></div>
413499

414500
<footer>
@@ -423,6 +509,7 @@ <h2><a href="#usage" id="usage" class="anchor">Usage</a></h2>
423509
<span> by <b>tsParticles</b></span>
424510
</a>
425511
<div class="footer-links">
512+
<a href="/about.html" class="cookie-policy-link">About</a>
426513
<a href="/blog/index.html" class="cookie-policy-link">Blog</a>
427514
<a href="/cookie-policy.html" class="cookie-policy-link">Cookie Policy</a>
428515
<a href="/privacy-policy.html" class="cookie-policy-link">Privacy Policy</a>

websites/ribbons/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
},
3131
"homepage": "https://ribbons.js.org",
3232
"dependencies": {
33+
"@tsparticles/confetti": "workspace:^",
34+
"@tsparticles/engine": "workspace:^",
3335
"@tsparticles/ribbons": "workspace:*",
3436
"ace-builds": "^1.44.0",
3537
"js-beautify": "^1.15.4"

0 commit comments

Comments
 (0)