spinner

De padres a hijos: Custom properties en producción (Parte I)

En los proyectos en los que trabajamos en BBVA Next Technologies, nos hemos encontrado con la posibilidad de poder usar custom properties, propiedades personalizadas de CSS, a la hora de definir los estilos para nuestros componentes y en este artículo os contamos cómo utilizarlos.

*Este artículo es la primera parte de «De padres a hijos: Custom properties en producción (Parte II)»

En nuestro trabajo diario hemos detectado diferentes patrones, tanto por repetición como por generación de una cantidad importante de clases CSS para definir un componente, que nos han servido para aprovechar todo el poder de estas variables y disminuir en código, sobreescritura de propiedades y en herencias con especificidades “imposibles”.

Generación de un componente “progress-bar”

En muchas ocasiones nos vemos con una tarea para crear algún tipo de gráfica en nuestro backlog, más concretamente una gráfica de progreso, ya sea de barra o incluso de donut. Si no estamos usando ninguna librería de gráficas y las estamos haciendo totalmente desde cero nos daremos cuenta de que tenemos varias posibilidades.

Una de ellas es asignar el valor en porcentaje de nuestro progreso directamente inyectando estilos por medio de JavaScript, ya sea cambiando el valor del “width” una de nuestras capas, cambiando el valor de un “translate” o incluso cambiando el valor de nuestro “stroke-dashoffset”.

Otra es generar en CSS una clase o declaración por cada uno de los valores porcentuales y cambiar este valor (por ejemplo un atributo “data-value”) desde JavaScript, lo que en la mayoría de casos nos va a generar 100 clases o declaraciones para cubrir todas estas posibilidades.

Para minimizar este impacto y no tener que inyectar sentencias completas desde JavaScript o generar tantas clases en nuestro CSS vamos a usar las custom properties de la siguiente forma por ejemplo:

CSS

.my-chart-value {
 width: var(--my-chart-percent, 0);
 }

JS

document.querySelector(‘.my-chart-value').style.setProperty(‘--my-chart-percent’, myValue);

Haciendo la inyección directamente desde JavaScript leyendo el valor del input o leyendo el dato de nuestro servicio a la custom property nos ahorramos todas esas declaraciones CSS y reducimos la inyección de código “style” en nuestro DOM al mínimo.

Herencia hacia los padres

En ocasiones nos vemos en la situación de tener un elemento que por necesidades del diseño y limitaciones técnicas, tiene una estructura determinada. En esos elementos también podemos tener una serie de interacciones que hacen imposible generarlas por medio de herencia en CSS.

Como ejemplo de esto tenemos un input “falseado” con otra serie de elementos HTML ya que estilar este tipo de elementos está un poco restringido en CSS. Nuestro input tiene una interacción marcada, al hacer foco debe cambiar el estilo de un borde definido. Este borde no forma parte del input en sí o de un elemento “hermano” del mismo, si no que forma parte del “padre”.

Para poder hacer esta interacción necesitamos hacer uso de JavaScript, ya sea inyectando un “style” en el elemento o añadiendo una clase al elemento “padre” detectando ese evento “focus” en nuestro input. Vamos a reducir el impacto haciendo uso de nuestras custom properties.

JS

$(".form__input").focus(function(){
 $($(this).parents(".form")).get(0).style.setProperty('--form_border-color', '--my-color');
 });

De esta forma vamos a poder asignar a nuestra custom property de nuestro componente que define el color del borde, otra custom property que tenemos declarada en nuestro “:root” y cambiar su valor. Podríamos asignar directamente un color en código hexadecimal por ejemplo, pero de esta forma podemos controlar su valor directamente en nuestra CSS, así si un día esa variable cambia de valor, no tendremos que buscar el color en el código JavaScript.

Interacción entre CSS y JavaScript

Hay veces en las que hemos echado de menos el poder conocer y trabajar con eventos en el DOM desde la parte de CSS. Hasta ahora teníamos que tener en cuenta de antemano todas los posibles valores y definirlos en CSS, ya que era un lenguaje “estático”. Con las custom properties hemos dado un paso muy importante en la dirección de poder llevar más de la mano un lenguaje de “tiempo de ejecución” como es JavaScript y un lenguaje de “tiempo de compilación” como es CSS.

Imaginemos que estamos modificando el aspecto visual de un “input type=range”. Para modificar su aspecto según las necesidades de diseño nos vemos obligados a crear elementos que no son propios de este elemento HTML, ya que tiene ciertas limitaciones a la hora de poder modificarlos.

Para ponernos en situación, el funcionamiento de un “input type=range” es el siguiente: Tenemos un carril o “track” por el que se desplaza un manejador con el que seleccionamos un valor entre una serie de posibilidades. Este manejador toca con su parte lateral cada uno de los extremos de nuestro “track” y cuando el valor elegido está en el centro de este “track”, también lo está el centro de nuestro manejador, es decir, si nuestro manejador fuese un cuadrado de 10px de ancho, en nuestra posición “0” el lateral izquierdo de nuestro cuadrado coincidiría con nuestro lateral izquierdo del “track”, en nuestra posición “100%” el lateral derecho de nuestro cuadrado coincidiría con el lateral derecho de nuestro “track” y en la posición “50%” nuestro indicador virtual se encontraría en la posición “5px” de nuestro cuadrado.

A la hora de dar estilos a nuestro “track”, la manera más efectiva puede ser definir un “linear-gradient” en CSS que tenga 2 colores (uno para parte completa y otro que haría de parte por completar), haciendo que el primer color pinte un gradiente desde la posición 0% hasta el porcentaje completado, y el segundo color iría desde ese porcentaje de completado hasta el 100%.

CSS

.my-track {
 background-image: linear-gradient(to right, var(--form-range-fake-track-background-complete) 0%, var(--form-range-fake-track-background-complete) var(--a), var(--form-range-fake-track-background) var(--a), var(--form-range-fake-track-background) 100%);
 }

JS

rangeInput.parent().find('.my-track').get(0).style.setProperty('--form-range-fake-track-percent-complete', getTrackStyle(rangeInput));

Observemos lo que tenemos. Tenemos una definición CSS para dibujar un progreso por medio de “linear-gradient” en el que tanto los colores (el componente está contextualizado visualmente, esto lo veremos en un punto posterior) como su tamaño lo estamos manejando con custom properties. Vamos a centrarnos en nuestro tamaño que es lo que nos interesa ahora mismo.

En la parte JavaScript tenemos una sentencia que lo que hace es asignar un valor (en este caso lanza una función que se encarga de calcular la posición en porcentaje de la posición en la que me encuentro en mi input range) a nuestra custom property de la CSS. Esta sentencia forma parte de una función que se lanza cada vez que existe un evento “oninput” en nuestro elemento, por lo que cada vez que cambia nuestro valor, la custom property se actualiza y la definición de nuestra CSS varía, consiguiendo así que con una única declaración seamos capaces de generar todos los posibles valores a representar gráficamente.

Habréis observado que en la declaración CSS hay una variable “–a”, ¿que es lo que hace esta variable? En nuestro caso particular, el diseño incluía que la barra de progreso tuviera una cierta inclinación a modo de “skew”, por lo que un valor de 0% a 100% no se ajusta exactamente a lo que nosotros necesitamos, es decir, al llegar a los valores mínimos y máximos con nuestro manejador, éramos capaces de ver como los picos de nuestro progreso pasaban por debajo de nuestro manejador dejando un efecto indeseable.

En la variable “–a” lo que tenemos es un cálculo para hacer este ajuste y no ver esos picos, pero como hemos contado anteriormente, la posición del manejador es “variable” con respecto al progreso desde la mitad de nuestro “track” hasta cada uno de los extremos, por eso no podemos simplemente sumar o restar el tamaño “que sobra”, quedando nuestro cálculo de la siguiente manera:

CSS

--a: calc((((100 - var(--form-range-fake-track-percent-complete))/100) * (8px)) + (var(--form-range-fake-track-percent-complete) * 1%));

Como vemos, estamos reajustando en cada uno de las posiciones de nuestro recorrido (101 posiciones, de 0 a 100) la diferencia de tamaño entre lo que debería medir nuestro “track” y lo que realmente mide por ese efecto “skew” o deformado, que en este caso son 8px, lo interesante y lo potente de esto es que estamos haciendo este cálculo directamente en nuestro CSS con el dato que obtenemos en nuestro JS, simplemente inyectando el valor concreto, nada de sobreescribir toda la sentencia CSS o de calcular todo esto en el JS, siendo mucho más efectivo de cara al navegador y controlando todo el aspecto visual desde CSS.

Las opiniones vertidas por el autor son enteramente suyas y no siempre representan la opinión de BBVA Next Technologies.

¿Quieres saber que más cosas hacemos en BBVA Next Technologies?