Tabla de Contenidos

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:

Curso de SASS:

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.

@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.

@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 {
  .columnas-#{$i} {
    display: grid;
    grid-template-columns: repeat($i, 1fr);
  }
}

@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;
}

Mas información en 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: sass:color

Las más útiles son:

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.

@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 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 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 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:

Ú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 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%)
);

Ú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: