Marquee
Scrolling strip of text.
HACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PMHACK NIGHT FRIDAY 8PM
build somethingbuild somethingbuild somethingbuild somethingbuild somethingbuild somethingbuild somethingbuild somethingbuild somethingbuild somethingbuild somethingbuild something
Code
Copy into src/components/ph/ .
marquee.css
.ph-marquee__track {
animation: ph-marquee var(--duration, 60s) linear infinite;
}
.ph-marquee[data-reverse] .ph-marquee__track {
animation-direction: reverse;
}
@keyframes ph-marquee {
to {
transform: translateX(-100%);
}
}
@media (prefers-reduced-motion: reduce) {
.ph-marquee__track {
animation: none;
}
}
Marquee.astro
---
import "./marquee.css";
type Props = {
text: string;
repeat?: number;
duration?: string;
reverse?: boolean;
class?: string;
};
const { text, repeat = 12, duration = "60s", reverse = false, class: className } = Astro.props;
---
<div
data-reverse={reverse ? "" : undefined}
class:list={["ph-marquee flex w-full overflow-hidden whitespace-nowrap select-none", className]}
style={`--duration: ${duration}`}
>
{
[0, 1].map((copy) => (
<div class="ph-marquee__track flex shrink-0" aria-hidden={copy === 1 ? "true" : undefined}>
{Array.from({ length: repeat }, () => (
<span class="pr-6">{text}</span>
))}
</div>
))
}
</div>
Copy into components/ph/ (or src/components/ph/).
marquee.css
.ph-marquee__track {
animation: ph-marquee var(--duration, 60s) linear infinite;
}
.ph-marquee[data-reverse] .ph-marquee__track {
animation-direction: reverse;
}
@keyframes ph-marquee {
to {
transform: translateX(-100%);
}
}
@media (prefers-reduced-motion: reduce) {
.ph-marquee__track {
animation: none;
}
}
marquee.tsx
import type { CSSProperties } from "react";
import { cn } from "./cn";
import "./marquee.css";
type MarqueeProps = {
text: string;
repeat?: number;
duration?: string;
reverse?: boolean;
className?: string;
};
export function Marquee({ text, repeat = 12, duration = "60s", reverse = false, className }: MarqueeProps) {
return (
<div
data-reverse={reverse ? "" : undefined}
className={cn("ph-marquee flex w-full overflow-hidden whitespace-nowrap select-none", className)}
style={{ "--duration": duration } as CSSProperties}
>
{[0, 1].map((copy) => (
<div key={copy} className="ph-marquee__track flex shrink-0" aria-hidden={copy === 1 ? true : undefined}>
{Array.from({ length: repeat }, (_, i) => (
<span key={i} className="pr-6">
{text}
</span>
))}
</div>
))}
</div>
);
}
cn.ts
export const cn = (...parts: (string | false | null | undefined)[]) => parts.filter(Boolean).join(" ");