Variables CSS
Propiedades personalizadas que permiten theming dinámico y mantenimiento centralizado.
SEMANA 05
Variables CSS, pseudo-clases, pseudo-elementos, transiciones, animaciones y efectos visuales.
Las variables CSS permiten almacenar valores que se reusan en toda la hoja de estilos. Se declaran con el prefijo -- y se leen con la función var().
Diferencia con variables de preprocesadores (SASS/LESS): las Custom Properties de CSS son en vivo: pueden cambiar con JavaScript, respetan la herencia del DOM y funcionan sin compilación.
:rootAl declarar variables en :root quedan disponibles en todo el documento. Es el patrón más común para colores, tipografías y espaciados globales.
:root {
--color-primario: #1a3a4a;
--color-secundario: #2d7d5f;
--espacio-md: 1rem;
--fuente-titulos: 'Georgia', serif;
}
/* Uso */
h1 {
color: var(--color-primario);
font-family: var(--fuente-titulos);
margin-bottom: var(--espacio-md);
}
Las variables CSS se heredan por el árbol DOM. Un descendiente puede redefinir la variable localmente, afectando solo a sí mismo y sus hijos.
/* Variable global en :root */
:root { --color-demo: navy; }
/* Sobreescritura en un elemento específico */
.hijo.verde { --color-demo: green; }
.hijo.ambar { --color-demo: orange; }
/* Los tres hijos leen la misma propiedad: var(--color-demo)
pero cada uno tiene un valor diferente */
.hijo { background: var(--color-demo); }
Si una variable no está definida, var() acepta un segundo argumento como valor de respaldo:
/* Si --color-inexistente no existe, usa el fallback */
background: var(--color-inexistente, red);
/* Fallback encadenado */
color: var(--color-a, var(--color-b, black));
:root {
--azul: #1a3a4a;
}
h1 { color: var(--azul); }
nav { background: var(--azul); }
.btn { border: 2px solid var(--azul); }
/* Cambiar un color: una sola línea */
/* Sin variables */
h1 { color: #1a3a4a; }
nav { background: #1a3a4a; }
.btn { border: 2px solid #1a3a4a; }
/* Cambiar: buscar y reemplazar N veces */
| Tipo de valor | Ejemplo de variable | Valor de ejemplo |
|---|---|---|
| Color | --color-primario | #1a3a4a |
| Longitud | --espacio-md | 1rem |
| Fuente | --fuente-titulos | 'Georgia', serif |
| Sombra | --sombra-md | 0 4px 12px rgba(0,0,0,0.1) |
| Tiempo | --duracion-media | 300ms |
| Número | --z-modal | 1000 |
Las pseudo-clases seleccionan un estado del elemento (:hover, :focus) o su posición en el DOM (:nth-child). Los pseudo-elementos seleccionan una parte del elemento o insertan contenido decorativo (::before, ::after).
Pasa el cursor, haz clic o escribe en el campo para ver los efectos.
.btn:hover {
background: green;
transform: translateY(-3px);
}
.input:focus {
border-color: green;
box-shadow: 0 0 0 3px ...;
}
.btn:active {
transform: translateY(3px);
box-shadow: 0 1px 0 ...;
}
li:not(.especial) {
opacity: 0.5;
}
input:focus { border-color: green; }
input:disabled { opacity: 0.5; cursor: not-allowed; }
input:required { border-left: 3px solid red; }
input:valid { border-color: green; }
input:invalid { border-color: red; }
Insertan contenido decorativo antes o después del contenido del elemento. Requieren la propiedad content (puede ser vacía: "").
Primera idea importante
Segunda idea importante
Tercera idea importante
p::before {
content: "→"; /* texto, emoji, o vacío "" */
position: absolute;
left: 0;
color: orange;
}
.cita::before {
content: "\201C"; /* " tipográfico */
font-size: 3rem;
color: orange;
}
.cita::after {
content: "\201D"; /* " tipográfico */
}
Selecciona este texto para ver el efecto ::selection personalizado. La primera letra usa ::first-letter para crear una letra capital decorativa, como en los manuscritos medievales.
Este párrafo tiene su primera línea estilizada con ::first-line: color verde y mayor espaciado entre letras. Este efecto se aplica de forma inteligente según el ancho real del contenedor.
| Pseudo | Qué selecciona | Caso de uso |
|---|---|---|
::before | Antes del contenido | Iconos, decoraciones, contadores |
::after | Después del contenido | Tooltips, clearfix, decoraciones |
::placeholder | Texto de ayuda en inputs | Estilo del hint de formularios |
::selection | Texto seleccionado | Branding en la selección de texto |
::first-letter | Primera letra | Letra capital en artículos |
::first-line | Primera línea visible | Énfasis tipográfico en párrafos |
La propiedad transition anima automáticamente el cambio entre dos estados CSS. Solo necesitas definir el estado inicial y el estado final (:hover, :focus, etc.) y el navegador calcula los fotogramas intermedios.
/* Sintaxis completa */
transition: propiedad duración timing-function retraso;
/* Ejemplos */
transition: background 300ms ease;
transition: transform 600ms cubic-bezier(0.34, 1.56, 0.64, 1);
transition: all 200ms ease-in-out;
/* Múltiples propiedades */
transition:
background 300ms ease,
transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 300ms ease;
Pasa el cursor sobre cada caja para comparar las curvas:
Cada propiedad puede tener su propia curva y duración:
.trans-multiple {
transition:
background 300ms ease,
transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 300ms ease,
border-radius 300ms ease;
}
.trans-multiple:hover {
background: green;
transform: rotate(-3deg) scale(1.05);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
border-radius: 999px;
}
Pasa el cursor sobre el grupo para ver el efecto de entrada escalonada:
.item:nth-child(1) { transition-delay: 0ms; }
.item:nth-child(2) { transition-delay: 80ms; }
.item:nth-child(3) { transition-delay: 160ms; }
.item:nth-child(4) { transition-delay: 240ms; }
.item:nth-child(5) { transition-delay: 320ms; }
@keyframesMientras las transiciones requieren un cambio de estado externo (hover, focus), las animaciones se reproducen de forma autónoma. Se definen con @keyframes y se aplican con la propiedad animation.
/* 1. Definir la animación */
@keyframes nombre {
from { transform: scale(1); }
to { transform: scale(1.1); }
}
/* Con porcentajes para mayor control */
@keyframes rebotar {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
/* 2. Aplicar la animación */
.elemento {
animation: nombre 1.5s ease-in-out infinite;
/* nombre | duración | timing | iteración */
}
| Propiedad | Valores comunes | Descripción |
|---|---|---|
animation-name | nombre del @keyframes | Qué animación usar |
animation-duration | 1s, 300ms | Duración total |
animation-timing-function | ease, linear, cubic-bezier | Curva de velocidad |
animation-delay | 0ms, 200ms | Retraso antes de iniciar |
animation-iteration-count | 1, 3, infinite | Número de repeticiones |
animation-direction | normal, reverse, alternate | Dirección de reproducción |
animation-fill-mode | none, forwards, backwards, both | Estado antes/después |
animation-play-state | running, paused | Reproducir o pausar |
fill-mode: both aplica los estilos del primer fotograma antes de que inicie y mantiene los del último al terminar. Ideal para animaciones de entrada que se ejecutan una sola vez:
@keyframes deslizarDesdeIzq {
from { opacity: 0; transform: translateX(-30px); }
to { opacity: 1; transform: translateX(0); }
}
.item { animation: deslizarDesdeIzq 600ms ease both; }
.item:nth-child(2) { animation-delay: 150ms; }
.item:nth-child(3) { animation-delay: 300ms; }
/* Cada letra es un con animation-delay distinto */
.letra { animation: ondaTexto 1.2s ease-in-out infinite; }
.letra:nth-child(1) { animation-delay: 0ms; }
.letra:nth-child(2) { animation-delay: 80ms; }
/* ... */
Pasa el cursor sobre el spinner para pausarlo:
.contenedor:hover .spinner {
animation-play-state: paused;
}
Accesibilidad: siempre incluye @media (prefers-reduced-motion: reduce) para respetar la preferencia del sistema operativo del usuario. Las animaciones excesivas pueden causar malestar a personas con trastornos vestibulares.
transform mueve, escala, rota o inclina un elemento sin afectar el espacio que ocupa en el layout. El resto de elementos no se mueven. Por eso es ideal para animaciones (y más eficiente que animar top/left).
Pasa el cursor sobre cada caja para ver la transformación:
/* transform-origin cambia el punto de pivote */
.elemento {
transform-origin: center center; /* por defecto */
transform-origin: top left;
transform-origin: bottom right;
transform-origin: 25% 75%;
}
/* Combinar transformaciones (el orden importa) */
transform: rotate(45deg) scale(1.2) translateX(10px);
Pasa el cursor sobre cada caja para ver el filtro aplicado:
/* Filtros individuales */
filter: blur(4px);
filter: brightness(150%);
filter: grayscale(100%);
/* Filtros combinados */
filter: brightness(1.2) contrast(1.1) saturate(1.3);
/* Truco hover en imágenes */
img { filter: grayscale(80%); transition: filter 300ms ease; }
img:hover { filter: grayscale(0%); }
backdrop-filter aplica filtros al contenido detrás del elemento. Requiere que el elemento tenga fondo semitransparente.
backdrop-filter: blur(12px)
.tarjeta-vidrio {
background: rgba(255, 255, 255, 0.15); /* semitransparente */
backdrop-filter: blur(12px) saturate(1.8);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
}
Este componente combina todos los conceptos de la semana: variables CSS locales por tarjeta, pseudo-elementos para decoración, transiciones suaves, animaciones de entrada y efectos transform.
Conceptos aplicados: variables CSS con --tarjeta-acento sobreescrita por tarjeta · ::before para el borde superior · ::after para el círculo de fondo · animation: aparecer-tarjeta con delay escalonado · transition: transform + box-shadow en hover · :nth-child para colores distintos · :hover en hijos descendientes.
Propiedades personalizadas que permiten theming dinámico y mantenimiento centralizado.
Selección de estados y posiciones sin agregar clases o markup extra al HTML.
Cambios de estado suaves y fluidos con control total sobre la curva de velocidad.
Secuencias de fotogramas clave que se ejecutan de forma autónoma, sin JavaScript.
/* Variable local por tarjeta → sobreescribe la global */
.tarjeta:nth-child(1) { --tarjeta-acento: navy; }
.tarjeta:nth-child(2) { --tarjeta-acento: green; }
.tarjeta:nth-child(3) { --tarjeta-acento: orange; }
/* ::before → borde de color superior */
.tarjeta::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; height: 4px;
background: var(--tarjeta-acento);
}
/* ::after → círculo decorativo de fondo */
.tarjeta::after {
content: "";
position: absolute; bottom: -30px; right: -30px;
width: 100px; height: 100px;
background: var(--tarjeta-acento);
border-radius: 50%;
opacity: 0.07;
transition: transform 600ms cubic-bezier(0.34,1.56,0.64,1);
}
/* Hover: eleva la tarjeta y expande el círculo */
.tarjeta:hover { transform: translateY(-6px); }
.tarjeta:hover::after { transform: scale(2.5); opacity: 0.12; }
/* Animación de entrada con delay escalonado */
.tarjeta { animation: aparecer 600ms ease both; }
.tarjeta:nth-child(2) { animation-delay: 100ms; }
.tarjeta:nth-child(3) { animation-delay: 200ms; }
.tarjeta:nth-child(4) { animation-delay: 300ms; }