Home

Creating a chameleon text effect

I recently created a new page on this website. From an early time in my front-end journey, I have been amazed by the websites that are featured on awwwards and I wanted to create something fancy like them.

Here's how I did it.

What we want to build

Let's take a look again at what we're trying to build here:

Hello there!

Changing colors

We can animate the color changing of the text by using transition:

transition-property: color;
transition-duration: 5s;
transition-timing-function: linear;

transition: color 5s linear;
/* or in one line */

And since I'm using styled-components, we can put this in a re-usable CSS string:

import { css } from "styled-components";

const WaveMixin = css`
  transition-property: color;
  transition-duration: 5s;
  transition-timing-function: linear;
`;

But this doesn't do anything at the moment — to actually change the color, we will need to update the color of the <span> that we are targeting after it has been rendered to the screen.

Let's start writing our little wrapper component for this:

// usage
<ChameleonHighlight>Hello there!</ChameleonHighlight>;

const ChameleonHighlight = ({ children }) => <Wave>{children}</Wave>;

// import styled, { css } from "styled-components";
const WaveMixin = css`
  transition-property: color;
  transition-duration: 5s;
  transition-timing-function: linear;
`;

const Wave = styled.span`
  ${WaveMixin}
`;

And now we change the color once the component has mounted:

<ChameleonHighlight>Hello there!</ChameleonHighlight>;

let root: HTMLElement;

const getNewColor = () => {
  const h = random(1, 360);
  const s = random(80, 90);
  const l = random(50, 60);

  return `hsl(${h}, ${s}%, ${l}%)`;
};

const ChameleonHighlight = ({ children }) => {
  const changeColor = () => {
    const newColor = getNewColor();

    if (root === undefined) root = document?.documentElement;
    root.style.setProperty("--color-chameleon", newColor);
  };

  useEffect(() => {
    changeColor();
  }, []);

  return <Wave>{children}</Wave>;
};

const WaveMixin = css`
  color: var(--color-chameleon);
  transition-property: color;
  transition-duration: 5s;
  transition-timing-function: linear;
`;

const Wave = styled.span`
  ${WaveMixin}
`;
Hello there!

Huh, that didn't seem to work out 🤔 but that's because the transition already ran since the above live example was already "mounted" by the time you scrolled down to it.

A wild custom hook appeared!

To keep changing the color after a pre-defined interval, we'll be defining our very own useInterval custom hook:

const useInterval = (callback, delay) => {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    const handler = (...args) => savedCallback.current(...args);

    if (delay !== null) {
      const intervalId = setInterval(handler, delay);
      return () => clearInterval(intervalId);
    }
  }, [delay]);
};

Let's now use this hook to change the color of our text just as it completes its transition.

import { useInterval } from "@/utils/hooks";

const ChameleonHighlight = ({ children }) => {
  const changeColor = () => {
    const newColor = getNewColor();

    if (root === undefined) root = document?.documentElement;
    root.style.setProperty("--color-chameleon", newColor);
  };

  useEffect(() => {
    changeColor();
  }, []);

  useInterval(() => {
    changeColor();
  }, 5000);

  return <Wave>{children}</Wave>;
};
Hello there

Voila! ✨

There we go. What's great is that you can also use this to highlight link, like so:

Look, a link!

Wrapping up

I'm really glad with how the effect turned out, and it was also easier to accomplish because I was using CSS-in-JS. It's great to see how powerful tools like styled-components can be! You can check the effect out in action and see it in all its glory 😄

Have a good one 👋

👀

Getting view count