El flujo para ajustar timing sin recompilar
Por qué las custom properties son el puente entre el navegador y el código, un panel de ajuste que puedes pegar en cualquier página, cómo persistir los cambios con overrides y espacio de trabajo, y cómo cerrar el ciclo hasta el repositorio.
El panel de animaciones deja arrastrar barras, pero solo de una en una y solo mientras la página no se recargue. Para afinar el movimiento de un producto entero hace falta otra cosa: un puente entre lo que ajustas en el navegador y lo que acaba en el repositorio. Ese puente existe, es barato de montar y consiste en que las duraciones y las curvas dejen de ser literales enterrados en cuarenta ficheros y pasen a ser variables que se pueden tocar en un solo sitio.
- Estructurar el movimiento en custom properties para poder ajustarlo desde el navegador.
- Montar un panel de ajuste con controles deslizantes que modifique la página en vivo.
- Persistir los cambios de las DevTools en disco con overrides y con el espacio de trabajo.
- Cerrar el ciclo llevando los valores acordados al código con una revisión trazable.
El puente son las variables
Si una duración está escrita como 280ms dentro de una regla de un componente, ajustarla implica encontrar esa regla, editarla, recargar y volver al estado. Si está escrita como var(--mov-media), ajustarla implica cambiar un valor en :root y ver cómo responde toda la aplicación a la vez, que es la única forma de juzgar la coherencia.
:root {
--mov-instantaneo: 90ms;
--mov-corta: 140ms;
--mov-media: 240ms;
--mov-larga: 400ms;
--mov-entrada: cubic-bezier(0, 0, 0.2, 1);
--mov-salida: cubic-bezier(0.4, 0, 1, 1);
--mov-estandar: cubic-bezier(0.4, 0, 0.2, 1);
--mov-enfasis: cubic-bezier(0.34, 1.56, 0.64, 1);
}
Con esto, el flujo de ajuste en el navegador es: selecciona el elemento html en el panel de elementos, y en el panel de estilos edita los valores de :root directamente. Cada cambio se aplica al instante en toda la página. Las curvas llevan su icono de editor al lado, así que puedes abrir el editor de Bézier desde ahí y arrastrar los puntos de control con la aplicación entera respondiendo.
Ese solo cambio de estructura —variables en lugar de literales— convierte el ajuste de movimiento de una tarea de código a una tarea de manipulación directa. Es, con diferencia, la inversión de mayor rentabilidad de todo este nivel.
Un panel de ajuste
Cuando quieres iterar de verdad, un panel con controles deslizantes es mejor que editar texto, porque puedes mover el valor mientras la animación se dispara una y otra vez. Este se pega en la consola de cualquier página y funciona sobre las variables que ya tengas.
(function panelDeMovimiento() {
const VARIABLES = [
["--mov-instantaneo", 0, 400, "ms"],
["--mov-corta", 0, 600, "ms"],
["--mov-media", 0, 900, "ms"],
["--mov-larga", 0, 1500, "ms"],
];
const raiz = document.documentElement;
const leer = (n) => parseFloat(getComputedStyle(raiz).getPropertyValue(n)) || 0;
const caja = document.createElement("div");
caja.style.cssText = `
position: fixed; inset-block-start: 12px; inset-inline-end: 12px; z-index: 2147483647;
background: #1e1e2e; color: #cdd6f4; font: 12px ui-monospace, monospace;
padding: 12px; border-radius: 8px; display: grid; gap: 8px; min-inline-size: 280px;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.4);
`;
for (const [nombre, min, max, unidad] of VARIABLES) {
const fila = document.createElement("label");
fila.style.cssText = "display:grid;grid-template-columns:1fr auto;gap:4px";
const valorActual = leer(nombre);
const titulo = document.createElement("span");
titulo.textContent = nombre;
const salida = document.createElement("output");
salida.textContent = valorActual + unidad;
const control = document.createElement("input");
control.type = "range";
control.min = min;
control.max = max;
control.step = 10;
control.value = valorActual;
control.style.gridColumn = "1 / -1";
control.addEventListener("input", () => {
raiz.style.setProperty(nombre, control.value + unidad);
salida.textContent = control.value + unidad;
});
fila.append(titulo, salida, control);
caja.append(fila);
}
const copiar = document.createElement("button");
copiar.textContent = "Copiar valores";
copiar.addEventListener("click", () => {
const texto = VARIABLES
.map(([n, , , u]) => ` ${n}: ${raiz.style.getPropertyValue(n) || leer(n) + u};`)
.join("\n");
navigator.clipboard.writeText(`:root {\n${texto}\n}`);
copiar.textContent = "Copiado";
setTimeout(() => (copiar.textContent = "Copiar valores"), 1200);
});
caja.append(copiar);
document.body.append(caja);
})();
El botón de copiar es la pieza que cierra el ciclo: cuando das con la combinación que funciona, te llevas el bloque :root completo al portapapeles y lo pegas en el código. Sin eso, acabas apuntando números en un papel y transcribiendo mal alguno.
Guárdalo como fragmento en el panel de fuentes —Sources, Snippets— y estará disponible en cualquier página con dos pulsaciones.
Persistir los cambios
Las DevTools ofrecen dos mecanismos para que lo que editas no se pierda al recargar, y hacen cosas distintas.
Overrides locales. En Sources, pestaña Overrides, activas la opción y eliges una carpeta del disco. A partir de ahí, cualquier cambio que hagas sobre un recurso servido por la red se guarda en esa carpeta, y el navegador sirve tu versión local en lugar de la del servidor en las recargas siguientes. Es la herramienta para trabajar sobre un sitio que no controlas, o sobre producción: puedes ajustar el CSS de un despliegue real, recargar tantas veces como quieras, y validar el cambio antes de escribir una sola línea en el repositorio.
Espacio de trabajo. En Sources, pestaña Filesystem, añades la carpeta del proyecto. El navegador empareja los ficheros servidos con los del disco, y entonces editar en el panel de estilos escribe directamente en tu fichero fuente. Es la opción para desarrollo local, y funciona bien cuando los ficheros que se sirven se parecen a los del disco. Con un pipeline que compila, minifica y añade prefijos, el emparejamiento falla o escribe en el sitio equivocado; con CSS servido tal cual, o con mapas de fuente bien generados, funciona perfectamente.
La combinación que mejor resultado da en la práctica: variables en :root en un fichero de tokens sin procesar, ese fichero emparejado con el espacio de trabajo. Ajustas en el navegador, se escribe en el fichero, tu servidor de desarrollo recarga, y el cambio ya está en el repositorio listo para revisión.
Un override activo sigue sirviendo tu versión modificada aunque el servidor cambie, y produce el bug más desconcertante posible: has arreglado algo en el servidor y el navegador sigue enseñando lo viejo. El indicador es un punto morado junto al fichero en el panel de fuentes. Cuando termines, borra los overrides o desactiva la opción.
Cerrar el ciclo
Un ajuste que se queda en el navegador no existe. El ciclo completo tiene cuatro pasos y ninguno se puede saltar.
Ajusta con la página real. No en un playground con un cuadrado gris sobre fondo blanco. La percepción del movimiento depende del contenido, del contraste y de la distancia recorrida, y un valor que funciona en un ejemplo aislado no funciona en tu interfaz.
Ajusta las variables, no los componentes. Si acabas necesitando una duración distinta para un componente concreto, eso es información: o el token es incorrecto para su familia, o ese componente pertenece a otra familia. Añadir un valor suelto es la forma en que un sistema de movimiento se degrada hasta desaparecer.
Copia el bloque completo. No los tres valores que cambiaste: el bloque entero. Los valores de movimiento se juzgan en relación unos con otros, y llevarse solo una parte rompe las proporciones que acabas de afinar.
Deja constancia de por qué. En el propio fichero de tokens, un comentario con la razón vale más que el número. “Media es 240 ms porque a 200 el desplazamiento de la hoja se lee como un salto y a 300 se percibe lento en uso repetido” es información que evita que alguien lo cambie dentro de seis meses sin saber lo que está deshaciendo.
El argumento habitual a favor de centralizar las duraciones en variables es el mantenimiento: un cambio en un sitio en lugar de en cuarenta. Es cierto y es la parte menos interesante. Lo que de verdad cambia es que hay una pregunta que solo se puede formular si los valores están juntos, y esa pregunta es la única que produce un sistema de movimiento coherente: ¿cuántas duraciones distintas necesita este producto y qué relación guardan entre sí? Con los valores repartidos por el código, esa pregunta es literalmente inaccesible. No es que sea difícil de contestar: es que no se puede ni plantear, porque nadie tiene delante la lista, y cada persona que escribe un componente elige un número mirando ese componente en aislamiento. El resultado predecible, comprobable en cualquier base de código con más de un año, es una distribución de duraciones con veinte valores entre 150 y 400 milisegundos, sin ningún patrón, donde la mitad de las diferencias son imperceptibles y la otra mitad son incoherencias visibles que nadie relaciona entre sí. Nadie tomó esa decisión; emergió de la ausencia de un sitio donde tomarla. En cuanto los valores están en un bloque de ocho líneas, la conversación aparece sola: alguien mira las ocho, ve que dos están a treinta milisegundos una de otra, y pregunta si de verdad hacen falta las dos. Esa pregunta es el sistema de movimiento. Por eso la refactorización de literales a variables no es una tarea de higiene que se pospone hasta que haya tiempo: es el requisito previo para que el diseño de movimiento pueda existir como actividad, y hasta que no se hace, todo lo demás —los tokens, la documentación, la revisión— no tiene dónde apoyarse.