Code Talk: How the Skate Text Effect Was Created on My Website



On the 'Skating' page on my personal website, I wanted to add a "skating in" animation to the page to capture to action of "skating".
In this post, I want to go over how I made the component.
The Purpose
For every page on the 'sidequest' subpage on my website, I want to add something that is thematically relevant of the activity on the page. In this case, I needed to create a component that demonstrates the action of skating.
Code
Here is the source code for the component:
/**
* What is this component you say??? It is a SkateboardingText component.
*
* It is a client component that displays text with a skateboarding effect on a given word.
* The word appears to skate/swipe into the screen with a dynamic motion.
*/
"use client";
import React from "react";
/**
* Props for the SkateEffect component
* @property {string} text - The complete text content to display
* @property {string} skateWord - The specific word that should have the skate effect
* @property {string} [className] - Optional CSS class name for styling the container paragraph
*/
interface SkateEffectProps {
text: string;
skateWord: string;
className?: string;
}
/**
* This renders the text with a skate effect applied to a chosen word
* @param {Object} props - Component props
* @param {string} props.text - The full text to display
* @param {string} props.skateWord - The word within the text that should skate in
* @param {string} [props.className] - Optional CSS class for styling
* @returns - The rendered text with a skate effect
*/
export default function SkateText({
text,
skateWord,
className,
}: SkateEffectProps): React.JSX.Element {
// If skateWord is not in text, just return the text
if (!text.includes(skateWord)) {
return <p className={className}>{text}</p>;
}
// Find the index of the first occurrence of skateWord
const wordIndex = text.indexOf(skateWord);
// Split the text into three parts: before the word, the word itself, and after the word
const beforeText = text.slice(0, wordIndex);
const afterText = text.slice(wordIndex + skateWord.length);
return (
<p className={className}>
{beforeText}
<span className="skateboard-effect">{skateWord}</span>
{afterText}
<style jsx>{`
.skateboard-effect {
display: inline-block;
position: relative;
animation: skateIn 1.5s ease-out;
}
@keyframes skateIn {
0% {
transform: translateX(-150px) rotate(10deg) skewX(20deg);
opacity: 0;
}
60% {
transform: translateX(15px) rotate(-5deg) skewX(-10deg);
opacity: 1;
}
75% {
transform: translateX(-8px) rotate(2deg) skewX(5deg);
}
90% {
transform: translateX(4px) rotate(-1deg) skewX(-2deg);
}
100% {
transform: translateX(0) rotate(0) skewX(0);
}
}
`}</style>
</p>
);
}
The API
This component accepts three props:
- •
text
: The complete text content to display. - •
skateWord
: The specific word that should have the skate effect. - •
className
: Optional CSS class name for styling the container paragraph.
/**
* Props for the SkateEffect component
* @property {string} text - The complete text content to display
* @property {string} skateWord - The specific word that should have the skate effect
* @property {string} [className] - Optional CSS class name for styling the container paragraph
*/
interface SkateEffectProps {
text: string;
skateWord: string;
className?: string;
}
The skateWord
is matched against the text
, and if it is found, it is extracted and wrapped within a span
that applies an inline animation. If skateWord
is not found, the component just renders the full string without any animation. This was done to prevent bugs and rendering empty span
's.
Usage
Here is how I used the SkateText
component in the "Skating" page on my website:
<SkateText
text={metadata.description}
skateWord="skate"
className="mb-8 text-neutral-600 dark:text-neutral-400"
/>
The Animation
When designing the animation, I was thinking about different types of animations I could do to capture the action of skating. I opted in for "skating in."
To capture the "skating in" animation, I used a blend of translateX
, rotate
, skewX
to create the look of the word kind of swiping into the view in a skate-type motion.
@keyframes skateIn {
0% {
/* Start off-screen to the left with a tilted and skewed appearance, invisible */
transform: translateX(-150px) rotate(10deg) skewX(20deg);
opacity: 0;
}
60% {
/* Overshoot slightly past the final position with a reversed tilt and skew, now visible */
transform: translateX(15px) rotate(-5deg) skewX(-10deg);
opacity: 1;
}
75% {
/* Recoil back to the left with a milder counter-adjustment */
transform: translateX(-8px) rotate(2deg) skewX(5deg);
}
90% {
/* Smaller forward correction, smoothing out the motion */
transform: translateX(4px) rotate(-1deg) skewX(-2deg);
}
100% {
/* Settle into final, neutral position with no transform effects */
transform: translateX(0) rotate(0) skewX(0);
}
}
Component Design
I kept the component self-contained:
- If the
skateWord
does not exist in the text, the original string is rendered and untouched. - It is a client-side component since it involves animation and JSX-styled logic.
- Instead of regex and parsing, the component uses simple string slicing for clarity and reliability.
text.indexOf()
,slice()
Intentionally, I designed the component to only "assume" that the skateWord
appears once. I wanted to keep the UI/UX simplified and not have all the skateWords
animate in the page. The description for the "Skating" page mentions the skateWord
numerous times in the description. I only wanted the animation to occur on the first use of the word:
// Find the index of the first occurrence of skateWord
const wordIndex = text.indexOf(skateWord);
I also chose a monolithic approach to this component. It is a very small component and really not going to be reused in a wide variety of ways; essentially, it is a one-off UI effect. If I went with a compositional approach, I would have to break out the responsibilites. For example, I would need a TextAnimator
component most likely to apply animation styles, and maybe another component that splits and renders text with effects.