I came across a visually appealing text shuffling effect, recreated by Hyperplexed that would be interesting to implement as an Easter egg when hovering over my site logo.

This blog is generated using Hugo, styled with Terminal theme so the original code would needs to be modified to accommodate that. I also wanted it to handle the capitalisation of my logo text, convert the JavaScript to TypeScript and use a cleaner code style.

The first step was to create a shuffle-text.ts module which will be used by a base index.ts script.

The shuffle-text module exports a shuffleElementText function which takes an HTMLElement whose `mouseenter` event will start a timer to shuffle the letters of the element’s innerText until it rotates back to the original text.

The following code was added to assets/ts/shuffle-text.ts:

const alphabet = "abcdefghijklmnopqrstuvwxyz";
let intervalID: ReturnType<typeof setInterval> | null;
let originalText: string;
let textElement: HTMLElement;

function isUpperCase(string: string): boolean {
  return /^[A-Z]*$/.test(string);
}

function randomLetter(letter: string): string {
  const index = Math.floor(Math.random() * alphabet.length);
  const replacedLetter = alphabet[index];
  return isUpperCase(letter) ? replacedLetter.toUpperCase() : replacedLetter;
}

function shuffleString(string: string, count: number = 0): string {
  return string
    .split("")
    .map((letter, index) => {
      if (index < count) return originalText[index];
      return randomLetter(letter);
    })
    .join("");
}

function shuffleText() {
  let iteration = 0;

  clearInterval(Number(intervalID));

  intervalID = setInterval(() => {
    textElement.innerText = shuffleString(originalText, iteration);

    if (iteration >= originalText.length) {
      clearInterval(Number(intervalID));
      intervalID = null;
    }

    iteration += 1 / 2;
  }, 70);
}

export default function shuffleElementText(
  element: HTMLElement,
  { onParentEvent = false }
) {
  textElement = element;
  originalText = element.innerText;
  const eventElement = onParentEvent ? element.parentElement : element;
  eventElement.onmouseenter = shuffleText;
}

To use the shuffle-text module, import in assets/ts/index.ts and pass the theme’s logo element:

import scrambleElementText from "./shuffle-text";

const logo = document.getElementsByClassName("logo")[0] as HTMLElement;
scrambleElementText(logo, { onParentEvent: true });

To include these custom scripts we can extend Terminal theme footer by creating layouts/partial/extended_footer.html in the project root directory containing the following:

{{ $js := resources.Get "ts/index.ts" | js.Build "assets/index.js" |
fingerprint}}
<script src="{{ $js.RelPermalink }}"></script>

This will get the index.ts file from the assets directory and using js.Build compile to index.js file. The target path is specified to keep the files grouped in assets directory with the theme JavaScript files and makes use of the fingerprint pipe to help with cache invalidation.

The result is simple but effective:

logo text shuffling