Ir al contenido
Evaluar mi proyecto
Componentes · Texto y animacionesDisponible

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.

Demostración interactiva

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

texto-marcador.tsx50 líneas
Descargar
"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.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/texto-marcador

Ejemplo de uso de Texto con marcador

Después de copiar texto-marcador.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
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.

PropiedadTipoDescripción
parts?TextoMarcadorParte[]Opcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

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.

VariableValor 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".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)