El uso de consultas de medios en CSS como parte de sitios web receptivos es pan comido para el desarrollador de front-end de hoy. El uso de preprocesadores para hacerlos más cómodos de escribir y más fáciles de mantener también se ha convertido en una práctica común.
Pasé unos meses experimentando con una docena de enfoques diferentes para las consultas de medios en Sass y de hecho usé algunos en la producción. Todos ellos finalmente fallaron en atender todo lo que necesitaba hacer de una manera elegante. Así que tomé lo que me gustó de cada uno de ellos y creé una solución que cubría todos los escenarios que encontré.
¿Por qué utilizar un preprocesador?
Esa es una pregunta justa. Después de todo, ¿cuál es el punto de hacer todo esto si uno puede simplemente escribir consultas de medios usando CSS puro? Orden y facilidad de mantenimiento.
El uso más común de las consultas de medios es la transformación de un diseño en función del ancho de la ventana del navegador. Puede hacer que un diseño se adapte de tal manera que múltiples dispositivos con diferentes tamaños de pantalla puedan disfrutar de una experiencia óptima. Como consecuencia, las expresiones utilizadas para definir las consultas de medios harán referencia al ancho de pantalla típico de esos dispositivos.
Entonces, si su código contiene 5 consultas de medios dirigidas a dispositivos de tableta con un ancho de 768px, codificará ese número 5 veces, lo cual es algo feo que mi TOC nunca perdonaría. En primer lugar, quiero que mi código sea fácil de leer hasta el punto de que cualquiera entienda instantáneamente que una consulta de medios está dirigida a dispositivos de tableta con solo mirarla; creo que la palabra tableta lo haría mejor que 768px.
Además, ¿qué pasa si ese ancho de referencia cambia en el futuro? Odio la idea de reemplazarlo en 5 instancias alrededor del código, especialmente cuando está disperso en varios archivos.
Un primer paso sería almacenar ese punto de interrupción en una variable y usarlo para construir la consulta de medios.
/* Using plain CSS */
@media (min-width: 768px) {
}
/* Using SCSS variables to store breakpoints */
$breakpoint-tablet: 768px;
@media (min-width: $breakpoint-tablet) {
}
Otra razón para escribir consultas de medios con un preprocesador como Sass es que a veces puede proporcionar una ayuda valiosa con la sintaxis, en particular cuando se escribe una expresión con una lógica o (representada con una coma en CSS).
Por ejemplo, si desea apuntar a dispositivos retina, la sintaxis CSS pura comienza a volverse un poco detallada:
/* Plain CSS */
@media (min-width: 768px) and
(-webkit-min-device-pixel-ratio: 2),
(min-width: 768px) and
(min-resolution: 192dpi) {
}
/* Using variables? */
@media (min-width: $bp-tablet) and ($retina) { // or #{$retina}
}
Se ve mejor, pero desafortunadamente no funcionará como se esperaba.
Un problema con la lógica
Debido a la forma en que funciona el operador “o” de CSS, no podría mezclar las condiciones de la retina con otras expresiones, ya que a (b or c)
sería compilado en (a or b) c
y no a b or a c
.
$retina: "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)";
// This will generate unwanted results!
@media (min-width: 480px) and #{$retina} {
body {
background-color: red;
}
}
/* Not the logic we're looking for */
@media (min-width: 480px) and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
body {
background-color: red;
}
}
Me di cuenta de que necesitaba algo más poderoso, como un mixin o una función, para abordar esto. Probé algunas soluciones.
La técnica de Dmitry Sheiko
Una que probé fue la técnica de Dmitry Sheiko, que tenía una buena sintaxis e incluye la declaración de retina de Chris.
// Predefined Break-points
$mediaMaxWidth: 1260px;
$mediaBp1Width: 960px;
$mediaMinWidth: 480px;
@function translate-media-condition($c) {
$condMap: (
"screen": "only screen",
"print": "only print",
"retina": "(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi)",
">maxWidth": "(min-width: #{$mediaMaxWidth + 1})",
"<maxWidth": "(max-width: #{$mediaMaxWidth})",
">bp1Width": "(min-width: #{$mediaBp1Width + 1})",
"<bp1Width": "(max-width: #{$mediaBp1Width})",
">minWidth": "(min-width: #{$mediaMinWidth + 1})",
"<minWidth": "(max-width: #{$mediaMinWidth})"
);
@return map-get( $condMap, $c );
}
// The mdia mixin
@mixin media($args...) {
$query: "";
@each $arg in $args {
$op: "";
@if ( $query != "" ) {
$op: " and ";
}
$query: $query + $op + translate-media-condition($arg);
}
@media #{$query} { @content; }
}
Pero el problema de la disyunción lógica seguía ahí.
.section {
@include media("retina", "<minWidth") {
color: white;
};
}
/* Not the logic we're looking for */
@media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3 / 2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi) and (max-width: 480px) {
.section {
background: blue;
color: white;
}
}
La técnica de Landon Schropp
Landon Schropp’s fue mi próxima parada. Landon crea mixins simples con nombre que realizan trabajos específicos. Me gusta:
$tablet-width: 768px;
$desktop-width: 1024px;
@mixin tablet {
@media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) {
@content;
}
}
@mixin desktop {
@media (min-width: #{$desktop-width}) {
@content;
}
}
También tiene una versión de retina de responsabilidad única.
Pero otro problema me golpeó cuando estaba diseñando un elemento que requería reglas adicionales en puntos de interrupción intermedios. No quería contaminar mi lista de puntos de interrupción globales con valores específicos de casos solo para poder usar el mixin, pero definitivamente no quería renunciar al mixin y volver a usar CSS simple y codificar cosas cada vez que tuvo que usar valores personalizados.
/* I didn't want to sometimes have this */
@include tablet {
}
/* And other times this */
@media (min-width: 768px) and (max-width: 950px) {
}
Técnica de punto de interrupción
Breakpoint-sass fue el siguiente en mi lista, ya que admite tanto variables como valores personalizados en su sintaxis (y, como beneficio adicional, es realmente inteligente con las consultas de medios de proporción de píxeles).
Podría escribir algo como:
$breakpoint-tablet: 768px;
@include breakpoint(453px $breakpoint-tablet) {
}
@include breakpoint($breakpoint-tablet 850px) {
}
/* Compiles to: */
@media (min-width: 453px) and (max-width: 768px) {
}
@media (min-width: 768px) and (max-width: 850px) {
}
Las cosas se veían mejor, pero personalmente creo que la sintaxis de Breakpoint-sass se siente menos natural que la de Dmitry. Puede darle un número y asume que es un valor de ancho mínimo, o un número y una cadena y asume un par de propiedad / valor, por nombrar solo algunas de las combinaciones que admite.
Está bien y estoy seguro de que funciona muy bien una vez que te acostumbras, pero no había renunciado a encontrar una sintaxis que fuera simple y lo más cercana posible a la forma en que describo oralmente a qué debe dirigirse una consulta de medios. .
Además, si observa el ejemplo anterior, verá que un dispositivo con un ancho de exactamente 768px activará ambas consultas de medios, que pueden no ser exactamente lo que queremos. Así que agregué la capacidad de escribir puntos de interrupción inclusivos y exclusivos a mi lista de requisitos.
Mi técnica (de Eduardo Bouças)
Esta es mi opinión sobre ella.
Sintaxis limpia, declaración dinámica
Soy fanático de la sintaxis de Dmitry, así que mi solución se inspiró en ella. Sin embargo, me gustaría tener más flexibilidad en la forma en que se crean los puntos de interrupción. En lugar de codificar los nombres de los puntos de interrupción en el mixin, utilicé un mapa multidimensional para declararlos y etiquetarlos.
$breakpoints: (phone: 640px,
tablet: 768px,
desktop: 1024px) !default;
@include media(">phone", "<tablet") {
}
@include media(">tablet", "<950px") {
}
El mixin viene con un conjunto de puntos de interrupción predeterminados, que puede anular en cualquier parte del código volviendo a declarar la variable $breakpoints
.
Puntos de ruptura inclusivos y exclusivos
Quería tener un control más preciso sobre los intervalos en las expresiones, por lo que incluí soporte para los operadores menor o igual a y mayor o igual a. De esta manera, puedo usar la misma declaración de punto de interrupción en dos consultas de medios mutuamente excluyentes.
@include media(">=phone", "<tablet") {
}
@include media(">=tablet", "<=950px") {
}
/* Compiles to */
@media (min-width: 640px) and (max-width: 767px) {
}
@media (min-width: 768px) and (max-width: 950px) {
}
Deducir tipos de medios y manejar la disyunción lógica
De manera similar a los puntos de interrupción, hay una lista de tipos de medios y otras expresiones estáticas declaradas de forma predeterminada (que puede anular configurando la variable $media-expressions
). Esto agrega soporte para tipos de medios opcionales, como pantalla o computadora de mano, pero también es capaz de manejar correctamente expresiones con disyunciones lógicas, como la consulta de medios retina que vimos antes. Las disyunciones se declaran como listas anidadas de cadenas.
$media-expressions: (screen: "screen",
handheld: "handheld",
retina2x:
("(-webkit-min-device-pixel-ratio: 2)",
"(min-resolution: 192dpi)")) !default;
@include media("screen", ">=tablet") {
}
@include media(">tablet", "<=desktop", "retina2x") {
}
/* Compiles to */
@media screen and (min-width: 768px) {
}
@media (min-width: 769px) and
(max-width: 1024px) and
(-webkit-min-device-pixel-ratio: 2),
(min-width: 769px) and
(max-width: 1024px) and
(min-resolution: 192dpi) {
}
No hay ciencia espacial bajo el capó, pero la implementación completa del mixin no es algo que pueda mostrar en solo unas pocas líneas de código. En lugar de aburrirlo con enormes fragmentos de código y comentarios interminables, incluí un lápiz con todo funcionando y describiré brevemente el proceso por el que pasa para construir las consultas de medios.
Cómo funciona
- El mixin recibe múltiples argumentos como cadenas y comienza revisando cada uno para averiguar si representa un punto de interrupción, un ancho personalizado o una de las expresiones de medios estáticos.
- Si se encuentra un operador, se extrae y se devolverá cualquier punto de interrupción coincidente, o de lo contrario asumimos que es un valor personalizado y lo convertimos en un número (usando SassyCast).
- Si es una expresión de medios estática, busca cualquier
or
operadores y genera todas las combinaciones necesarias para representar la disyunción. - El proceso se repite para todos los argumentos y los resultados se unirán mediante el
and
conector para formar la expresión de consulta de medios.
Si desea ver el Sass completo, está aquí. Se llama include-media en GitHub.
Pensamientos finales
- Soy un gran admirador de esta técnica para hacer que Sass hable con JavaScript. Debido a que declaramos los puntos de interrupción como una lista multidimensional con sus nombres como claves, exportarlos de forma masiva a JavaScript se vuelve realmente sencillo y se puede hacer automáticamente con solo unas pocas líneas de código.
- No estoy tratando de menospreciar las soluciones de otras personas y definitivamente no estoy diciendo que esta sea mejor. Los mencioné para mostrar algunos de los obstáculos que encontré en el camino hacia mi solución ideal, así como algunas cosas geniales que introdujeron y que inspiraron mi propia solución.
- Es posible que le preocupe la duración y la complejidad de esta implementación. Si bien lo entiendo, la idea detrás de esto es que descargue un solo archivo,
@import
en su proyecto y comience a usarlo sin tener que tocar el código fuente. Hazme ping en Gorjeo aunque si tienes alguna pregunta. - Puede obtenerlo de GitHub y puede contribuir con temas / código / amor. Estoy seguro de que todavía hay mucho que podemos hacer para mejorarlo.
¡Actualizar!
Eduardo hizo un sitio web para su enfoque: @ include-media.