====== 7. SASS ======
SASS es un preprocesador de CSS. Es como añadir funcionalidad a CSS que no tiene pero no de cosas del navegador sino para ayudar a escribir menos CSS.
El código se escribe en SASS y se transforma en CSS. Permite bucles, funciones, variables ,etc.
La implementación oficial de SASS es Dart Sass, que es la que usa Angular.
Enlaces con mas información de SASS:
* [[https://sass-guidelin.es/|Sass Guidelines]]
* [[https://sass-lang.com/documentation/at-rules/mixin/|Sass: @mixin and @include]]
* [[https://sass-lang.com/documentation/at-rules/function/|Sass: @function]]
* [[http://thesassway.com/intermediate/if-for-each-while|Sass control directives: @if, @for, @each and @while]]
* [[https://sass-lang.com/documentation/at-rules/control/if|Sass: @if and @else]]
* [[https://sass-lang.com/documentation/at-rules/control/for|Sass: @for]]
* [[https://sass-lang.com/documentation/at-rules/control/each#with-maps|sass: @each with maps]]
* [[https://teamtreehouse.com/community/-can-someone-tell-me-how-i-can-install-sass-on-windows-by-nodejs-httpnodejsorg|Instalar SASS en node]]
* [[https://sass-lang.com/|Página del lenguaje SASS]]
* [[https://sass-lang.com/guide| Guía básica de SASS]]
Curso de SASS:
* [[https://www.chucksacademy.com/es/topic/css-preprocessors|Preprocesadores de CSS]]
===== Variables =====
Las variables en SASS empiezan por el símbolo $. Sirven para no repetir el mismo color o la misma fuente en todo el CSS.
$color-principal: #1979E6;
$color-texto: #323E49;
$fuente-titulos: Georgia, serif;
body {
color: $color-texto;
}
h1 {
font-family: $fuente-titulos;
color: $color-principal;
}
se transforma en
body {
color: #323E49;
}
h1 {
font-family: Georgia, serif;
color: #1979E6;
}
===== Listas =====
Una lista guarda varios valores en una variable. Por ejemplo los tamaños de fuente del sistema de tamaños que vimos en el tema de diseño.
* Para acceder a un elemento de una lista se usa la función ''list.nth''. **El índice empieza por 1 y no por cero**.
* Para obtener el tamaño de una lista se usa la función ''list.length''.
@use "sass:list";
$tamanyos-fuente: (12px, 14px, 16px, 18px, 20px, 24px, 30px, 36px, 48px);
body {
font-size: list.nth($tamanyos-fuente, 3);
}
h1 {
font-size: list.nth($tamanyos-fuente, list.length($tamanyos-fuente));
}
se transforma en
body {
font-size: 16px;
}
h1 {
font-size: 48px;
}
===== Mapas =====
Un mapa guarda pares clave-valor. Por ejemplo los breakpoints de las media queries con su nombre.
* Para acceder a un elemento de un map se usa la función ''map.get''.
* Para obtener las claves de un map se usa la función ''map.keys''. Se obtiene una lista.
@use "sass:map";
$breakpoints: (
'tablet': 576px,
'desktop': 768px,
'fulldesktop': 992px
);
h1 {
font-size: 30px;
}
@media (min-width: map.get($breakpoints, 'desktop')) {
h1 {
font-size: 48px;
}
}
se transforma en
h1 {
font-size: 30px;
}
@media (min-width: 768px) {
h1 {
font-size: 48px;
}
}
''map.keys($breakpoints)'' devolvería la lista ''('tablet', 'desktop', 'fulldesktop')''.
===== Bucle @for =====
Repite un trozo de CSS cambiando un número. Por ejemplo para crear las clases de columnas de grid.
* ''@for $i from 1 to 5'': Desde ''1'' hasta ''4''. El último número **no** se incluye.
@for $i from 1 to 5 {
.columnas-#{$i} {
display: grid;
grid-template-columns: repeat($i, 1fr);
}
}
* ''@for $i from 1 through 4'': Desde ''1'' hasta ''4''. El último número **sí** se incluye.
@for $i from 1 through 4 {
.columnas-#{$i} {
display: grid;
grid-template-columns: repeat($i, 1fr);
}
}
Los dos se transforman en
.columnas-1 {
display: grid;
grid-template-columns: repeat(1, 1fr);
}
.columnas-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
.columnas-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.columnas-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
La sintaxis de usar una variable en SASS cuando es el nombre de una clase CSS es un poco distinta. Hay que usar lo siguiente ''#{$variable}''
Usa siempre ''through'': se lee igual que se piensa, «de 1 a 4».
===== Bucle @each de una lista =====
Recorre los elementos de una lista. Por ejemplo para crear los tamaños de los iconos.
$tamanyos-icono: (16px, 24px, 32px);
@each $tamanyo in $tamanyos-icono {
.icono-#{$tamanyo} {
width: $tamanyo;
height: $tamanyo;
}
}
se transforma en
.icono-16px {
width: 16px;
height: 16px;
}
.icono-24px {
width: 24px;
height: 24px;
}
.icono-32px {
width: 32px;
height: 32px;
}
===== Bucle @each de un Map =====
Recorre un map obteniendo la clave y el valor. Por ejemplo para los colores que se usan para cosas concretas, como vimos en el tema de diseño: verde si ha ido bien, amarillo para avisar y rojo si hay un error.
$colores-estado: (
'exito': #1BAF7A,
'aviso': #ECC94B,
'error': #E61919
);
@each $nombre, $color in $colores-estado {
.mensaje-#{$nombre} {
border-left: 4px solid $color;
}
}
se transforma en
.mensaje-exito {
border-left: 4px solid #1BAF7A;
}
.mensaje-aviso {
border-left: 4px solid #ECC94B;
}
.mensaje-error {
border-left: 4px solid #E61919;
}
===== Selector Padre =====
En SASS se puede escribir un selector dentro de otro. El "&" se convierte en el selector "padre", así que todo lo de un elemento queda junto.
.boton {
background-color: #1979E6;
transition: background-color 0.3s;
&:hover {
background-color: #1363B4;
}
&:focus {
outline: 2px solid #80ADF4;
}
&-grande {
font-size: 20px;
}
.icono {
margin-right: 8px;
}
}
se transforma en:
.boton {
background-color: #1979E6;
transition: background-color 0.3s;
}
.boton:hover {
background-color: #1363B4;
}
.boton:focus {
outline: 2px solid #80ADF4;
}
.boton-grande {
font-size: 20px;
}
.boton .icono {
margin-right: 8px;
}
* ''&:hover'': El "&" pega el '':hover'' al selector padre: ''.boton:hover''.
* ''&-grande'': El "&" pega el texto al nombre de la clase: ''.boton-grande''.
* ''.icono'' sin "&": Es un elemento que está dentro: ''.boton .icono''.
Mas información en [[https://css-tricks.com/the-sass-ampersand/|The Sass Ampersand]]
===== Funciones =====
En SASS también existen funciones "normales" que calculan un valor. Por ejemplo, si todos los espacios del sistema de tamaños son múltiplos de 4px:
@function espacio($n) {
@return $n * 4px;
}
.tarjeta {
padding: espacio(4);
margin-bottom: espacio(6);
}
se transforma en
.tarjeta {
padding: 16px;
margin-bottom: 24px;
}
===== Mixins =====
Son como trozos de código CSS que puedes añadir en otra clase CSS. Es como llamar a una "función" pero con CSS. Son muy útiles para evitar repetir código CSS. Por ejemplo, todos los botones son iguales salvo el color:
@mixin boton($color) {
padding: 8px 16px;
border: none;
border-radius: 6px;
background-color: $color;
color: #FFFFFF;
}
.boton-aceptar {
@include boton(#1979E6);
}
.boton-borrar {
@include boton(#E61919);
}
se transforma en
.boton-aceptar {
padding: 8px 16px;
border: none;
border-radius: 6px;
background-color: #1979E6;
color: #FFFFFF;
}
.boton-borrar {
padding: 8px 16px;
border: none;
border-radius: 6px;
background-color: #E61919;
color: #FFFFFF;
}
===== Funciones de Color =====
SASS tiene funciones para calcular colores a partir de otro: [[https://sass-lang.com/documentation/modules/color/|sass:color]]
Las más útiles son:
* ''color.scale($color, $lightness: -20%)'': Un 20% más oscuro. Con un valor positivo, más claro. También vale con ''$saturation''.
* ''color.mix($color1, $color2, $peso)'': Mezcla dos colores. ''$peso'' es cuánto hay del primero.
Mejoramos el mixin del botón para que el borde y el '':hover'' sean un poco más oscuros que el color del botón, sin tener que calcularlos a mano:
@use "sass:color";
@mixin boton($color) {
padding: 8px 16px;
border: 1px solid color.scale($color, $lightness: -20%);
border-radius: 6px;
background-color: $color;
color: #FFFFFF;
&:hover {
background-color: color.scale($color, $lightness: -10%);
}
}
.boton-aceptar {
@include boton(#1979E6);
}
se transforma en
.boton-aceptar {
padding: 8px 16px;
border: 1px solid rgb(20, 96.8, 184);
border-radius: 6px;
background-color: #1979E6;
color: #FFFFFF;
}
.boton-aceptar:hover {
background-color: rgb(22.5, 108.9, 207);
}
SASS escribe los colores calculados con ''rgb()'' y decimales. El navegador los acepta tal cual.
===== Condicional @if =====
Permite generar un CSS u otro según una condición.
En el botón, si el color de fondo es muy claro, el texto blanco no se lee: hay que cambiarlo a negro para que haya contraste. ''color.channel'' devuelve la luminosidad del color en HSL.
@use "sass:color";
@mixin boton($color) {
padding: 8px 16px;
border: 1px solid color.scale($color, $lightness: -20%);
border-radius: 6px;
background-color: $color;
@if color.channel($color, "lightness", $space: hsl) > 60% {
color: #000000;
} @else {
color: #FFFFFF;
}
&:hover {
background-color: color.scale($color, $lightness: -10%);
}
}
.boton-aceptar {
@include boton(#1979E6);
}
.boton-avisar {
@include boton(#ECC94B);
}
se transforma en
.boton-aceptar {
padding: 8px 16px;
border: 1px solid rgb(20, 96.8, 184);
border-radius: 6px;
background-color: #1979E6;
color: #FFFFFF;
}
.boton-aceptar:hover {
background-color: rgb(22.5, 108.9, 207);
}
.boton-avisar {
padding: 8px 16px;
border: 1px solid rgb(225.0452261307, 181.2864321608, 23.7547738693);
border-radius: 6px;
background-color: #ECC94B;
color: #000000;
}
.boton-avisar:hover {
background-color: rgb(233.0306532663, 192.5608040201, 46.8693467337);
}
El ''.boton-aceptar'' tendrá el texto blanco y el ''.boton-avisar'' (amarillo, que es claro) lo tendrá negro.
===== @use =====
Con ''@use'' se usa en un fichero lo que hay en otro: variables, funciones y mixins. Por ejemplo, los colores en un fichero y el botón en otro:
$color-principal: #1979E6;
$color-error: #E61919;
@use 'colores';
.boton-aceptar {
background-color: colores.$color-principal;
}
.boton-borrar {
background-color: colores.$color-error;
}
Con @use se cargan los estilos de otro fichero y, para evitar conflictos, las variables se acceden con el prefijo del nombre del fichero ''nombreFichero.$nombreVariable''.
Si quieres usar las variables directamente sin el prefijo, puedes añadir la opción as *:
@use 'colores' as *;
.boton-aceptar {
background-color: $color-principal;
}
.boton-borrar {
background-color: $color-error;
}
Al usar un fichero se pone su nombre sin ''_'' ni ''.scss''. Por qué empieza por ''_'' está en [[#ficheros_parciales|Ficheros parciales]].
===== @forward =====
Cuando tenemos varios ficheros con variables, mixins o funciones,
puede ser pesado tener que importarlos uno por uno en cada archivo SASS.
Para simplificar, usamos ''@forward'', que actúa como un "reexportador". Permite centralizar varios ficheros en uno solo y luego usarlos todos de golpe.
Estructura de ejemplo:
proyecto
├─ estilos
│ ├─ _colores.scss
│ ├─ _funciones.scss
│ ├─ _mixins.scss
│ └─ _index.scss
└─ main.scss
Contenido de los ficheros:
$color-principal: #1979E6;
$color-error: #E61919;
@function espacio($n) {
@return $n * 4px;
}
@mixin boton($color) {
padding: 8px 16px;
border: none;
border-radius: 6px;
background-color: $color;
color: #FFFFFF;
}
@forward 'colores';
@forward 'funciones';
@forward 'mixins';
@use 'estilos';
.boton-aceptar {
@include estilos.boton(estilos.$color-principal);
margin: estilos.espacio(2);
}
Fijarse que si creamos un fichero llamado ''_index.scss'' lo carga automáticamente al hacer ''@use'' de la carpeta. Véase [[https://sass-lang.com/documentation/at-rules/use/#loading-a-directory|loading-a-directory]]
===== Ficheros parciales =====
Todos los ficheros SASS empiezan por ''_'' (''_colores.scss'', ''_index.scss'', ''_c-panel.scss'') excepto el que lo une todo, ''main.scss''.
Los ficheros que empiezan por ''_'' son //parciales//: SASS no genera un CSS para ellos, solo se cargan desde otro con ''@use'' o ''@forward''. Así, al compilar la carpeta con ''npx sass --watch scss:css'' sale un único ''css/main.css'' con todo, que es el que se enlaza en el HTML (ver [[clase:daw:diw:sistema-diseno-estructura-carpetas#proyecto_sin_angular|Sistema de diseño: Estructura de carpetas]]).
Si un fichero no empieza por ''_'', SASS genera también su propio CSS: un ''colores.css'' vacío o un ''c-panel.css'' con el mismo CSS que ya está dentro de ''main.css''. La carpeta ''css'' se llena de ficheros que no se usan y no se sabe cuál hay que enlazar.
===== Ejercicios =====
==== Ejercicio 1 ====
Dado el siguiente mapa con los 9 tonos de cada color de la paleta, generados con el [[https://logongas.github.io/color-schemes-generator/|Color Scheme Generator]]:
$colores: (
'principal': (#011019, #052F4D, #0B4D83, #1363B4, #1979E6, #4B8EEC, #80ADF4, #B2CAFA, #E6EDFE),
'alternativo': (#190801, #4D1D05, #83370B, #B45613, #E67519, #EC9C4B, #F4BE80, #FADCB2, #FEF5E6),
'rojo': (#190501, #4D0D05, #83150B, #B41813, #E61919, #EC534B, #F48A80, #FABCB2, #FEEAE6),
'gris': (#1D2830, #323E49, #485461, #5C6775, #717D8E, #8A93A3, #A1A9BA, #B6BDCD, #CFD4E2)
);
Genera las siguientes variables CSS:
:root {
--color-principal-1: #011019;
--color-principal-2: #052F4D;
...
--color-gris-8: #B6BDCD;
--color-gris-9: #CFD4E2;
}
El valor de una variable CSS hay que ponerlo con interpolación: ''--color-#{$nombre}-#{$i}: #{list.nth($tonos, $i)};''
==== Ejercicio 2 ====
Crea la función ''tono($color, $n)'' que devuelva el tono ''$n'' (de 1 a 9) de un color:
* El tono 5 es el propio color.
* Los tonos 1 a 4 se obtienen mezclándolo con negro (''color.mix'') al 80%, 60%, 40% y 20%.
* Los tonos 6 a 9 se obtienen mezclándolo con blanco al 20%, 40%, 60% y 80%.
Úsala para generar los 9 tonos de ''#1979E6'' como variables CSS ''--color-principal-1'' ... ''--color-principal-9'' y compáralos con los del ejercicio anterior, que se hicieron con el [[https://logongas.github.io/color-schemes-generator/|Color Scheme Generator]]. ¿Por qué no salen iguales?
==== Ejercicio 3 ====
Dada la lista:
$escala-titulos: (48px, 36px, 30px, 24px, 20px, 18px);
Genera con un bucle el tamaño de fuente de los títulos ''h1'' a ''h6'':
h1 {
font-size: 48px;
}
h2 {
font-size: 36px;
}
...
h6 {
font-size: 18px;
}
==== Ejercicio 4 ====
Crea el mixin ''elevacion($nivel)'' que ponga una de las sombras del tema de diseño (la luz viene del cielo):
$sombras: (
0 1px 3px rgb(0 0 0 / 20%),
0 4px 6px rgb(0 0 0 / 20%),
0 5px 15px rgb(0 0 0 / 20%),
0 10px 24px rgb(0 0 0 / 20%),
0 15px 35px rgb(0 0 0 / 20%)
);
* Con nivel ''0'' no tendrá sombra (''box-shadow: none'').
* Con nivel ''1'' a ''5'' tendrá la sombra correspondiente.
Úsalo en una página con una tarjeta (nivel 2), un menú desplegable (nivel 3) y una ventana modal (nivel 5).
==== Ejercicio 5 ====
Crea el mixin ''desde($nombre)'' que use el mapa ''$breakpoints'' para generar la media query y meta dentro el CSS que se le pase:
h1 {
font-size: 30px;
@include desde('desktop') {
font-size: 48px;
}
}
Dentro del mixin, ''@content'' es el CSS que se ha escrito entre las llaves del ''@include''.
Rehaz con él las media queries del menú hamburguesa del tema de CSS responsivo.
==== Ejercicio 6 ====
Crea la función ''fluido($min, $max, $ancho-min, $ancho-max)'' que devuelva el ''clamp()'' calculando la pendiente y el coeficiente con las fórmulas del tema de CSS responsivo.
Comprueba que ''fluido(12px, 20px, 230px, 600px)'' devuelve ''clamp(12px, 2.1621621622vw + 7.027027027px, 20px)''.
* Para dividir se usa ''math.div($a, $b)'' de ''@use "sass:math"''.
* SASS no puede sumar ''vw'' y ''px'', así que el resultado hay que escribirlo con interpolación: ''clamp(#{$min}, #{$pendiente}vw + #{$coeficiente}, #{$max})''.
==== Ejercicio 7 ====
Haz una lista de 5 elementos que aparezcan uno detrás de otro: todos usan la misma animación (de ''opacity: 0'' a ''opacity: 1'') pero cada uno empieza 0.2s después que el anterior.
Genera con un bucle el ''animation-delay'' de cada elemento usando '':nth-child()''.
==== Ejercicio 8 ====
Organiza en ficheros todo lo que has hecho en los ejercicios anteriores:
* ''_colores.scss'': El mapa de colores.
* ''_funciones.scss'': Las funciones ''tono'' y ''fluido''.
* ''_mixins.scss'': Los mixins ''elevacion'' y ''desde''.
* ''_index.scss'': Con los ''@forward''.
* ''main.scss'': Que lo use todo con un único ''@use''.