
Recientemente, CSS ha agregado muchas características nuevas e interesantes, como propiedades personalizadas y nuevas funciones. Si bien estas cosas pueden hacernos la vida mucho más fácil, también pueden terminar interactuando con preprocesadores, como Sass, de maneras divertidas.
Así que esta será una publicación sobre los problemas que he encontrado, cómo los soluciono y por qué todavía considero que Sass es necesario en estos días.
Los errores
Si has jugado con el nuevo min()
y max()
funciones, es posible que haya encontrado un mensaje de error como este al trabajar con diferentes unidades: “Unidades incompatibles: vh
y em
.”
Un error al trabajar con diferentes tipos de unidades en el
min()
/ max()
función
Esto es porque Sass tiene lo suyomin()
e ignora el CSS min()
función. Además, Sass no puede realizar ningún tipo de cálculo utilizando dos valores con unidades que no tengan una relación fija entre ellos.
Por ejemplo, cm
y in
unidades tienen una relación fija entre ellos, por lo que Sass puede averiguar cuál es el resultado de min(20in, 50cm)
y no arroja un error cuando intentamos usarlo en nuestro código.
Lo mismo ocurre con otras unidades. Las unidades angulares, por ejemplo, tienen todas una relación fija entre ellas: 1turn
, 1rad
o 1grad
calcula siempre lo mismo deg
valores. Lo mismo ocurre con 1s
que es siempre 1000ms
, 1kHz
que es siempre 1000Hz
, 1dppx
que es siempre 96dpi
, y 1in
que es siempre 96px
. Es por eso que Sass puede convertir entre ellos y mezclarlos en cálculos y funciones internas como la suya. min()
función.
Pero las cosas se rompen cuando estas unidades no tienen una relación fija entre ellas (como el caso anterior con em
y vh
unidades).
Y no son solo diferentes unidades. tratando de usar calc()
dentro min()
también da como resultado un error. Si intento algo como calc(20em + 7px)
, el error que me sale es, “calc(20em + 7px)
no es un numero para min
.”
Un error al usar diferentes valores de unidad con
calc()
anidado en el min()
función
Otro problema surge cuando queremos utilizar una variable CSS o el resultado de una función CSS matemática (como calc()
, min()
o max()
) en un filtro CSS como invert()
.
En este caso, nos dicen que “$color: 'var(--p, 0.85)
no es un color para invert
.”
var()
en filter: invert()
error
Lo mismo sucede para grayscale()
: “$color
: ‘calc(.2 + var(--d, .3))
‘ no es un color para grayscale
.”
calc()
en filter: grayscale()
error
opacity()
causa el mismo problema: “$color
: ‘var(--p, 0.8)
‘ no es un color para opacity
.”
var()
en filter: opacity()
error
Sin embargo, otros filter
funciones, incluyendo sepia()
, blur()
, drop-shadow()
, brightness()
, contrast()
y hue-rotate()
¡Todo funciona bien con las variables CSS!
Resulta que lo que está pasando es similar a la min()
y max()
problema. Sass no tiene incorporado sepia()
, blur()
, drop-shadow()
, brightness()
, contrast()
, hue-rotate()
funciones, pero tiene su propio grayscale()
, invert()
y opacity()
funciones, y su primer argumento es un $color
valor. Como no encuentra ese argumento, arroja un error.
Por la misma razón, también tenemos problemas cuando tratamos de usar una variable CSS que enumera al menos dos hsl()
o hsla()
valores.
var()
en color: hsl()
error.
Por otro lado, color: hsl(9, var(--sl, 95%, 65%))
es CSS perfectamente válido y funciona bien sin Sass.
Pasa exactamente lo mismo con el rgb()
y rgba()
funciones
var()
en color: rgba()
error.
Además, si importamos Compass e intentamos usar una variable CSS dentro de un linear-gradient()
o dentro de un radial-gradient()
, obtenemos otro error, aunque usamos variables dentro conic-gradient()
funciona bien (es decir, si el navegador lo admite).
var()
en background: linear-gradient()
error.
Esto se debe a que Compass viene con linear-gradient()
y radial-gradient()
funciones, pero nunca ha agregado un conic-gradient()
una.
Los problemas en todos estos casos surgen de que Sass o Compass tienen funciones con nombres idénticos y suponen que esas son las que pretendíamos usar en nuestro código.
¡Maldita sea!
La solución
El truco aquí es recordar que Sass distingue entre mayúsculas y minúsculas, pero CSS no.
Eso significa que podemos escribir Min(20em, 50vh)
y Sass no lo reconocerá como propio min()
función. No se arrojarán errores y todavía es un CSS válido que funciona según lo previsto. Del mismo modo, escribir HSL()
/ HSLA()
/ RGB()
/ RGBA()
o Invert()
nos permite evitar problemas que vimos anteriormente.
En cuanto a los degradados, normalmente prefiero linear-Gradient()
y radial-Gradient()
solo porque está más cerca de la versión SVG, pero usar al menos una letra mayúscula allí funciona bien.
¿Pero por qué?
Casi cada vez que tuiteo algo relacionado con Sass, recibo lecciones sobre cómo no debería usarse ahora que tenemos variables CSS. Pensé en abordar eso y explicar por qué no estoy de acuerdo.
En primer lugar, si bien encuentro que las variables CSS son inmensamente útiles y las he usado para casi todo durante los últimos tres años, es bueno tener en cuenta que vienen con un costo de rendimiento y que rastrear dónde algo salió mal en un laberinto de calc()
los cálculos pueden ser una molestia con nuestras DevTools actuales. Trato de no abusar de ellos para evitar entrar en un territorio donde las desventajas de usarlos superan los beneficios.
No es exactamente fácil averiguar cuál es el resultado de esos
calc()
expresiones
En general, si actúa como una constante, no cambia de elemento a elemento o de estado a estado (en cuyo caso las propiedades personalizadas son definitivamente el camino a seguir) ni reduce la cantidad de CSS compilado (resolviendo el problema de repetición creado por prefijos), entonces voy a usar una variable Sass.
En segundo lugar, las variables siempre han sido una parte bastante pequeña de por qué uso Sass. Cuando comencé a usar Sass a finales de 2012, era principalmente para hacer bucles, una característica que todavía no tenemos en CSS. Si bien he movido parte de ese bucle a un preprocesador HTML (porque reduce el código generado y evita tener que modificar tanto el HTML como el CSS más adelante), sigo usando bucles Sass en muchos casos, como generar listas de valores, listas de parada dentro de funciones de gradiente, listas de puntos dentro de una función poligonal, listas de transformaciones, etc.
Aquí hay un ejemplo. solía generar n
Elementos HTML con un preprocesador. La elección del preprocesador importa menos, pero usaré Pug aquí.
- let n = 12;
while n--
.item
Entonces establecería el $n
variable en Sass (y tendría que ser igual a la del HTML) y hacer un bucle para generar las transformaciones que posicionarían cada elemento:
$n: 12;
$ba: 360deg/$n;
$d: 2em;
.item {
position: absolute;
top: 50%; left: 50%;
margin: -.5*$d;
width: $d; height: $d;
/* prettifying styles */
@for $i from 0 to $n {
&:nth-child(#{$i + 1}) {
transform: rotate($i*$ba) translate(2*$d) rotate(-$i*$ba);
&::before { content: '#{$i}' }
}
}
}
Sin embargo, esto significaba que tendría que cambiar tanto el Pug como el Sass al cambiar la cantidad de elementos, lo que hacía que el código generado fuera muy repetitivo.
CSS generado por el código anterior
Desde entonces, pasé a hacer que Pug genere los índices como propiedades personalizadas y luego los use en el transform
declaración.
- let n = 12;
body(style=`--n: ${n}`)
- for(let i = 0; i < n; i++)
.item(style=`--i: ${i}`)
$d: 2em;
.item {
position: absolute;
top: 50%;
left: 50%;
margin: -.5*$d;
width: $d;
height: $d;
/* prettifying styles */
--az: calc(var(--i)*1turn/var(--n));
transform: rotate(var(--az)) translate(2*$d) rotate(calc(-1*var(--az)));
counter-reset: i var(--i);
&::before { content: counter(i) }
}
Esto reduce significativamente el código generado.
CSS generado por el código anterior
Sin embargo, aún es necesario hacer un bucle en Sass si quiero generar algo como un arcoíris.
@function get-rainbow($n: 12, $sat: 90%, $lum: 65%) {
$unit: 360/$n;
$s-list: ();
@for $i from 0 through $n {
$s-list: $s-list, hsl($i*$unit, $sat, $lum)
}
@return $s-list
}
html { background: linear-gradient(90deg, get-rainbow()) }
Claro, podría generarlo como una variable de lista de Pug, pero hacerlo no aprovecha la naturaleza dinámica de las variables CSS y no reduce la cantidad de código que se envía al navegador, por lo que no hay ningún beneficio. fuera de el.
Otra gran parte de mi uso de Sass (y Compass) está vinculada a las funciones matemáticas integradas (como las funciones trigonométricas), que ahora forman parte de la especificación CSS, pero que aún no se han implementado en ningún navegador. Sass tampoco viene con estas funciones, pero Compass sí y es por eso que a menudo necesito usar Compass.
Y, claro, podría escribir mis propias funciones de este tipo en Sass. Recurrí a esto al principio, antes de que Compass admitiera funciones trigonométricas inversas. Realmente los necesitaba, así que escribí los míos basados en la serie de Taylor. Pero Compass proporciona este tipo de funciones hoy en día y son mejores y más eficaces que las mías.
Las funciones matemáticas son extremadamente importantes para mí, ya que soy un técnico, no un artista. Los valores en mi CSS generalmente resultan de cálculos matemáticos. No son números mágicos o algo usado puramente por estética. Un ejemplo es la generación de listas de puntos de rutas de recorte que crean polígonos regulares o casi regulares. Piense en el caso en el que queremos crear cosas como pegatinas o avatares no rectangulares.
Consideremos un polígono regular con vértices en un círculo con un radio 50%
del elemento cuadrado del que partimos. Arrastrar el control deslizante en la siguiente demostración nos permite ver dónde se colocan los puntos para diferentes números de vértices:
Poniéndolo en código Sass, tenemos:
@mixin reg-poly($n: 3) {
$ba: 360deg/$n; // base angle
$p: (); // point coords list, initially empty
@for $i from 0 to $n {
$ca: $i*$ba; // current angle
$x: 50%*(1 + cos($ca)); // x coord of current point
$y: 50%*(1 + sin($ca)); // y coord of current point
$p: $p, $x $y // add current point coords to point coords list
}
clip-path: polygon($p) // set clip-path to list of points
}
Tenga en cuenta que aquí también estamos haciendo uso de bucles y cosas como condicionales y módulo que son un verdadero dolor cuando se usa CSS sin Sass.
Una versión un poco más evolucionada de esto podría implicar rotar el polígono agregando el mismo ángulo de desplazamiento ($oa
) al ángulo de cada vértice. Esto se puede ver en la siguiente demostración. Este ejemplo lanza una combinación de estrellas que funciona de manera similar, excepto que siempre tenemos un número par de vértices y cada vértice indexado impar está situado en un círculo de un radio más pequeño ($f*50%
, donde $f
es subunitario):
También podemos tener estrellas gorditas como esta:
O pegatinas con interesantes border
patrones. En esta demostración en particular, cada etiqueta se crea con un solo elemento HTML y el border
patrón se crea con clip-path
, looping y matemáticas en Sass. Bastante, de hecho.
Otro ejemplo son estos fondos de tarjeta donde el bucle, la operación de módulo y las funciones exponenciales trabajan juntas para generar las capas de fondo de píxeles difuminados:
Esta demostración también depende en gran medida de las variables CSS.
Luego está el uso de mixins para evitar escribir exactamente las mismas declaraciones una y otra vez al diseñar cosas como entradas de rango. Los diferentes navegadores usan diferentes pseudoelementos para diseñar los componentes de dicho control, por lo que para cada componente, tenemos que establecer los estilos que controlan su apariencia en múltiples pseudos.
Lamentablemente, por muy tentador que sea poner esto en nuestro CSS:
input::-webkit-slider-runnable-track,
input::-moz-range-track,
input::-ms-track { /* common styles */ }
…¡no podemos hacerlo porque no funciona! Todo el conjunto de reglas se descarta si no se reconoce ni uno solo de los selectores. Y dado que ningún navegador reconoce los tres anteriores, los estilos no se aplican en ningún navegador.
Necesitamos tener algo como esto si queremos que se apliquen nuestros estilos:
input::-webkit-slider-runnable-track { /* common styles */ }
input::-moz-range-track { /* common styles */ }
input::-ms-track { /* common styles */ }
Pero eso puede significar muchos estilos idénticos repetidos tres veces. Y si queremos cambiar, digamos, el background
de la pista, tenemos que cambiarlo en el ::-webkit-slider-runnable-track
estilos, en el ::-moz-range-track
estilos y en el ::-ms-track
estilos.
La única solución sensata que tenemos es usar un mixin. Los estilos se repiten en el código compilado porque tienen que repetirse allí, pero ya no tenemos que escribir lo mismo tres veces.
@mixin track() { /* common styles */ }
input {
&::-webkit-slider-runnable-track { @include track }
&::-moz-range-track { @include track }
&::-ms-track { @include track }
}
La conclusión es: sí, Sass sigue siendo muy necesario en 2020.