Docs
Loki Text Effect
Loki Text Effect
The iconic text animation from the Loki series intro.
Loki
Installation
Download all the fonts and add them to your project. Download Link
└──app
└──fonts
├──fontOne.woff2
├──fontTwo.woff2
├──fontThree.woff2
├──fontFour.woff2
├──fontFive.woff2
├──fontSix.woff2
└──fontSeven.woff2Copy and paste the following code into your project.
components/atlas_ui/loki-text-effect.tsx
"use client";
import { useEffect, useState } from "react";
import { motion } from "motion/react";
import {
fontFive,
fontFour,
fontOne,
fontSeven,
fontSix,
fontThree,
fontTwo,
} from "@/fonts/font";
const fonts = [
fontOne.className,
fontTwo.className,
fontThree.className,
fontFour.className,
fontFive.className,
fontSix.className,
fontSeven.className,
];
interface LokiEffectProps {
text: string;
velocityFont?: number; // In ms
velocityMove?: number; // In ms
className?: string;
}
const getRandomFont = (current: string) => {
let next = current;
while (next === current) {
next = fonts[Math.floor(Math.random() * fonts.length)];
}
return next;
};
const getRandomOffset = () => ({
x: (Math.random() - 0.5) * 2,
y: (Math.random() - 0.5) * 2,
});
const AnimatedLetter = ({
char,
baseFont,
velocityFont,
velocityMove,
}: {
char: string;
baseFont: string;
velocityFont: number;
velocityMove: number;
}) => {
const [fontClass, setFontClass] = useState(baseFont);
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const fontTimer = setInterval(() => {
setFontClass((prev) => getRandomFont(prev));
}, Math.max(100, velocityFont));
const moveTimer = setInterval(() => {
setPos(getRandomOffset());
}, Math.max(100, velocityMove));
return () => {
clearInterval(fontTimer);
clearInterval(moveTimer);
};
}, [velocityFont, velocityMove]);
return (
<motion.span
className={`${fontClass} inline-block text-6xl md:text-9xl drop-shadow-[0_0_8px_black] dark:drop-shadow-[0_0_8px_white]`}
style={{
fontWeight: 700,
letterSpacing: "0.15em",
textShadow: "0 0 2px #ffffff66, 0 0 5px #ffffff66",
}}
animate={{ x: pos.x, y: pos.y }}
transition={{
x: { duration: 0.5, ease: "easeInOut" },
y: { duration: 0.5, ease: "easeInOut" },
}}
>
{char}
</motion.span>
);
};
export const LokiTextEffect = ({
text,
velocityFont = 800,
velocityMove = 1800,
className = "",
}: LokiEffectProps) => {
return (
<div className={`flex flex-wrap justify-center gap-[1px] ${className}`}>
{text.split("").map((char, i) => (
<AnimatedLetter
key={i}
char={char}
baseFont={fonts[i % fonts.length]}
velocityFont={velocityFont}
velocityMove={velocityMove}
/>
))}
</div>
);
};Add @/fonts/font.ts.
import localFont from "next/font/local";
const fontOne = localFont({
src: "../app/fonts/fontOne.woff2",
});
const fontTwo = localFont({
src: "../app/fonts/fontTwo.woff2",
});
const fontThree = localFont({
src: "../app/fonts/fontThree.woff2",
});
const fontFour = localFont({
src: "../app/fonts/fontFour.woff2",
});
const fontFive = localFont({
src: "../app/fonts/fontFive.woff2",
});
const fontSix = localFont({
src: "../app/fonts/fontSix.woff2",
});
const fontSeven = localFont({
src: "../app/fonts/fontSeven.woff2",
});
export { fontOne, fontTwo, fontThree, fontFour, fontFive, fontSix, fontSeven };Update the import paths to match your project setup.
Usage
import { LokiTextEffect } from "@/components/atlas_ui/(react)/loki-text-effect";<LokiTextEffect text="Loki" />Props / Data Attributes
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
text | string | – | true | The text to display and animate |
velocityFont | number | 800 | false | Interval in ms for how often the font changes |
velocityMove | number | 1800 | false | Interval in ms for how often the position jitter is updated |
className | string | – | false | Optional classnames for according to match your setup |