Decrypting Text

Displays text with a decrypting animation effect, revealing the final content through randomized characters.

1. Copy the component file

Create a new file called decrypting-text-animation.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

decrypting-text-animation.jsx

import { memo, useEffect, useMemo, useState } from "react";
import styles from "./decrypting-text-animation.module.css";

const random = (n = 1) => {
  return Math.floor(Math.random() * n);
};

const DecryptingTextAnimation = (props) => {
  const {
    text,
    speed = 50,
    charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%&*-+?",
  } = props;

  const letterVariants = ["outlined", "filled"];

  const getRandomLetterVariant = () => {
    return letterVariants[random(letterVariants.length)];
  };

  const getTextMappings = () => {
    return (
      text
        ?.split(" ")
        .filter(Boolean)
        .map(word => (
          word
            .split("")
            .map((letter) => ({
              letter,
              variant: getRandomLetterVariant(),
              index: random(charset.length),
            }))
        ))
    );
  };

  const [currentText, setCurrentText] = useState(text);
  const [textMapping, setTextMapping] = useState(getTextMappings);

  const shuffledCharset = useMemo(() => {
    return charset.split("").sort(() => (
      random(5) - random(5)
    )).join("");
  }, [charset, text]);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setTextMapping((prev) => {
        return prev.map(word => (
          word.map(entry => {
            const isDone = entry.letter === shuffledCharset[entry.index];
            return {
              ...entry,
              variant: isDone ? "" : getRandomLetterVariant(),
              index: isDone ? (
                entry.index
              ) : (
                (entry.index + 1) % shuffledCharset.length
              )
            };
          })
        ))
      });
      const isDone = textMapping.flat().every(entry => {
        return entry.letter === shuffledCharset[entry.index];
      });
      if (isDone) {
        clearInterval(intervalId);
        return;
      }
    }, speed);
    return () => {
      clearInterval(intervalId);
    };
  }, [shuffledCharset, textMapping, speed]);

  if (currentText !== text) {
    setCurrentText(text);
    setTextMapping(getTextMappings());
  }

  return (
    <span
      aria-label={currentText}
      className={styles["decrypting-text"]}
    >
      {
        textMapping.map((word, wordIndex, arr) => (
          <>
            <span 
              aria-hidden={true}
              className={styles["word"]}
            >
              {word.map((letter) => {
                return (
                  <span
                    aria-hidden={true}
                    className={[
                      styles["letter"],
                      styles[`letter-${letter.variant}`]
                    ].join(" ")}
                  >
                    {shuffledCharset[letter.index]}
                  </span>
                );
              })}
            </span>
            {wordIndex !== (arr.length - 1) && (
              <span aria-hidden={true}>
                &nbsp;
              </span>
            )}
          </>
        ))
      }
    </span>
  )
};

export default memo(DecryptingTextAnimation);

2. Copy the CSS module file

In the same folder, create a file called decrypting-text-animation.module.css and paste the following CSS.

decrypting-text-animation.module.css

.decrypting-text {
  .word {
    white-space: nowrap;
    word-break: keep-all;

    .letter {
      display: inline-block;
      font: inherit;
      transition: all 100ms ease-in-out;

      &.letter-filled {
        background: currentColor;
      }

      &.letter-outlined {
        box-shadow: inset 0 0 1px 1px currentColor;
      }
    }
  }
}

3. Use the component

Now you can import and use the component anywhere in your project.

decrypting-text-animation-preview.jsx

import DecryptingTextAnimation from "@/components/text-effects/decrypting-text-animation/decrypting-text-animation";

/* keep text on center */
const wrapperStyles = {
  textAlign: "center",
  padding: "24px",
  textWrap: "balance",
};

const DecryptingTextAnimationPreview = () => {
  return (
    <div style={wrapperStyles}>
      <p style={{
        fontFamily: "monospace",
        fontSize: "1.5rem",
      }}>
        Beyond the encrypted noise lives {" "}
        <strong>
          <DecryptingTextAnimation
            text="Pure Awareness"
            speed={50}
          />
        </strong>
      </p>
    </div>
  );
};

export default DecryptingTextAnimationPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
textstringYes-The text content to be decrypted and displayed.
speednumberNo50Speed in milliseconds between each decrypting step.
charsetstringNo"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%&*-+?"The character set used to generate random decrypting characters.