Big Numbers SDC — animierte Kennzahlen
big-numbers (Canvas-Block-Type, Grid-Container) und
big-number-item (Slot-Child, ein Kennzahl-Element) — realisieren eine animierte
Kennzahlen-Sektion. Zahlen zählen per IntersectionObserver von 0 auf den Zielwert
hoch, sobald die Komponente in den Viewport scrollt. Werte wie 9/10 werden über
strukturierte Props (animated_value: 9, suffix: /10) modelliert —
kein clientseitiges Parsing. Figma-Frame 61447:26224.
Problem Statement
SOLCOM möchte auf Landingpages und der Startseite zentrale Kennzahlen (z. B. Kundenzufriedenheit, abgeschlossene Projekte, Vermittlungen) prominent und einprägsam darstellen. Eine animierte Hochzähl-Darstellung erzeugt Aufmerksamkeit und unterstreicht die Relevanz der Zahlen.
Bisher gibt es nur die rudimentäre result-counter-SDC: ein einzelner
Integer-Zähler ohne Grid, ohne Animation, ohne Support für zusammengesetzte Werte
wie 9/10 oder 95%. Sie ist semantisch an die Projekt-Listing-Seite
gebunden und nicht als Editorial-Block konzipiert. Redakteure haben keine Möglichkeit,
eine vollständige Kennzahlen-Sektion nach Figma-Spec im Canvas-Editor aufzubauen.
Solution
Zwei neue SDCs bilden das Container+Item-Muster ab (analog zu
slider-container / slider-item):
-
big-numbersist der Canvas-Block-Type und steuert das responsive Grid-Layout. Er hat keine eigenen Daten-Props — alle Inhalte kommen von seinen Slot-Kindern. -
big-number-itemrepräsentiert eine einzelne Kennzahl mit den Propsanimated_value(Zielzahl),format(Tausender-Formatierung),suffix(statischer Rest nach der Zahl) undlabel(Beschriftung). Er trägt die JS-Library vialibraryOverrides.
Beim Scrollen in den Viewport startet ein IntersectionObserver die
Hochzähl-Animation. Bei prefers-reduced-motion: reduce wird der Endwert
sofort ohne Animation gesetzt.
User Stories
- Als Redakteur:in möchte ich im Canvas-Editor einen „Big Numbers"-Block anlegen können, damit ich eine Kennzahlen-Sektion auf einer Landingpage platzieren kann.
- Als Redakteur:in möchte ich bis zu 5 Kennzahl-Items anlegen können, damit ich die wichtigsten Zahlen von SOLCOM gebündelt darstellen kann.
- Als Redakteur:in möchte ich die Reihenfolge der Items per Drag&Drop steuern können, damit ich die Gewichtung der Kennzahlen jederzeit anpassen kann.
- Als Redakteur:in möchte ich für jedes Item einen Zahlenwert und einen Beschriftungstext eingeben können, damit Besucher:innen verstehen, was die Zahl bedeutet.
- Als Redakteur:in möchte ich Werte im Format
9/10eingeben können (animierter Teil + statischer Suffix), damit ich Bewertungszahlen wie Kundenzufriedenheitswerte korrekt darstellen kann. - Als Redakteur:in möchte ich Prozentwerte wie
95%eingeben können (Zahlenwert95+ Suffix%), damit das Prozentzeichen statisch neben der animierenden Zahl steht. - Als Redakteur:in möchte ich große Zahlen wie
100.300mit automatischem Tausender-Trenner ausgeben können, damit der formatierte Wert dem deutschen Zahlenformat entspricht. - Als Besucher:in der Website möchte ich sehen, wie die Zahlen beim Scrollen von 0 auf den Endwert hochzählen, damit die Kennzahlen Aufmerksamkeit erregen und einprägsam sind.
- Als Besucher:in der Website möchte ich, dass der animierte Teil (
9) und der statische Teil (/10) immer klar getrennt sichtbar sind — auch während der Animation. - Als Besucher:in mit aktiviertem
prefers-reduced-motionmöchte ich, dass die Zahlen direkt als Endwert angezeigt werden, ohne Animations-Flimmern. - Als Besucher:in auf einem Mobilgerät möchte ich die Kennzahlen in einer übersichtlichen 1-Spalten-Ansicht sehen, damit der Inhalt lesbar bleibt.
- Als Besucher:in auf einem Tablet möchte ich die Kennzahlen in einem 2-Spalten-Layout sehen, damit die Seite den Platz effizient nutzt.
- Als Site-Builder möchte ich
big-numbersin eine Dark-Background-Section einbetten können, damit der weiße Text der Komponente korrekt auf dem dunklen Hintergrund erscheint. - Als Entwickler möchte ich, dass die Animation nur einmal pro Seitenbesuch startet (kein Re-Trigger beim Hin- und Zurückscrollen), damit die UX konsistent und nicht störend ist.
- Als Entwickler möchte ich, dass die JS-Library per
libraryOverridesambig-number-item-SDC hängt (nicht global geladen), damit kein unbenutztes JS auf Seiten ohne diese Komponente ausgeliefert wird.
Implementation Decisions
Modul 1 — big-numbers (Canvas-Block-Type)
Das Container-SDC hat keine eigenen Daten-Props. Es rendert einen <div>
mit dem Slot-Slot items als direktem Kind. Das Grid-Layout
(flex, flex-wrap: wrap, gap: 32px) ist ausschließlich
CSS/Tailwind. Der Container ist transparent — Hintergrundfarbe kommt immer vom
umgebenden Section-Block.
Canvas-Registrierung: big-numbers wird als Canvas-Block-Type registriert,
damit Redakteure ihn als Block einfügen können. Die Items sind Canvas-Slots,
die per Drag&Drop umgeordnet werden können.
Modul 2 — big-number-item (Slot-Child)
Props-Schema:
| Prop | Typ | Pflicht | Default | Beschreibung |
|---|---|---|---|---|
animated_value | number | ja | — | Numerischer Zielwert der Animation (z. B. 9, 95, 100300) |
format | string enum | nein | none | none: keine Formatierung; thousands: Tausender-Trenner mit . (dt.) via toLocaleString('de-DE') |
suffix | string | nein | leer | Statischer Teil nach dem Zahlenwert (z. B. %, /10) |
label | string | ja | — | Beschriftungstext unter der Zahl (z. B. „Kundenzufriedenheit") |
Das Twig-Template rendert die Zahl als zwei benachbarte <span>-Elemente:
einen animierbaren Span (mit data-target-Attribut für die Zielzahl und
data-format für die Formatierungsoption) und einen statischen Span für den
Suffix. Das JS liest data-target und data-format aus dem DOM,
ohne die Twig-Props direkt zu kennen.
Die JS-Library wird per libraryOverrides im big-number-item.component.yml
geladen (analog zum slider-container-Muster):
libraryOverrides:
js:
big-numbers.js: { attributes: { type: module } }
Damit wird das JS nur geladen, wenn mindestens ein big-number-item auf der Seite vorhanden ist.
Modul 3 — big-numbers.js (Drupal.behaviors)
Das JS-Modul registriert ein Drupal.behaviors.solcomBigNumbers-Behavior.
Im attach-Hook werden alle .big-number-item-Elemente mit
once() prozessiert. Für jedes Element wird ein
IntersectionObserver registriert.
Beim ersten Eintreten in den Viewport (threshold: 0.3) startet die
Hochzähl-Animation:
- Animationsdauer: 1500 ms, Easing:
ease-out(quadratisches Easing viarequestAnimationFrame). - Zwischenwerte:
Math.round(eased * target)für Integer; für Float-Werte (erkennbar amdata-target-Typ): Dezimalstellen beibehalten. - Nach Abschluss: wenn
data-format="thousands", Endwert viatoLocaleString('de-DE')formatieren. - Observer wird nach dem ersten Trigger abgemeldet (einmaliger Start, kein Re-Trigger).
prefers-reduced-motion-Check vor dem Observer-Setup:
window.matchMedia('(prefers-reduced-motion: reduce)').matches →
Endwert sofort setzen, kein Observer.
Canvas-Registrierung
big-numbers wird als Canvas-Block-Type registriert. Die Registrierung
folgt dem etablierten Muster für Container-SDCs im Projekt. big-number-item
wird als Slot-Child registriert. Details zur Canvas-Konfiguration: ADR-0002,
ADR-0007, ADR-0014, ADR-0016 (zusammengefasst in docs/wiki/canvas-integration.html).
Responsive Grid
| Breakpoint | Layout |
|---|---|
| Mobile < 768 px | 1 Spalte — Items untereinander, volle Breite |
| Tablet 768–1023 px | 2 Spalten — flex-wrap, je calc(50% - 16px) |
| Desktop ≥ 1024 px | Auto — alle Items nebeneinander mit flex: 1 0 0 |
Kein explizites columns-Prop am Container — die Spaltenanzahl ergibt sich
natürlich aus der Item-Anzahl (Figma-Varianten 3/4/5 entsprechen der Item-Anzahl).
Testing Decisions
Was einen guten Test ausmacht
Tests prüfen das gerenderte HTML-Output der Komponenten — nicht die interne
Implementierung (keine Twig-Variablen, keine JS-Internals). Gute Tests verifizieren:
korrekte DOM-Struktur, korrekte data-*-Attribute, korrekte Klassen für
das responsive Layout. Das JS-Verhalten wird durch Browser-Tests abgedeckt
(Playwright), nicht durch Unit-Tests.
Kernel-Tests (PHP)
-
big-number-itemRender-Test: Rendert das SDC mitanimated_value: 9,suffix: /10,label: Weiterempfehlungund verifiziert: ein Element mitdata-target="9", ein statisches<span>mit/10, ein Label-Element mit „Weiterempfehlung". -
Format-Test:
animated_value: 100300,format: thousands→ verifiziertdata-format="thousands"im DOM. -
Kein-Suffix-Test:
animated_value: 11, keinsuffix→ verifiziert, dass kein Suffix-<span>gerendert wird.
Prior Art: Kernel-Tests für slider-container und slider-item
unter web/modules/custom/ (Tag slider). Big-Numbers-Tests
werden mit dem Tag big-numbers angelegt und per
ddev exec ./vendor/bin/phpunit --group big-numbers ausgeführt.
Manuelle Browser-Tests
Das JS-Verhalten (Animation, Observer-Trigger, prefers-reduced-motion) wird
manuell auf der Test-URL https://solcom.ddev.site/page/73
gemäß dem QA-Plan im Story-Hub geprüft.
Out of Scope
- Automatische Synchronisation der Kennzahlen aus einem Drittsystem (alle Werte werden manuell redaktionell gepflegt).
- Animationen für den Suffix-Teil (z. B.
/10einblenden) — nur der numerische Teil animiert. - Dezimalzahl-Animation mit sich ändernden Dezimalstellen während der Animation — der Endwert wird exakt so angezeigt, wie er via
animated_valueübergeben wurde. - Negative Zahlen oder Zahlen mit führendem Vorzeichen.
- Mehr als 5 Items pro Container-Block.
- Eigene Hintergrundfarbe auf der
big-numbers-Komponente — das ist Aufgabe des Section-Wrappers. - Anpassung oder Deprecation von
result-counter— diese Komponente bleibt unverändert im Code und wird für ihren bestehenden Anwendungsfall weiter genutzt. - Staggered Animation (Items zählen zeitlich versetzt hoch) — alle Items starten gleichzeitig.
Further Notes
-
Figma-Referenz: File
CHHDfbpX8Ys2LBiGs5HVK0, Node61447:26224(BigNumbersHor/CounterItem). Typografie: Montserrat ExtraBold 45 px für Zahlenwerte, Scala Sans LF Regular 19 px für Labels, Gap Zahl→Label 24 px (var(--xs)), Gap Item→Item 32 px. - Story-Hub: docs/stories/P2503-173-big-numbers.html enthält den vollständigen QA-Plan mit Click-Pfaden pro AK.
-
result-counter-Abgrenzung: Die bestehende SDC hat eine andere semantische Rolle (Suchfilter-Counter für Projekt-Listings) und ein anderes Props-Schema (nurcount: integer). Sie ist kein Vorläufer vonbig-number-itemund wird nicht ersetzt. -
Tausender-Trenner: Nur bei
format: thousandsaktiv. Der Wert178.2(Dezimalzahl) wird alsanimated_value: 178.2,format: nonemodelliert — kein Punkt als Tausender-Trenner. -
Canvas-Integration: Detaillierte Canvas-Konventionen (active_version-Hash,
Slot-Topologie, Block-Type-Registrierung) sind in
docs/wiki/canvas-integration.htmlzusammengefasst.