5 Punkte von GN⁺ 2024-04-26 | 1 Kommentare | Auf WhatsApp teilen
  • canvas-confetti ist eine clientseitige Bibliothek, die canvasbasierte Confetti-Animationen auf Webseiten ausführt und sowohl die Installation über NPM als auch die direkte Einbindung per CDN unterstützt
  • Die grundlegende confetti()-API lässt sich mit einem einzigen Optionsobjekt für Partikelanzahl, Winkel, Streuung, Geschwindigkeit, Gravitation, Farben, Formen, Position, z-index usw. anpassen; in Umgebungen mit Promise-Unterstützung kann außerdem der Zeitpunkt des Animationsendes abgefragt werden
  • Für Nutzer mit Reduced Motion gibt es die Option disableForReducedMotion; ihr Standardwert ist aktuell false, könnte sich aber in einem künftigen Major Release ändern
  • Es lassen sich benutzerdefinierte Formen auf Basis von SVG-Paths und Text erstellen; zusätzlich zu den Standardformen square, circle und star sind auch Effekte wie Emoji-Confetti möglich
  • confetti.create() erstellt eine Instanz für ein bestimmtes Canvas und unterstützt globale Optionen wie resize und useWorker; bei useWorker: true wird die Kontrolle über das Canvas jedoch an einen Web Worker übertragen, sodass Manipulationen im Main Thread Fehler verursachen

Installation und Ausführung

  • Auf der Demoseite kann die Funktionsweise der Bibliothek ausprobiert werden
  • Die Installation als NPM-Paket ist möglich
npm install --save canvas-confetti
  • Im Projekt-Build kann sie mit require('canvas-confetti') verwendet werden
  • Diese Bibliothek ist eine Client-Komponente und läuft nicht in Node
    • Laut README sollte das Projekt mit einem Tool wie webpack gebaut werden
  • In HTML-Seiten kann sie direkt per CDN-Skript eingebunden werden
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/…;
  • Bei der Nutzung per CDN wird empfohlen, die zum Einbindungszeitpunkt aktuelle Version zu verwenden; alle Versionen sind auf der Releases-Seite verfügbar

Unterstützung für Reduced Motion

  • Einige Nutzer möchten Bewegungen auf Websites nicht sehen oder bevorzugen weniger davon; Browser können dies über prefers-reduced-motion mitteilen
  • Mit der Option disableForReducedMotion kann verhindert werden, dass potenziell irritierende Animationen Nutzern angezeigt werden, für die solche Effekte problematisch sind
  • Der Standardwert dieser Option ist derzeit false
  • Für ein künftiges Major Release wird erwogen, den Standardwert zu ändern; starke Meinungen dazu können als Issue eingebracht werden
  • Wenn durch disableForReducedMotion das Confetti deaktiviert wird, wird das confetti()-Promise sofort aufgelöst

Grundlegende API und Promise-Verhalten

  • Bei Installation über NPM kann die Bibliothek im Projekt-Build als Client-Komponente per require genutzt werden; in der CDN-Version ist sie als confetti-Funktion auf window verfügbar
  • confetti([options]) nimmt ein optionales Optionsobjekt entgegen
  • Falls window.Promise vorhanden ist, wird ein Promise zurückgegeben, das das Ende der Animation signalisiert
    • In Umgebungen ohne Promise wie IE wird null zurückgegeben
    • Es kann ein Promise-Polyfill verwendet werden
    • Mit confetti.Promise = MyPromise kann auch eine eigene Promise-Implementierung bereitgestellt werden
  • Wenn confetti vor Abschluss erneut mehrfach aufgerufen wird, wird jedes Mal dasselbe Promise zurückgegeben
  • Intern wird dasselbe Canvas-Element wiederverwendet, und neues Confetti wird hinzugefügt, während bestehende Animationen weiterlaufen
  • Die von den einzelnen Aufrufen zurückgegebenen Promises werden erst aufgelöst, wenn alle Animationen beendet sind

Wichtige Optionen

  • particleCount: Anzahl der abzufeuenden Confetti-Partikel, Standardwert 50
  • angle: Abschusswinkel, Standardwert 90; 90 bedeutet nach oben
  • spread: Streuungsbereich vom Zentrum aus, Standardwert 45
  • startVelocity: Anfangsgeschwindigkeit, Standardwert 45
  • decay: Stärke der Geschwindigkeitsabnahme, Standardwert 0.9
    • Sollte zwischen 0 und 1 liegen; außerhalb dieses Bereichs kann sich die Geschwindigkeit erhöhen
  • gravity: Stärke, mit der Partikel nach unten gezogen werden, Standardwert 1
    • 0.5 entspricht halber Gravitation; da es keine Begrenzung gibt, lassen sich Partikel auch nach oben steigen lassen
  • drift: Seitliche Drift, Standardwert 0
    • Negative Werte bedeuten links, positive rechts
  • flat: Deaktiviert den Effekt, dass sich das Confetti wie reales 3D-Confetti neigt und flattert; Standardwert false
  • ticks: Anzahl der Bewegungszyklen des Confettis, Standardwert 200
  • origin: Startposition des Abschusses
    • origin.x: x-Position auf der Seite, 0 ist links, 1 ist rechts, Standardwert 0.5
    • origin.y: y-Position auf der Seite, 0 ist oben, 1 ist unten, Standardwert 0.5
  • colors: Array von Farbstrings im HEX-Format
  • shapes: Array von Confetti-Formen
    • Standardmäßig eingebaut sind square, circle, star
    • Standard ist eine gleichmäßige Mischung aus square und circle
    • Mit Array-Verhältnissen wie ['circle', 'circle', 'square'] lässt sich die Mischverteilung anpassen
  • scalar: Skalierung jedes Partikels, Standardwert 1
  • zIndex: Anzeigeebene des Confettis, Standardwert 100
  • disableForReducedMotion: Deaktiviert Confetti für Nutzer mit Reduced-Motion-Präferenz

Benutzerdefinierte Formen erstellen

  • confetti.shapeFromPath({ path, matrix? }) erstellt mit einem SVG-Path-String eine benutzerdefinierte Confetti-Form
  • Für pathbasierte Formen gelten einige Einschränkungen
    • Alle Paths werden als gefüllte Formen behandelt; Stroke-Paths sind nicht implementiert
    • Paths sind auf eine einzelne Farbe beschränkt
    • Jeder Path benötigt eine gültige Transform-Matrix
    • Die Berechnung der Matrix ist aufwendig; während der Entwicklung sollte sie pro Path einmal berechnet und zwischengespeichert werden
    • Für denselben Path-Wert ist die Matrix immer identisch
    • Nach einem Bibliotheks-Update sollte die Matrix zur Wahrung der Forward-Kompatibilität neu erzeugt und gecacht werden
    • Pathbasiertes Confetti ist auf Browser beschränkt, die Path2D unterstützen
  • Der Rückgabewert ist ein Shape-Objekt, das direkt im shapes-Array verwendet werden kann
var triangle = confetti.shapeFromPath({ path: 'M0 10 L5 0 L10 10z' });

confetti({
  shapes: [triangle]
});
  • confetti.shapeFromText({ text, scalar?, color?, fontFamily? }) erstellt textbasierte Confetti-Formen und kann Standard-Unicode-Emoji verwenden
  • Textbasierte Formen eignen sich gut für Emoji-Confetti
    • Für flatterndes Confetti eignen sich im Allgemeinen einzelne Zeichen mit annähernd quadratischer Form, besonders Emoji
    • Der Text wird nicht bei jedem Frame neu gezeichnet, sondern rasterisiert; wenn die Skalierung nach dem Erstellen stark verändert wird, kann er unscharf werden
    • Wenn die Option scalar in den Confetti-Optionen verwendet werden soll, empfiehlt es sich, beim Erstellen der Form denselben scalar-Wert zu nutzen
  • Die Textoptionen sind text, scalar, color, fontFamily
    • Der Standardwert von fontFamily folgt den nativen Emoji-Rendering-Konventionen des Betriebssystems und verwendet sans-serif als Fallback
    • Bei Webfonts muss die Schrift vor dem Confetti-Rendering geladen sein
var scalar = 2;
var pineapple = confetti.shapeFromText({ text: '🍍', scalar });

confetti({
  shapes: [pineapple],
  scalar
});

Benutzerdefiniertes Canvas und Rendering mit Worker

  • confetti.create(canvas, [globalOptions]) erstellt eine Instanz der Confetti-Funktion, die ein bestimmtes Canvas verwendet
  • Das ist nützlich, wenn Confetti auf einen bestimmten Bereich der Seite beschränkt werden soll
  • Standardmäßig verändert diese Methode das Canvas nicht, außer darauf zu zeichnen
  • Wenn die sichtbare Größe des Canvas per CSS geändert wird, ändert sich die tatsächliche Bildgröße des Canvas nicht; das kann zu Streckung und Unschärfe führen
    • Mit aktivierter Option resize passt die Bibliothek die Bildgröße des Canvas an und reagiert auch auf Änderungen der Fenstergröße oder auf das Drehen mobiler Geräte
  • Eine Confetti-Instanz sollte nicht mehrfach für dasselbe Canvas-Element initialisiert werden; die erzeugte benutzerdefinierte Instanz sollte beibehalten werden
  • Globale Optionen

    • resize: Legt fest, ob die Bildgröße des Canvas gesetzt und an Fensteränderungen angepasst werden soll; Standardwert false
    • useWorker: Rendert die Confetti-Animation, wenn möglich, in einem asynchronen Web Worker; Standardwert false
    • Im Standardfall läuft die Animation immer im Main Thread
    • Wenn der Browser es unterstützt, läuft die Animation außerhalb des Main Threads, damit dieser nicht blockiert wird
    • In nicht unterstützenden Browsern wird dieser Wert ignoriert
    • disableForReducedMotion: Sorgt dafür, dass diese Confetti-Instanz die Reduced-Motion-Anforderung des Nutzers immer respektiert
  • Hinweise zu useWorker: true

    • Bei useWorker: true wird die Kontrolle über das Canvas an einen Web Worker übergeben
    • In diesem Fall führt jede Manipulation im Main Thread außer dem Entfernen des Canvas aus dem DOM zu Fehlern
    • Wenn das Canvas direkt manipuliert werden muss, sollte die Option useWorker nicht verwendet werden
    var myCanvas = document.createElement('canvas');
    document.body.appendChild(myCanvas);
    
    var myConfetti = confetti.create(myCanvas, {
      resize: true,
      useWorker: true
    });
    myConfetti({
      particleCount: 100,
      spread: 160
    });
    

Animation stoppen und Beispielmuster

  • confetti.reset() stoppt die Animation, entfernt sämtliches Confetti und löst wartende Promises sofort auf
  • Separate Instanzen, die mit confetti.create() erstellt wurden, haben eine eigene Methode reset
confetti();

setTimeout(() => {
  confetti.reset();
}, 100);
  • Die Basisausführung erfolgt über den Aufruf confetti() ohne Argumente
  • Mit particleCount: 150 kann eine große Menge Confetti abgeschossen werden
  • Mit spread: 180 lässt sich besonders breit gestreutes Confetti erzeugen
  • Wird in origin Math.random() verwendet, lassen sich kleine Explosionseffekte an zufälligen Positionen auf der Seite erzeugen
  • Das README zeigt als Beispiel ein Muster, das mit requestAnimationFrame 30 Sekunden lang fortlaufend Confetti vom linken und rechten Seitenrand aus abschießt

1 Kommentare

 
GN⁺ 2024-04-26
Hacker-News-Kommentare
  • Der Trick für performante Animationen hier besteht darin, auf ein Canvas zu zeichnen und dieses Canvas dann vor alle anderen Elemente zu legen, dabei aber Pointer Events zu deaktivieren, damit man weiter mit der Seite interagieren kann

    • Stimmt. Deaktivierte Pointer Events sind überraschend oft nützlich
    • Das wurde zwar als Trick für performante Animationen beschrieben, aber mir fällt ehrlich gesagt keine andere Art ein, so etwas umzusetzen. Wie würde eine naive Implementierung aussehen?
  • Das erinnert mich an die gute alte Zeit, als ich 2015 in der Highschool Webentwicklung gemacht habe. Ich habe eine kleine Website mit Konfetti gebaut, um ein Mädchen zu fragen, ob sie mit mir zum Homecoming geht, und rückblickend war das extrem nerdig
    Damals wirkte es auf ein Kind wie eine Superkraft, eine Website bauen zu können. Zeitlich war es wohl nicht dieses Paket, aber die Animation war ziemlich gut
    Ich liebe solche kleinen Projekte, die einfach nur Freude machen. Deshalb habe ich überhaupt mit dem Programmieren angefangen, und bis heute ist das ein großer Antrieb

    • Hat es geklappt? Hat sie Ja gesagt?
  • Diesen Teil der Demo-Seite mag ich:

    If you happened to get curious and changed the particle count to 400 or so, you saw something disappointing. An even "flattened cone" look to the confetti, making it look way too perfect and ruining the illusion.

    Diese Liebe zum Detail ist selten, und ob bei statistischer Visualisierung, Filmrequisiten oder Website-Konfetti: Jedes Mal, wenn ich so etwas entdecke, weiß ich es zu schätzen
    Als Lösung würde ich wahrscheinlich die Zufallsverteilung selbst ändern. Ich müsste es mir genauer ansehen, aber ich vermute, dass die reale Verteilung eher einer Gauß-Verteilung ähnelt

  • Wir haben Konfetti im Admin-Dashboard eingebaut, das erscheint, wenn ein Vertriebsmitarbeiter einen Abschluss macht, und es macht überraschend viel Spaß und motiviert

  • Ich wünschte, die reset-Funktion würde confetti.resetti() heißen

    • Ist ja JavaScript, also kann man das zumindest lokal einfach mit "confetti.resetti = confetti.reset" beheben
      Dieser Ansatz hat zwar gewisse softwaretechnische Kosten, aber wie jeder aufmerksame Beobachter unschwer erkennen kann, überwiegt der Nutzen ganz eindeutig, also würde ich es einfach machen
    • Dieser Mensch sollte einen Job bekommen. Falls er schon einen hat, sollte man ihm zumindest einen Keks geben
    • Man könnte sogar einen PR dazu machen
  • Abgesehen davon, dass es eine coole und nützliche Bibliothek ist, ist das auch ein gutes Beispiel für ein Deep Module, wie John Ousterhout es in Philosophy of Software Design beschreibt
    Die einfachste Version, also einfach Konfetti auszulösen, ist extrem leicht zu benutzen, aber wenn man sich die Optionen ansieht, bekommt man ziemlich viel dazu: Schnee, bestimmte Farben, verschiedene Konfetti-Effekte und mehr

  • Cool und beeindruckend
    Gleichzeitig möchte ich nicht, dass das auf irgendeiner Website läuft, die ich benutze. Besonders bei Newsletter-Pop-ups oder wenn ich etwas in den Warenkorb lege, will ich nicht von Konfetti verfolgt werden

    • Seltsamerweise kann dieser Effekt ziemlich wirkungsvoll eingesetzt werden. Ob in dieser Vollbild-Variante, weiß ich nicht, aber in einer Projektmanagement-Software eines Kunden, den ich kürzlich besucht habe, wurde beim Schließen eines Elements der Button grün und bekam so einen Effekt
      Es war subtil, aber auffällig genug, und nach dem Meeting meinten ein anderer Entwickler und ich beide: „War eigentlich ein ziemlich netter Effekt.“ Es vermittelte dieses Gefühl von „Gut, es geht voran!“
      Man sollte es nur optional machen

    • Ein legitimer Einsatzort wäre wohl so etwas wie der Like-Button auf YouTube. Es gibt dort eine nette Animation, und in der mobilen App vibriert sogar das Gerät. Das ist eine sehr angenehme User Experience

    • https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pref...

      Im Browser kann man einstellen, dass man reduzierte Bewegung bevorzugt. Website-Betreiber und Maintainer von Bibliotheken sollten das respektieren, wenn sie so etwas wie Konfetti implementieren. Diese Bibliothek hat dafür sogar die Option disableForReducedMotion

    • Es gibt Orte, an denen so ein Effekt passt. Zum Beispiel beim Abschluss eines Spiels

    • Wir verwenden diese Bibliothek, wenn jemand eine bestimmte Qualifikation erfüllt. Das gibt dem Onboarding-Flow einen ziemlich netten Effekt

  • Es gibt auch die Bibliothek Party.js: https://party.js.org/

    • Welche ist dann kleiner?
      10.4 kB minifiziert, 4.2kB minifiziert + Gzip
      https://bundlephobia.com/package/canvas-confetti@1.9.2

      28.3kB minifiziert, 7.4kB minifiziert + Gzip
      https://bundlephobia.com/package/party-js@2.2.0

      Allerdings weiß ich nicht genau, wie bundlephobia arbeitet. Es zeigt möglicherweise nicht die endgültige Größe eines Pakets am besten an. Wahrscheinlich berücksichtigt es weder Code-Splitting noch das Importieren nur dessen, was man braucht. Ich sehe es eher als schnelle, grobe Übersicht

      Nach Gzip scheint confetti ein paar KB zu gewinnen, also wenn man nicht unbedingt jedes einzelne KB herausquetschen muss, können beide sinnvoll sein, je nachdem, welche Funktionen man braucht

    • Das Skript im Original wirkt auf Mobilgeräten deutlich performanter

    • Die Bibliothek im Original wirkt deutlich performanter. Auf meinem alten Arbeitsrechner spüre ich bei Party.js schon nach drei Klicks eine leichte Verzögerung
      Bei canvas-confetti setzt die Verzögerung erst ein, wenn ich ein paar Sekunden ohne Pause geklickt und wahrscheinlich über 30 Konfetti-Instanzen mit vielen Partikeln erzeugt habe

  • Ich löse Kreuzworträtsel auf downforacross.com, und wenn man ein Rätsel fertigstellt, erscheint Konfetti
    Vielleicht könnten sie hier etwas von dem performanteren Code übernehmen, damit es sich leichter anfühlt
    Aber außer bei „Spaß“-Seiten oder seltener Nutzung möchte ich nicht, dass solche Animationen überall auftauchen

  • Ich finde nicht, dass man useful extra in den Titel packen muss

    • Wie wäre es als Motivationswerkzeug und als Mittel, um zu prüfen, ob der Code kompiliert wurde: https://squint-cljs.github.io/squint/
    • Stimmt. Aber genau dieses Wort hat mein Interesse geweckt, und ich musste lachen, weil es in Wirklichkeit gar nicht so nützlich ist. Empfehlenswert
    • So nützlich wie echtes Konfetti, also zu 100 % nützlich