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;/* or in one line */transition: color 5s linear;
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:
This is 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 👋
No views yet. Wait what, HOW? 🤔