Shuffle the frontend

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;
  }
}

Itā€™s important to stress that Iā€™m not using the media query to prevent the flash of unsorted content. Iā€™m using JavaScript to trigger the visibility change. Thanks to Vadim on Mastodon for the feedback on my wording.

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!