====== 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''.