Home

Creating a chameleon text effect

May 11, 2021 ยท 3 min read

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