La regla @function en CSS

Funciones reutilizables en CSS


De forma tradicional, en CSS no teníamos la capacidad de crear funciones como en los lenguajes de programación. Sin embargo, eso está cambiando gracias a la regla @function de CSS.

La regla @function

Mediante la regla @function podemos definir funciones reutilizables que podremos usar en nuestro código CSS, pasándole parámetros y devolviendo un valor como resultado.

Como puedes ver en el siguiente ejemplo, las funciones se definen con la sintaxis de las variables CSS, prefijadas con dos guiones. Por ejemplo, la función --darken es una función que oscurece el color que le pasamos por parámetro --value:

<div class="box"></div>

<style>
@function --darken(--value) {
  result: color-mix(in srgb, var(--value), black 50%);
}

.box {
  width: 100px;
  height: 100px;
  background: indigo;
  transition: background 1s;
}

.box:hover { background: --darken(indigo) }
</style>

En este caso, se trata de un ejemplo muy sencillo, pero aún así se puede ver como hace que el código CSS sea mucho más legible si utilizamos nombres de funciones apropiadas. Observa que :hover hace que al pasar el ratón por encima del elemento, se aplique la función --darken, a la que le pasamos el color que queremos oscurecer. Esto, mediante el result de la función CSS, devuelve el color oscurecido mediante la función color-mix, simplificando su uso en el código.

Otro ejemplo

Ahora que ya conocemos la funcionalidad básica de @function, veamos otro ejemplo donde trabajamos con valores numéricos, lo que lo hace diferente al ejemplo anterior:

@function --negative(--value) {
  result: calc(-1 * var(--value));
}

.box {
  --offset: 50px;

  translate: var(--offset) --negative(var(--offset));   /* 50px -50px */
}

Este ejemplo toma un valor numérico por parámetro y luego, result: lo devuelve multiplicado por -1, es decir, devolviendo su valor con signo cambiado. Luego, en la propiedad translate lo utilizamos para invertir el signo de la variable --offset.

Observa que cuando usamos una variable, utilizamos la función var(). Sin embargo, con las @function no aplica. Sólo añadimos los paréntesis al final y le pasamos los parámetros (si los tiene).

Parámetros de la función

Vamos a centrarnos durante un momento en los parámetros de nuestra función y a observar las diferentes personalizaciones de las que podemos dotarlos.

Parámetros con valor por defecto

Es posible que nos interese indicar valores por defecto a los parámetros de nuestra función, de forma que si al llamar a la función, no le indicamos unos parámetros, use los valores por defecto.

La forma de hacerlo sería como se puede ver en este ejemplo:

@function --gradient(--color1: indigo, --color2: rebeccapurple) {
  result: linear-gradient(75deg, var(--color1), var(--color2));
}

.box {
  background: --gradient(blue, red);   /* --gradient(blue, red) */
  background: --gradient(green);       /* --gradient(green, rebeccapurple) */
  background: --gradient();            /* --gradient(indigo, rebeccapurple) */
  background: --gradient(red, 10px);   /* Se ignora */
}

Especial atención al último ejemplo, donde si añadimos un valor que no es adecuado (se esperan colores, no tamaños), se intentará aplicar 10px al linear-gradient() y como es un valor no válido se ignora el background.

Ojo que la @function es correcta. Lo anterior ocurre debido al funcionamiento de CSS. Si le pasas un 10px a una propiedad background, simplemente se ignora porque no es válido. Sin embargo, en la siguiente pestaña veremos como cambiar ese comportamiento, que en funciones podría ser algo más apropiado.

Parámetros con tipo de dato

Opcionalmente, podemos definir el tipo de dato de los parámetros de nuestra función añadiendo la función type() al final del parámetro.

La siguiente función --half-size nos devolverá la mitad del valor que le pasemos por parámetro:

@function --half-size(--size type(<length> | <percentage>)) {
  result: calc(var(--size) / 2);
}

Observa, que tras el parámetro --size hemos añadido type(<length> | <percentage>). Esto significa que nuestro parámetro --size puede ser:

  • 1️⃣ De tipo «longitud» (px, etc...)
  • 2️⃣ De tipo «porcentaje» (%)

Respecto a la función type(), solo es necesaria si necesitamos indicar más de un tipo de dato. Si en nuestra función CSS sólo requerimos un tipo de dato (por ej: length), podríamos definir el tipo de dato de dos formas:

  • 1️⃣ El que ya hemos visto: type(<length>)
  • 2️⃣ Sin usar type(), de una forma compacta: <length>
@function --half-size(--size <length>) {
  result: calc(var(--size) / 2);
}

Definir los tipos de datos en CSS es algo muy útil cuando necesitamos que el navegador comprenda que tipo tiene una variable CSS, para poder actuar en consecuencia, como por ejemplo, al crear animaciones con variables CSS.

Si quieres saber más sobre tipos de datos en CSS, o que tipos de datos puedes utilizar, te aconsejo echar un vistazo a la tabla del artículo la propiedad @property.

Asegurar parámetros con tipos

Observa el siguiente ejemplo. Hemos retomado el ejemplo anterior, pero ahora vamos a asegurar con tipos de datos los parámetros --color1 y --color2. Además, de forma opcional podemos añadirle valores por defecto:

@function --gradient(--color1 <color>: indigo, --color2 <color>: rebeccapurple) {
  result: linear-gradient(75deg, var(--color1), var(--color2));
}

.box {
  background: --gradient(green, yellow);  /* Usa --gradient(green, yellow) */
  background: --gradient();               /* Usa --gradient(indigo, rebeccapurple) */
  background: --gradient(red, 10px);      /* Usa --gradient(red, rebeccapurple) */
  background: --gradient(default, red);   /* Usa --gradient(indigo, red) */
}

Observa especialmente, los dos últimos ejemplos. En esta ocasión, como hemos definido que la función debe aceptar sólo colores por parámetro, en el caso de no cumplirse esta restricción, se descartará ese parámetro y se utilizará el valor por defecto designado. De esta forma no se ignora la propiedad.

En el último ejemplo, el primer parámetro tiene el valor default (que no existe), por lo que descarta y se utilizará el color por defecto. En el segundo parámetro si hemos definido un color válido, por lo que se utilizará.

Valor devuelto de la función

Ya hemos visto que el valor devuelto por la funcion en @function se hace con la propiedad result, que es una especie de return de programación. Sin embargo, también podemos definir, al cerrar los paréntesis de los parámetros, un returns indicando el tipo de dato que se espera que devuelva la función.

Veámoslo con un ejemplo:

@function --gradient(--color1, --color2) returns type(<image>) {
  result: linear-gradient(75deg, var(--color1), var(--color2));
}

Nuestra función espera dos parámetros: --color1 y --color2 (que también podrían estar tipados, no lo he hecho para simplificar el ejemplo). Esta función toma esos dos colores y los convierte en un gradiente linear-gradient(), que es lo que devuelve la función.

En CSS los gradientes tienen el tipo de dato <image>, ya que son imágenes prerenderizadas por el navegador. En este caso, definimos returns type(<image>) para determinar que la función espera que se devuelva una imagen. Por defecto, si no definimos returns, se asume el tipo returns type(<*>).

Funciones condicionales

Las funciones de CSS, combinadas con las reglas @media nos permiten crear funciones condicionales muy potentes, que pueden ser muy interesantes para definir los estilos de nuestras páginas.

Por ejemplo, observa la siguiente función --my-font-size, la cuál no tiene ningún parámetro. Esta función devuelve el valor 1rem. Sin embargo, luego tiene definido un @media query que nos indica que en el caso de que la pantalla o dispositivo sea más grande de 1000px, devolverá el valor 2rem:

@function --my-font-size() {
  result: 1rem;

  @media (width > 1000px) {
    result: 2rem;
  }
}

De esta forma, podemos hacer un poco más flexibles y autónomas nuestras funciones.

Ten mucho cuidado con el orden cuando usas múltiples result en una @function. En CSS no puedes hacer early returns, ya que se aplican las reglas de CSS de herencia. Si en el ejemplo anterior, ponemos el @media antes del primer result, el valor devuelto por la función siempre será 1rem, ya que el segundo result se aplica por herencia y sobreescribe al anterior.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev