Texto con marcador
Palabras resaltadas a rotulador que se dibujan al entrar en pantalla, una tras otra.
Construimos software que se adapta a tu operación, no al revés. Primero entendemos cómo trabajas; después lo automatizamos con trazabilidad desde el primer día.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useEffect, useRef, useState, type CSSProperties } from "react";
/** Párrafo con palabras resaltadas a mano: el marcador se dibuja al entrar en pantalla, con borde irregular. React + TypeScript, sin dependencias. */
export type TextoMarcadorParte = string | { mark: string; color?: string };
export type TextoMarcadorProps = {
parts?: TextoMarcadorParte[];
className?: string;
style?: CSSProperties;
};
const CSS = `
.gctmr-root { display: block; max-width: 34ch; color: var(--gctmr-fg, #1c1b19); font: 500 clamp(24px, 4.4vw, 40px)/1.5 "Newsreader", Georgia, ui-serif, serif; letter-spacing: -.01em; }
.gctmr-root *, .gctmr-root *::before, .gctmr-root *::after { box-sizing: border-box; }
.gctmr-m { position: relative; display: inline; padding: 0 .12em; margin: 0 -.12em; color: inherit; background: linear-gradient(var(--c), var(--c)) no-repeat 0 62% / 0% .78em; -webkit-box-decoration-break: clone; box-decoration-break: clone; transition: background-size .9s cubic-bezier(.65, 0, .35, 1); transition-delay: calc(var(--d) * 280ms + 200ms); }
.gctmr-root[data-in="true"] .gctmr-m { background-size: 100% .78em; }
.gctmr-m::selection, .gctmr-root ::selection { background: #1c1b19; color: #fff; }
@media (prefers-reduced-motion: reduce) { .gctmr-m { transition: none; background-size: 100% .78em; } }`;
const DEFAULT_PARTS: TextoMarcadorParte[] = ["Construimos software que ", { mark: "se adapta a tu operación", color: "#ffe066" }, ", no al revés. Primero entendemos cómo trabajas; después ", { mark: "lo automatizamos", color: "#9be7c4" }, " con trazabilidad desde el primer día."];
export function TextoMarcador({ parts = DEFAULT_PARTS, className = "", style }: TextoMarcadorProps) {
const ref = useRef<HTMLParagraphElement>(null);
const [seen, setSeen] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver(([entry]) => entry.isIntersecting && (setSeen(true), io.disconnect()), { threshold: 0.4 });
io.observe(el);
return () => io.disconnect();
}, []);
let n = 0;
return (
<p ref={ref} className={`gctmr-root ${className}`} style={style} data-in={seen}>
<style>{CSS}</style>
{parts.map((part, index) =>
typeof part === "string" ? (
part
) : (
<mark key={index} className="gctmr-m" style={{ "--c": part.color ?? "#ffe066", "--d": n++ } as CSSProperties}>
{part.mark}
</mark>
),
)}
</p>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/texto-marcadorEjemplo de uso de Texto con marcador
Después de copiar texto-marcador.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { TextoMarcador } from "./texto-marcador";
export default function Ejemplo() {
return <TextoMarcador />;
}Propiedades de TextoMarcador
TextoMarcador acepta 3 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
parts? | TextoMarcadorParte[] | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Personaliza Texto con marcador con variables CSS
Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.
| Variable | Valor por defecto |
|---|---|
--gctmr-fg | #1c1b19 |
Accesibilidad y rendimiento
El archivo tiene 50 líneas (2 KB) y no necesita @keyframes. Esto es lo que hace Texto con marcador por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Se pausa cuando sale de la pantalla para no gastar batería ni CPU.
- Es un componente de cliente de Next.js: incluye la directiva "use client".