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