Skip to content

Transiciones CSS

Posted on:23 de febrero de 2020

Este artículo es parte de una serie de artículos dedicados a la Animación Web.

Aquí encontrarás una explicación teórica y detallada de las Transiciones CSS, si quieres ver ejemplos de implementación y casos de uso, echa un vistazo al vídeo.

Cambios de estado de propiedades CSS

Normalmente, cuando cambiamos el valor de una o varias propiedades CSS, el resultado representado por el navegador se actualiza instantáneamente. Lo podemos ver en el siguiente ejemplo, donde los valores de width y box-shadow cambian inmediatamente al poner el cursor sobre el logo, o un tap si estás en un dispositivo táctil.

Las transiciones son un efecto de presentación. El motor de renderizado del navegador hace una interpolación, calculando los valores intermedios entre un valor y otro.

Aquí tenemos el mismo ejemplo aplicando la transición.

Así funciona la transición

Si tenemos un elemento en nuestra página que queremos desplazar 200px a la derecha, lo podemos hacer con la propiedad transform.

En el ejemplo usaremos un poco de Javascript para añadir la interacción, en este caso añadiremos una clase para indicarle al elemento su nuevo estado. Podríamos hacer esto mismo sin Javascript, con un checkbox y combinación de selectores CSS, pero yo creo que Javascript nos sirve para añadir la interacción con las usuarias. Pero si hay interés ya veremos en un artículo de CSS cómo hacer este tipo de cosas sin Javascript.

Como hemos visto, la propiedad transition indica al navegador que haga la interpolación. El navegador intentará, según los recursos disponibles, actualizar el renderizado de la página a 60 fotogramas por segundo.

Si tenemos en cuenta que el desplazamiento es de 200px en un tiempo de 0.5 segundos, en un caso ideal de 60 fotogramas/segundo, la interpolación será de 30 fotogramas. El navegador hará el cálculo para la interpolación: 200px/30fps = 6.666px, así que desplazará el elemento 6.666px a la derecha en cada ciclo de la animación.

En la siguiente imagen podemos ver la representación que hace Chrome en el Developer Tools de la transición.

Developer Tools: Transición

Tenemos una representación gráfica del número de fotogramas en la línea de tiempo, y también podemos ver el tiempo que ha tardado en hacer la transición 502,29 ms.

En las herramientas de desarrollos, tanto Chrome como Firefox, tenemos mucha información y herramientas. Ya lo analizaremos en detalle en un futuro artículo, y conocer cómo las podemos utilizar para depurar nuestras animaciones o analizar las animaciones de otras páginas web (se aprende mucho analizando el desarrollo de otras personas).

Propiedades CSS para las transiciones

Analicemos en detalle cada una de las propiedades CSS que tenemos para definir y controlar las transiciones.

transition-property

En esta propiedad podemos definir la propiedad CSS a la que queremos aplicar la transición.

Si queremos aplicar una transición entre el cambio de color de fondo de un elemento, deberíamos definirlo de la siguiente forma: transition-property: background-color.

Si echamos un vistazo a la documentación de la W3C veremos la siguiente tabla.

Valuesnone OR [ all OR <property-name> ]#
Initial valueall
Applies toAll elements and :before and :after pseudo-elements
Computed valueAs specified
InheritedNo
AnimatableNo

Esta tabla la veremos en casi todas la propiedades CSS en la documentación oficial de la W3C, nos informa de los posibles valores que podemos definir para esa propiedad, el valor inicial que tiene si no definimos uno nosotras, a los elementos que aplica, el valor calculado, si hereda el valor del elemento ancestro y si es un valor que se puede animar.

Un valor destacable de esta información es el valor inicial, all lo que hace que podamos obviar la definición de la propiedad que queremos animar. Otra información interesante es el hashtag # que vemos al final de los valores, esto indica que la propiedad admite una lista de valores separados por comas.

Así que podríamos definir lo siguiente para hacer referencia a varias propiedades.

.logo {
  transition-property: width, box-shadow;
}

Dedicaré un artículo a la performance en la animación, y analizaremos si tiene impacto definir las propiedades que queremos animar de forma explícita en lugar de dejar el valor predeterminado all.

transition-duration

Con la propiedad transition-duration definimos el tiempo de la transición, o transiciones si definimos una lista separada por comas (aquí también podemos ver el #). Acepta valores positivos con unidades de segundos (s) o milisegundos (ms).

Values<time>#
Initial value0s
Applies toAll elements and :before and :after pseudo-elements
Computed valueAs specified
InheritedNo
AnimatableNo

Si definimos un valor en negativo, el navegador ignora la regla y los cambios se aplicarían sin ninguna transición.

Negative values

transition-timing-function

Con la propiedad transition-timing-function tenemos la opción de definir las funciones de tiempo. Se trata de una serie de valores: ease, linear, ease-in, ease-out, ease-in-out, step-start, step-end, steps(n, start) predefinidos de funciona de Curvas de Bécier cubic-bezier(x1, y1, x2, y2).

Las Curvas de Bézier merecen un artículo específico y detallado, ya que tenemos una especificación dedicada a su definición, CSS Easing Functions Leve 1, así que lo trataremos en futuros artículos analizando los aspectos técnicos y casos de uso de su implementación.

Aquí tenemos la tabla con la información básica.

Values<timing-function>#
Initial valueease
Applies toAll elements and :before and :after pseudo-elements
Computed valueAs specified
InheritedNo
AnimatableNo

Podemos ver que el valor por defecto es ease que equivale a la Curva de Bézier cubic-bezier(0.25, 0.1, 0.25, 1).

ease function

Esta es su representación gráfica. Como ya he comentado, veremos todas con más detalle en un futuro artículo.

transition-delay

Como su propio nombre indica, la propiedad transition-delay nos permite definir un valor para indicar el timpo de espera antes de empezar la transición.

La tabla de valores es idéntica a la de transition-duration.

Values<time>#
Initial value0s
Applies toAll elements and :before and :after pseudo-elements
Computed valueAs specified
InheritedNo
AnimatableNo

Esta propiedad sí que permite definir un valor negativo. Por ejemplo, si tenemos un valor de -250ms en una transición de una duración de 500ms la animación tendrá la mitad del recorrido. Y si el valor negativo es superior a su equivalente positivo de transition-duration no obtendremos una transición, sino un cambio inmediato entre los dos estados.

Aquí podemos ver un ejemplo de definir un valor negativo en la propiedad transition-delay.

transition (shorthand)

Muchas de las propiedades CSS tienen un shorthand (abreviado). Nos permite utilizar una propiedad con múltiples valores, veamos un ejemplo que creo que es mucho más fácil de entender.

/* Transición con múltiples propiedades */
.logo {
  transition-property: width;
  transition-duration: 0.5s;
  transition-timing-function: ease-out;
  transition-delay: 0.25s;
}

/* Transición con shorthand */
.logoJL {
  transition: width 0.5s ease-out 0.25s;
}

En el código anterior, las dos clases aplicarían la misma transición. Cuando definimos una transición como shorthand en nuestro código, si la inspeccionamos con el DevTools podremos verlo así.

DevTools: Transición abreviada

Podemos ver que hay un triángulo, nos indica que hay más información disponible en esa propiedad CSS. Si hacemos clic en él, podremos observar que internamente el navegador las interpreta por separado.

DevTools: Transición abreviada desplegada

Como en las propiedades anteriores, en transition también tenemos la tabla informativa de la especificación.

Values<single-transition>#
Initial valueall 0s ease 0s
Applies toAll elements and :before and :after pseudo-elements
Computed valueAs specified
InheritedNo
AnimatableNo

<single-transition> = [ [ none ‖ <transition-property> ] ‖ <time> ‖ <transition-timingfunction> ‖ <time> ]#

En los ejemplos que hemos ido viendo he estado utilizando la transición como shorthand, ya que es lo más habitual.

Trabajando con transiciones

Valores por defecto

A la hora de trabajar con transiciones hay que entender cómo el navegador hace la interpretación de la sintaxis.

El valor por defecto de transition-property o como valor en el shorthand transition es all, eso quiere decir que la transición que apliquemos afectará a todas la propiedades (que lo permitan) de la clase CSS donde lo estemos indicando.

.logo {
  transition-property: all;
  transition-duration: 250ms;
  transition-timing-function: ease;
}

.logoJL {
  transition: all 250ms ease;
}

En el código anterior las dos clases hacen lo mismo, pero hay un detalle que debemos tener en cuenta. Si miramos cúales son los valores por defecto de cada una de esas propiedades podremos ver que estamos definiendo valores que el navegador ya los tiene por defecto.

.logo {
  transition-duration: 250ms;
}

.logoJL {
  transition: 250ms;
}

Este código tendrá exactamente el mismo comportamiento, aunque no le hayamos definido valores a transition-property y transition-timing-function.

Por temas de semántica y posterior interpretación del código, ya sea por nosotras mismas en el futuro, o por otra persona, lo habitual es (como mínimo) encontrarnos con el valor de la transition-property definido.

Transiciones múltiples

Ya lo hemos visto desde el primer ejemplo del artículo.

.logo {
  ... transition: width 0.25s, box-shadow 0.25s;
  ...;
}

Hemos definido transiciones de forma independiente para width y box-shadow. En este caso lo podríamos haber hecho con un valor de all .25s, tendría el mismo efecto. Pero hacerlo de forma separada nos permite poder definir diferentes tiempos, retardo o funciones de tiempo a cada una de las propiedades.

.logo {
  ... transition-property: width, box-shadow;
  transition-duration: 0.25s, 250ms;
  transition-timing-function: ease, ease-in-out;
  ...;
}

.logoJL {
  ... transition: width 0.25s, box-shadow 0.25s ease-in-out;
  ...;
}

A modo de ejemplo he utilizado las unidades en segundo y mili segundos, para mostrar que pueden convivir sin problemas.

En el caso de que definamos varias propiedades, donde el número de propiedades es mayor al número de valores en la propiedad duración, este se repite.

En el siguiente código los valores de transition-duration y transition-timing-function se repetirán hasta coincidir con el número de valores de transition-property.

.logo {
  ... transition-property: width, box-shadow, color, padding, border-radius;
  transition-duration: 0.25s, 250ms;
  transition-timing-function: ease, ease-in-out;
  ...;
}

Quedando así:

.logo {
  ... transition-property: width, box-shadow, color, padding, border-radius;
  transition-duration: 0.25s, 250ms, 0.25s, 250ms, 0.25s;
  transition-timing-function: ease, ease-in-out, ease, ease-in-out, ease;
  ...;
}

CSS Transtions Canonical Order

El orden de los valores

Como la propia documentación de la W3C nos indica, el orden de los valores en la propiedad transition es importante. Como hemos visto en las tablas más arriba nos dicen <single-transition> = [ none | <single-transition-property> ] || <time> || <timing-function> || <time>. Eso quiere decir que el navegador está esperando: none o una propiedad, seguido de la duración, la función de tiempo y por último el tiempo de retardo.

He estado probando varias combinaciones, el navegador es lo suficientemente inteligente como para saber corregir un error en el orden.

.logo {
  transition: ease-in opacity 250ms 3s;
}

En el código anterior el navegador será capaz de interpretar la transición a pesar de que los valores no estén ordenados siguiendo el estándar. Incluso si el primer valor de tiempo es negativo, interpreta que estamos indicando el valor de transition.delay ya que en transition-duration no podemos tener un valor negativo. Os invito a que probéis combinaciones para entender cómo funciona.

¿Cómo funcionan por dentro las transiciones?

Más allá de saber cómo definir nuestras transiones para poder añadir animaciones e interacciones, podemos ver en los códigos de los navegadores cómo están aplicando lo que podemos ver en las especificaciones de la W3C, que de hecho es una documentación para los desarrolladores de los navegadores, todo sea dicho.

Tenemos la suerte de tener disponible el código fuente, de la mayoría de los navegadores, accesible.

  • Chromium el motor utilizado en Google Chrome, Opera y recientemente por el navegador de Microsoft
  • Geko el motor que utiliza Mozilla para Firefox.
  • Webkit fue el motor de Chrome y Opera, pero ahora solo lo utiliza Safari.

Es curioso ver código como el siguiente, donde podemos ver que justamente cómo crea la lista de valores de una transición en modo shorthand transition.

const CSSValue* Transition::CSSValueFromComputedStyleInternal(
    const ComputedStyle& style,
    const SVGComputedStyle&,
    const LayoutObject*,
    bool allow_visited_style) const {
  const CSSTransitionData* transition_data = style.Transitions();
  if (transition_data) {
    CSSValueList* transitions_list = CSSValueList::CreateCommaSeparated();
    for (wtf_size_t i = 0; i < transition_data->PropertyList().size(); ++i) {
      CSSValueList* list = CSSValueList::CreateSpaceSeparated();
      list->Append(*ComputedStyleUtils::CreateTransitionPropertyValue(
          transition_data->PropertyList()[i]));
      list->Append(*CSSNumericLiteralValue::Create(
          CSSTimingData::GetRepeated(transition_data->DurationList(), i),
          CSSPrimitiveValue::UnitType::kSeconds));
      list->Append(*ComputedStyleUtils::CreateTimingFunctionValue(
          CSSTimingData::GetRepeated(transition_data->TimingFunctionList(), i)
              .get()));
      list->Append(*CSSNumericLiteralValue::Create(
          CSSTimingData::GetRepeated(transition_data->DelayList(), i),
          CSSPrimitiveValue::UnitType::kSeconds));
      transitions_list->Append(*list);
    }
    return transitions_list;
  }

  CSSValueList* list = CSSValueList::CreateSpaceSeparated();
  // transition-property default value.
  list->Append(*CSSIdentifierValue::Create(CSSValueID::kAll));
  list->Append(
      *CSSNumericLiteralValue::Create(CSSTransitionData::InitialDuration(),
                                      CSSPrimitiveValue::UnitType::kSeconds));
  list->Append(*ComputedStyleUtils::CreateTimingFunctionValue(
      CSSTransitionData::InitialTimingFunction().get()));
  list->Append(
      *CSSNumericLiteralValue::Create(CSSTransitionData::InitialDelay(),
                                      CSSPrimitiveValue::UnitType::kSeconds));
  return list;
}

Conclusiones

Las transiciones fueron el primer paso para tener animaciones en la web, son tan básicas como potentes. Nos permiten definir unas animaciones muy optimizadas. Combinando varias propiedades, tiempos de duración y tiempos de retardo podemos hacer pequeñas animaciones, pero cuando necesitamos hacer animaciones con mas de dos estados ya tenemos que pasar a utilizar las propiedades animation junto con @keyframes, pero eso lo veremos en el siguiente artículo de esta serie.