Wie das Mischen funktioniert, warum der verbreitete Einzeiler mit sort() nicht gleichmässig mischt, warum die Cards unsichtbar, aber nicht ausgeblendet sind und weshalb das alles im Browser passiert und nicht im Backend, liest du in diesem Beitrag.
Wenn keine Reihenfolge feststehen soll
In zwei Kundenprojekten standen Cards – so nennt man im Webdesign Inhaltsblöcke in Kartenform – in einem Raster (Grid) nebeneinander. In beiden Kundenprojekten sollte die Reihenfolge der Cards bei jedem Seitenaufruf eine andere sein, allerdings aus unterschiedlichen Gründen.
Im einen Kundenprojekt stellten die Cards die Personen eines Teams vor. Keine Person sollte immer an der gleichen Stelle stehen – es ging um Gleichbehandlung. Im anderen Kundenprojekt waren es Cards, die sich beim Darüberfahren mit der Maus umdrehen und ihre Rückseite zeigen. Dort sollten sie bei jedem Besuch neu gemischt sein, ähnlich wie bei einem Memory-Spiel.
Für die schnelle Umsetzung stand mir jeweils nur das Frontend zur Verfügung. Daher wurde aus der Idee des zufälligen Mischens ein kleines JavaScript, das ich auf der betreffenden Seite direkt nach dem Grid eingebunden habe. Gebraucht habe ich es inzwischen schon zweimal – Grund genug, es hier festzuhalten.
Hinweis: Beide Kundenprojekte hatten Bootstrap zur Verfügung, deshalb hat die Klasse
invisibledas Verstecken übernommen. Die Code-Beispiele in diesem Beitrag kommen ohne Framework aus und verwenden ein CSS-Grid. Das JavaScript selbst arbeitet mit jedem Container gleich – ob Grid, Flexbox oder Bootstrap-Row.
Wie werden die Cards gemischt?
Mit einem kleinen JavaScript, das die Cards aus dem umschliessenden Container nimmt, in eine zufällige Reihenfolge bringt und in dieser Reihenfolge wieder einhängt.
Den Ausgangspunkt bildet ein Container, der das Attribut data-shuffle enthalten muss. Über dieses Attribut finden sowohl das JavaScript als auch das CSS den Container:
<div class="card-grid" data-shuffle>
<div class="card">…</div>
<div class="card">…</div>
<div class="card">…</div>
</div>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
gap: 1.5rem;
}
[data-shuffle] {
visibility: hidden;
}
Das Grid selbst ist ein gewöhnliches CSS-Grid, die Werte sind nur ein Beispiel. Entscheidend ist die zweite Regel: Solange der Container das Attribut data-shuffle enthält, ist er unsichtbar.
document.querySelectorAll( '[data-shuffle]' ).forEach( ( container ) => {
const items = Array.from( container.children );
for ( let i = items.length - 1; i > 0; i-- ) {
const j = Math.floor( Math.random() * ( i + 1 ) );
[ items[ i ], items[ j ] ] = [ items[ j ], items[ i ] ];
}
items.forEach( ( item ) => {
container.appendChild( item );
} );
container.removeAttribute( 'data-shuffle' );
} );
Das JavaScript sucht alle Container mit data-shuffle und funktioniert damit auch bei mehreren Grids auf derselben Seite. Die Cards kopiert es zuerst in ein Array, weil children eine lebende Liste ist, die sich beim Umsortieren ständig mitverändern würde. Anschliessend mischt eine Schleife (Loop) dieses Array und setzt die Cards wieder via appendChild() in der neuen Reihenfolge in den Container. Eine Card, die bereits im Dokument steht, wird dabei nicht kopiert, sondern verschoben. Zum Schluss entfernt das JavaScript das Attribut data-shuffle – die CSS-Regel greift damit nicht mehr und das CSS-Grid wird sichtbar.
Eingebunden wird das JavaScript direkt nach dem Grid. Denn an dieser Stelle hat der Browser den Container bereits aus dem HTML aufgebaut, das JavaScript muss also nicht warten, bis die ganze Seite geladen ist. So bleiben die Cards nur so lange unsichtbar wie wirklich nötig.
Weil die Cards im DOM – der Struktur, die der Browser aus dem HTML aufbaut – tatsächlich umsortiert werden, stimmt die sichtbare Reihenfolge mit der Reihenfolge überein, in der man sie mit der Tastatur oder einem Screenreader erreicht. Würde man die Cards nur optisch umsortieren, etwa mit der CSS-Eigenschaft order, liefen die beiden Reihenfolgen auseinander.
Warum nicht einfach sort() verwenden?
Weil sort() mit einer zufälligen Vergleichsfunktion nicht gleichmässig mischt.
Statt eines Loops findet man in vielen Beispielen einen Einzeiler mit sort():
items.sort( () => Math.random() - 0.5 );
Das Ergebnis sieht zufällig aus, ist aber nicht gleichmässig verteilt. sort() geht davon aus, dass der Vergleich zweier Elemente immer gleich ausgeht. Kommt stattdessen jedes Mal ein zufälliges Ergebnis zurück, hängt die Verteilung davon ab, wie der Browser intern sortiert – und manche Reihenfolgen kommen häufiger vor als andere.
Der Loop im obigen JavaScript folgt deshalb dem Fisher-Yates-Verfahren. Er geht die Cards von hinten nach vorne durch und tauscht jede mit einer zufällig gewählten Card aus dem noch nicht gemischten Teil – sich selbst eingeschlossen. Was einmal an einer Position gelandet ist, bleibt dort. So ist jede Reihenfolge gleich wahrscheinlich, und das mit einem einzigen Durchgang durch die Liste.
Warum unsichtbar und nicht ausgeblendet?
Weil visibility: hidden den Platz der Cards reserviert – display: none würde das nicht machen.
Versteckt werden die Cards, damit die ursprüngliche Reihenfolge nie zu sehen ist – auch nicht für einen kurzen Augenblick, bevor das JavaScript gelaufen ist. Wie sie versteckt werden, macht aber einen Unterschied. Ein Element mit visibility: hidden ist im Layout vollständig vorhanden: Es hat seine Breite und Höhe, der Inhalt darunter steht bereits dort, wo er auch nach dem Mischen stehen wird. Man sieht das Element nur nicht. Ein Element mit display: none oder dem HTML-Attribut hidden nimmt dagegen gar keinen Platz ein. Damit würde der ganze Inhalt darunter nach unten rutschen, sobald das Grid eingeblendet wird.
In Bootstrap ist das der Unterschied zwischen den Klassen invisible und d-none. Genau deshalb war es in beiden Kundenprojekten invisible.
Fallback: Ist JavaScript im Browser deaktiviert, bleibt das Grid unsichtbar. Ein
noscript-Element mit einer CSS-Regel, die das Verstecken wieder aufhebt, kann die Cards in diesem Fall trotzdem einblenden, nur eben in der ursprünglichen Reihenfolge, ohne Neusortierung.
<noscript>
<style>
[data-shuffle] {
visibility: visible;
}
</style>
</noscript>
Warum im Frontend und nicht im Backend?
Weil mir für die schnelle Umsetzung nur das Frontend zur Verfügung stand – und das Mischen im Browser bei wenigen Cards schnell ist.
Hast du allerdings Zugriff aufs Backend und keinen Seiten-Cache im Weg, der eine gemischte Reihenfolge der Cards über alle Seitenaufrufe hinweg festhalten würde, ist das Mischen auf dem Server genauso gut, wenn nicht manchmal sogar die bessere Lösung.
Fazit
HTML-Cards mit JavaScript zu mischen ist eine pragmatische Lösung für ein kleines Problem. Die Sorgfalt steckt darin, dass man vom Mischen nichts merkt: keine Reihenfolge, die kurz aufblitzt, kein Inhalt, der nachrutscht, keine lange Verzögerung beim Laden. Die Cards sind jedes Mal anders angeordnet. Die Seite drumherum bleibt jedes Mal gleich. Viel Spass beim Mischen!

Kommentare