When you want to randomly order elements on a website, server-side solutions like Kirbyās shuffle method are neat.
Recently I used it for an amazing project by Verlage gegen Rechts. They collected hundreds of diverse posters against discrimination and hate and wanted to display them in random order to give each one equal visibility.
Unfortunately, shuffling in the backend doesnāt work well with caching ā the order gets āstuckā until the cache is cleared. And in this case I definitely needed caching. It makes me super happy to see how much traffic that website gets, but it also means I need to be very careful with performance.
So hereās a simple frontend solution:
The HTML setup
We use a data-shuffle attribute to mark the container whose children we want to shuffle:
<div data-shuffle>
<div>First</div>
<div>Second</div>
<div>Third</div>
</div>
Prevent flash of unsorted content
To avoid seeing the original order briefly before JavaScript runs, we hide the elements initially.
The @media (scripting: enabled) query keeps elements visible when JavaScript is disabled. For users with scripting enabled, elements become visible once the data-shuffle attribute is set to shuffled.
@media (scripting: enabled) {
[data-shuffle] > * {
visibility: hidden;
}
[data-shuffle="shuffled"] > * {
visibility: inherit;
}
}
The JavaScript shuffler
This lightweight class handles the shuffling:
export class Shuffler {
constructor() {
document.querySelectorAll('[data-shuffle]').forEach(container => {
this.shuffleContainer(container);
});
}
shuffleContainer(container) {
const elements = [...container.children];
// Fisher-Yates shuffle
let i = elements.length;
while (i) {
const j = Math.floor(Math.random() * i--);
[elements[i], elements[j]] = [elements[j], elements[i]];
}
// Update DOM efficiently
const fragment = document.createDocumentFragment();
elements.forEach(element => fragment.appendChild(element));
container.appendChild(fragment);
// Show the elements (with CSS)
container.dataset.shuffle = 'shuffled';
}
}
I use the Fisher-Yates algorithm for unbiased shuffling and a DocumentFragment for performance.
Usage
Import the class and instantiate it:
import { Shuffler } from './Shuffler.js';
new Shuffler();
Thatās it! The elements will be randomly ordered on each page load, even with caching enabled.
Is it even a good idea?
Yes, asking this question now seems a bit late. But random ordering simply isnāt always the right choice ā it can be confusing when users expect a specific order or need to find items quickly. But when you want to give equal visibility to all elements, like we did for Verlage gegen Rechts, it can be a good solution.
Feedback
I was recently looking into webmentions for a different project. Iām still not sure whether I want to implement it for my own website. Seems like a lot of work. For now, you can ācommentā on Mastodon.
Let me know if you have any questions or feedback!
Replies
-
@medienbaecker Hey, nice one! But this caught my eye:
> To avoid seeing the original order briefly before JS runs, we hide the elements initially
Unfortunately, the `scripting: enabled` media feature doesnāt work like that. It will only match if JS is disabled or not supported, more like <noscript>.
It means that itās true while JS is still loading and itās true if it fails to load. The only reliable solution is the class="no-js" on the root element that you remove with loaded JS.
-
@pepelsbey You're right, the headline of that section is misleading. I'm using the data attribute to prevent the flash of unsorted content, not the media query. The media query is only there to keep images visible for users with JavaScript disabled. So in this case it's good it also works while JS is still loading. :)
I just added an explanation. Thanks for the feedback!