Componentes integrados en un sistema de diseño trucos CSS
Al crear una infraestructura de front-end basada en componentes, uno de los mayores puntos débiles que he encontrado personalmente es la creación de componentes que se pueden reutilizar y responder cuando los componentes están integrados en los componentes.
Tome la siguiente "llamada a la acción" (<CTA />
) componente, por ejemplo:
En dispositivos más pequeños queremos que se vea así:
Eso es bastante simple con las solicitudes básicas de los medios. Si usamos flexbox, una solicitud de medios puede cambiar la dirección de la flexibilidad y hacer que el botón alcance todo su ancho. Pero nos encontramos con un problema cuando empezamos a invertir otros componentes allí. Por ejemplo, digamos que usamos un componente para el botón y ya tiene un soporte que lo hace ancho completo. De hecho, duplicamos el estilo del botón cuando aplicamos una solicitud de medios al componente principal. ¡El botón insertado ahora puede procesarlo!
Este es un pequeño ejemplo y no será un problema tan grave, pero para otros escenarios puede dar lugar a una gran cantidad de código de replicación de estilo duplicado. ¿Qué pasa si en el futuro queremos cambiar algo sobre cómo se estilizan los botones de ancho completo? Tendremos que revisarlo y cambiarlo en todos estos lugares diferentes. Deberíamos poder cambiarlo en el componente del botón y tener esta actualización en todas partes.
¿No sería bueno si pudiéramos alejarnos de las demandas de los medios y tener más control sobre el estilo? Necesitamos usar detalles de componentes existentes y poder pasar diferentes valores según el ancho de la pantalla.
Bueno, tengo una manera de hacerlo y te mostraré cómo lo hice.
Soy consciente de eso solicitudes de contenedores puede resolver muchos de estos problemas, pero aún está en pañales y no resuelve el problema de transmitir diferentes accesorios según el ancho de la pantalla.
Anchura de la ventana de pista
Primero, necesitamos rastrear el ancho actual de la página y establecer un punto de interrupción. Esto se puede hacer con cualquier marco frontal, pero yo uso Vista componible aquí para demostrar la idea:
// composables/useBreakpoints.js
import { readonly, ref } from "vue";
const bps = ref({ xs: 0, sm: 1, md: 2, lg: 3, xl: 4 })
const currentBreakpoint = ref(bps.xl);
export default () => {
const updateBreakpoint = () => {
const windowWidth = window.innerWidth;
if(windowWidth >= 1200) {
currentBreakpoint.value = bps.xl
} else if(windowWidth >= 992) {
currentBreakpoint.value = bps.lg
} else if(windowWidth >= 768) {
currentBreakpoint.value = bps.md
} else if(windowWidth >= 576) {
currentBreakpoint.value = bps.sm
} else {
currentBreakpoint.value = bps.xs
}
}
return {
currentBreakpoint: readonly(currentBreakpoint),
bps: readonly(bps),
updateBreakpoint,
};
};
La razón por la que usamos números para currentBreakpoint
el objeto se aclarará más tarde.
Ahora podemos escuchar eventos de cambio de tamaño de ventana y actualizar el punto de interrupción actual usando componible en la pantalla principal. App.vue
expediente:
// App.vue
<script>
import useBreakpoints from "@/composables/useBreakpoints";
import { onMounted, onUnmounted } from 'vue'
export default {
name: 'App',
setup() {
const { updateBreakpoint } = useBreakpoints()
onMounted(() => {
updateBreakpoint();
window.addEventListener('resize', updateBreakpoint)
})
onUnmounted(() => {
window.removeEventListener('resize', updateBreakpoint)
})
}
}
</script>
Probablemente queramos eliminar esto, pero mantengo las cosas simples para abreviar.
Componentes de estilo
podemos actualizar <CTA />
componente para adoptar un nuevo accesorio sobre cómo se debe diseñar:
// CTA.vue
props: {
displayMode: {
type: String,
default: "default"
}
}
La denominación aquí es completamente arbitraria. Puede usar los nombres que desee para cada uno de los modos de los componentes.
Luego podemos usar esta propiedad para cambiar el modo en función del punto de interrupción actual:
<CTA :display-mode="currentBreakpoint > bps.md ? 'default' : 'compact'" />
Ahora puede ver por qué usamos un número para representar el punto de interrupción actual, por lo que el modo correcto se puede aplicar a todos los puntos de interrupción por debajo o por encima de un número determinado.
Luego podemos usar esto en el componente CTA para diseñar de acuerdo con el modo pasado:
// components/CTA.vue
<template>
<div class="cta" :class="displayMode">
<div class="cta-content">
<h5>title</h5>
<p>description</p>
</div>
<Btn :block="displayMode === 'compact'">Continue</Btn>
</div>
</template>
<script>
import Btn from "@/components/ui/Btn";
export default {
name: "CTA",
components: { Btn },
props: {
displayMode: {
type: String,
default: "default"
},
}
}
</script>
<style scoped lang="scss">
.cta {
display: flex;
align-items: center;
.cta-content {
margin-right: 2rem;
}
&.compact {
flex-direction: column;
.cta-content {
margin-right: 0;
margin-bottom: 2rem;
}
}
}
</style>
¡Ya hemos eliminado la necesidad de solicitudes de medios! Puedes ver esto en la acción de un página de demostración Yo creé.
Por supuesto, esto puede parecer un proceso largo para algo tan simple. Pero cuando se aplica a varios componentes, este enfoque puede mejorar significativamente la coherencia y la estabilidad de la interfaz de usuario, al tiempo que reduce la cantidad total de código que tenemos que escribir. Esta forma de usar clases JavaScript y CSS para controlar el estilo responsive también tiene otra ventaja...
Funcionalidad ampliable para componentes anidados
Hubo escenarios en los que tuve que volver a un punto de interrupción anterior para un componente. Por ejemplo, si ocupa el 50% de la pantalla, quiero que se muestre en modo pequeño. Pero con un cierto tamaño de pantalla, se convierte en ancho completo. En otras palabras, el modo debe cambiar de una forma u otra cuando hay un evento de cambio de tamaño.
También he estado en situaciones en las que el mismo componente se usa en diferentes modos en diferentes páginas. Esto no es algo que los marcos como Bootstrap y Tailwind puedan hacer, y usar solicitudes de medios para recuperarlo sería una pesadilla. (Todavía puede usar estos marcos usando esta técnica, solo que sin la necesidad de las clases receptivas que brindan).
nosotros podría use una consulta de medios que solo se aplica a pantallas medianas, pero que no resuelve el problema con diferentes soportes según el ancho de la pantalla. Afortunadamente, el enfoque que estamos tomando puede resolver esto. Podemos cambiar el código anterior para permitir un modo de punto de interrupción personalizado arrastrándolo a través de una matriz, con el primer elemento de la matriz que tiene el tamaño de pantalla más pequeño.
<CTA :custom-mode="['compact', 'default', 'compact']" />
Primero, actualicemos los accesorios que <CTA />
el componente puede aceptar:
props: {
displayMode: {
type: String,
default: "default"
},
customMode: {
type: [Boolean, Array],
default: false
},
}
Luego podemos agregar lo siguiente para generar en el modo correcto:
import { computed } from "vue";
import useBreakpoints from "@/composables/useBreakpoints";
// ...
setup(props) {
const { currentBreakpoint } = useBreakpoints()
const mode = computed(() => {
if(props.customMode) {
return props.customMode[currentBreakpoint.value] ?? props.displayMode
}
return props.displayMode
})
return { mode }
},
Esto toma el modo de la matriz en función del punto de interrupción actual y es el valor predeterminado displayMode
si no se encuentra. Entonces podemos usar mode
en lugar de estilizar el componente.
Extracción para reutilización
Muchos de estos métodos se pueden derivar en composiciones y mezclas adicionales que se pueden reutilizar con otros componentes.
Extraer modo ordenador
La lógica para volver al modo correcto se puede derivar en compuesto:
// composables/useResponsive.js
import { computed } from "vue";
import useBreakpoints from "@/composables/useBreakpoints";
export const useResponsive = (props) => {
const { currentBreakpoint } = useBreakpoints()
const mode = computed(() => {
if(props.customMode) {
return props.customMode[currentBreakpoint.value] ?? props.displayMode
}
return props.displayMode
})
return { mode }
}
Extracción de atrezo
En Vue 2 podríamos repetir los accesorios usando mixins, pero hay inconvenientes notables. Vue 3 nos permite combinarlos con otros accesorios utilizando el mismo compuesto. Hay una pequeña advertencia con esto, ya que el IDE no parece ser capaz de reconocer accesorios de autocompletar usando este método. Si esto es demasiado molesto, puede usar mixin en su lugar.
Opcionalmente, también podemos pasar una verificación personalizada para asegurarnos de que usamos modos disponibles solo para cada componente, donde el primer valor pasado al validador es el predeterminado.
// composables/useResponsive.js
// ...
export const withResponsiveProps = (validation, props) => {
return {
displayMode: {
type: String,
default: validation[0],
validator: function (value) {
return validation.indexOf(value) !== -1
}
},
customMode: {
type: [Boolean, Array],
default: false,
validator: function (value) {
return value ? value.every(mode => validation.includes(mode)) : true
}
},
...props
}
}
Ahora, cambiemos la lógica e importemos estos en su lugar:
// components/CTA.vue
import Btn from "@/components/ui/Btn";
import { useResponsive, withResponsiveProps } from "@/composables/useResponsive";
export default {
name: "CTA",
components: { Btn },
props: withResponsiveProps(['default 'compact'], {
extraPropExample: {
type: String,
},
}),
setup(props) {
const { mode } = useResponsive(props)
return { mode }
}
}
Conclusión
Crear un sistema de diseño a partir de componentes reutilizables y receptivos es un desafío y propenso a las inconsistencias. También vimos lo fácil que es terminar con mucho código duplicado. Hay un buen equilibrio cuando se trata de crear componentes que no solo funcionan en muchos contextos, sino que funcionan bien con otros componentes cuando se combinan.
Estoy seguro de que te has encontrado con una situación similar en tu propio trabajo. El uso de estos métodos puede reducir el problema y, con suerte, hacer que la interfaz de usuario sea más estable, reutilizable, manejable y fácil de usar.
Deja una respuesta