Cree sus propios paneles de contenido que se expanden y contraen

 

 

 

  • Patrones de diseño para interfaces de IA, con Vitaly Friedman
  • SmashingConf Friburgo 2024

  • Índice
    1. Enfoques
      1. Consideraciones de cada enfoque
      2. El elefante en la habitación: detalles y elementos resumidos
      3. Patrón de marcado
      4. Lógica básica
      5. Puntos a tener en cuenta
      6. ¿Por qué no utilizar simplemente un valor predeterminado de 100.000 px?
    2. Todos juntos
    3. Resumen

    En UI/UX, un patrón común que se necesita una y otra vez es el de un simple panel animado de apertura y cierre, o "cajón". No necesitas una biblioteca para hacerlos. Con algo de HTML/CSS y JavaScript básico, aprenderemos a hacerlo nosotros mismos.

     

    Hasta ahora los hemos llamado "paneles de apertura y cierre", pero también se los describe como paneles de expansión o, más simplemente, paneles expandibles.

    Para aclarar exactamente de qué estamos hablando, consulte este ejemplo en CodePen:

    Cajón fácil de mostrar/ocultar (varios) de Ben Frain en CodePen .

    Eso es lo que construiremos en este breve tutorial.

    Desde el punto de vista de la funcionalidad, existen algunas formas de lograr la apertura y el cierre animados que buscamos. Cada enfoque tiene sus propios beneficios y compensaciones. Voy a compartir los detalles de mi método de referencia en detalle en este artículo. Consideremos primero posibles enfoques.

    Enfoques

    Existen variaciones de estas técnicas, pero en términos generales, los enfoques se dividen en una de tres categorías:

    1. Animar/transicionar el heighto max-heightdel contenido.
    2. Úselo transform: translateYpara mover elementos a una nueva posición, dando la ilusión de que un panel se cierra y luego vuelva a renderizar el DOM una vez que se complete la transformación con los elementos en su posición final.
    3. ¡Utilice una biblioteca que haga alguna combinación/variación de 1 o 2!

    Consideraciones de cada enfoque

    Desde una perspectiva de rendimiento, usar una transformación es más efectivo que animar o realizar una transición de altura/altura máxima. Con una transformación, los elementos móviles se rasterizan y la GPU los desplaza. Esta es una operación sencilla y económica para una GPU, por lo que el rendimiento tiende a ser mucho mejor.

    Los pasos básicos al utilizar un enfoque de transformación son:

    1. Obtenga la altura del contenido que se contraerá.
    2. Mueva el contenido y todo lo que sigue a la altura del contenido que se va a contraer usando transform: translateY(Xpx). Opere la transformación con la transición de su elección para brindar un efecto visual agradable.
    3. Utilice JavaScript para escuchar el transitionendevento. Cuando se dispara, display: noneel contenido y se elimina la transformación y todo debería estar en el lugar correcto.

    No suena tan mal, ¿verdad?

    Sin embargo, hay una serie de consideraciones con esta técnica, por lo que tiendo a evitarla en implementaciones casuales, a menos que el rendimiento sea absolutamente crucial.

    Por ejemplo, con el transform: translateYenfoque es necesario considerar la z-indexde los elementos. De forma predeterminada, los elementos que se transforman están después del elemento desencadenante en el DOM y, por lo tanto, aparecen encima de los elementos anteriores cuando se traducen.

    También debes considerar cuántas cosas aparecen después del contenido que deseas contraer en el DOM. Si no desea un gran agujero en su diseño, puede que le resulte más fácil usar JavaScript para envolver todo lo que desea mover en un elemento contenedor y simplemente moverlo. Manejable, ¡pero acabamos de introducir más complejidad! Sin embargo, este es el tipo de enfoque que elegí cuando moví jugadores hacia arriba y hacia abajo en In/Out . Puedes ver cómo se hizo aquí .

    Para necesidades más informales, tiendo a optar por la transición max-heightdel contenido. Este enfoque no funciona tan bien como una transformación. La razón es que el navegador intercala la altura del elemento que se contrae durante la transición; eso provoca muchos cálculos de diseño que no son tan baratos para la computadora host.

    Sin embargo, este enfoque gana desde el punto de vista de la simplicidad. La recompensa de sufrir el impacto computacional antes mencionado es que el reflujo DOM se encarga de la posición y la geometría de todo. Tenemos muy pocos cálculos que escribir y el JavaScript necesario para hacerlo bien es comparativamente simple.

    El elefante en la habitación: detalles y elementos resumidos

    Aquellos con un profundo conocimiento de los elementos de HTML sabrán que existe una solución HTML nativa para este problema en forma de elementos detailsy summary. A continuación se muestra un ejemplo de marcado:

    details summaryClick to open/close/summary Here is the content that is revealed when clicking the summary.../details

    De forma predeterminada, los navegadores proporcionan un pequeño triángulo desplegable junto al elemento de resumen; Haga clic en el resumen y se revelará el contenido debajo del resumen.

     

    Genial, ¿oye? Los detalles incluso admiten el toggleevento en JavaScript, por lo que puedes hacer este tipo de cosas para realizar diferentes cosas según si está abierto o cerrado (no te preocupes si ese tipo de expresión de JavaScript parece extraña; llegaremos a eso con más detalle). dentro de poco):

    details.addEventListener("toggle", () = { details.open ? thisCoolThing() : thisOtherThing();})

    Bien, voy a detener tu entusiasmo allí mismo. Los detalles y elementos de resumen no tienen animación. No de forma predeterminada y actualmente no es posible hacer que se abran y cierren con CSS y JavaScript adicionales.

    Si sabes lo contrario, me encantaría que me demuestren que estoy equivocado.

    Lamentablemente, como necesitamos una estética de apertura y cierre, tendremos que arremangarnos y hacer el mejor y más accesible trabajo que podamos con las otras herramientas a nuestra disposición.

    Bien, dejando de lado las deprimentes noticias, sigamos haciendo que esto suceda.

    Patrón de marcado

    El marcado básico se verá así:

    div button type="button"Show/Hide content/button div All the content here /div/div

    Tenemos un contenedor exterior para envolver el expansor y el primer elemento es el botón que sirve de disparador de la acción. ¿Observa el atributo de tipo en el botón? Siempre incluyo que, de forma predeterminada, un botón dentro de un formulario realizará un envío. Si pierde un par de horas preguntándose por qué su formulario no funciona y hay botones involucrados en su formulario; ¡Asegúrate de marcar el atributo de tipo!

    El siguiente elemento después del botón es el propio cajón de contenidos; todo lo que quieres ocultar y mostrar.

    Para darle vida a las cosas, utilizaremos propiedades personalizadas de CSS, transiciones de CSS y un poco de JavaScript.

    Lógica básica

    La lógica básica es esta:

    1. Deje que la página se cargue, mida la altura del contenido.
    2. Establezca la altura del contenido en el contenedor como el valor de una propiedad personalizada de CSS.
    3. Oculte inmediatamente el contenido agregándole un aria-hidden: "true"atributo. El uso aria-hiddengarantiza que la tecnología de asistencia sepa que el contenido también está oculto.
    4. Conecte el CSS para que la max-heightclase de contenido sea el valor de la propiedad personalizada.
    5. Al presionar nuestro botón de activación, la propiedad aria-hidden cambia de verdadero a falso, lo que a su vez alterna el max-heightcontenido entre 0la altura establecida en la propiedad personalizada. Una transición en esa propiedad proporciona el toque visual: ¡ajústese al gusto!

    Nota: Ahora, este sería un caso simple de alternar una clase o atributo si max-height: autofuera igual a la altura del contenido. Lamentablemente no es así. Vayan y grítenlo al W3C aquí .

    Echemos un vistazo a cómo se manifiesta ese enfoque en el código. Los comentarios numerados muestran los pasos lógicos equivalentes anteriores en el código. Fulares Portabebes

     

    Aquí está el JavaScript:

    // Get the containing elementconst container = document.querySelector(".container");// Get contentconst content = document.querySelector(".content");// 1. Get height of content you want to show/hideconst heightOfContent = content.getBoundingClientRect().height;// Get the trigger elementconst btn = document.querySelector(".trigger");// 2. Set a CSS custom property with the height of contentcontainer.style.setProperty("--containerHeight", `${heightOfContent}px`);// Once height is read and setsetTimeout(e = { document.documentElement.classList.add("height-is-set"); 3. content.setAttribute("aria-hidden", "true");}, 0);btn.addEventListener("click", function(e) { container.setAttribute("data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true"); // 5. Toggle aria-hidden content.setAttribute("aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true");})

    El CSS:

    .content { transition: max-height 0.2s; overflow: hidden;}.content[aria-hidden="true"] { max-height: 0;}// 4. Set height to value of custom property.content[aria-hidden="false"] { max-height: var(--containerHeight, 1000px);}

    Puntos a tener en cuenta

    ¿Qué pasa con varios cajones?

    Cuando tenga varios cajones para abrir y ocultar en una página, deberá recorrerlos todos, ya que probablemente serán de diferentes tamaños.

    Para manejar eso, necesitaremos hacer un querySelectorAllpara obtener todos los contenedores y luego volver a ejecutar la configuración de variables personalizadas para cada contenido dentro de un archivo forEach.

    Ese tiempo de espera establecido

    Tengo una duración setTimeoutcon 0antes de configurar el contenedor para que esté oculto. Podría decirse que esto es innecesario, pero lo uso como un enfoque de "cinturón y tirantes" para asegurar que la página se haya renderizado primero para que las alturas del contenido estén disponibles para ser leídas.

    Solo activa esto cuando la página esté lista.

    Si tiene otras cosas en marcha, puede optar por envolver el código de su cajón en una función que se inicializa al cargar la página. Por ejemplo, supongamos que la función del cajón estuviera envuelta en una función llamada, initDrawerspodríamos hacer esto:

    window.addEventListener("load", initDrawers);

    De hecho, lo agregaremos en breve.

    Atributos de datos-* adicionales en el contenedor

    Hay un atributo de datos en el contenedor exterior que también se alterna. Esto se agrega en caso de que haya algo que deba cambiar con el gatillo o el contenedor cuando el cajón se abre/cierra. Por ejemplo, quizás queramos cambiar el color de algo o revelar o alternar un ícono.

    Valor predeterminado en la propiedad personalizada

    Hay un valor predeterminado establecido en la propiedad personalizada en CSS de 1000px. Ese es el bit después de la coma dentro del valor: var(--containerHeight, 1000px). Esto significa que si todo --containerHeightse estropea de alguna manera, aún así deberías tener una transición decente. Obviamente, puede configurarlo en lo que sea adecuado para su caso de uso.

    ¿Por qué no utilizar simplemente un valor predeterminado de 100.000 px?

    Dado que eso max-height: autono realiza una transición, es posible que se pregunte por qué no opta simplemente por una altura establecida de un valor mayor al que jamás necesitaría. Por ejemplo, ¿10000000px?

     

    El problema con ese enfoque es que siempre hará la transición desde esa altura. Si la duración de la transición se establece en 1 segundo, la transición "viajará" 10000000 px en un segundo. Si tu contenido tiene solo 50 píxeles de alto, ¡obtendrás un efecto de apertura/cierre bastante rápido!

    Operador ternario para alternancia

    Hemos utilizado un operador ternario un par de veces para alternar atributos. Algunas personas los odian, pero yo y otros los amamos. Pueden parecer un poco raros y un poco 'golf de código' al principio, pero una vez que te acostumbras a la sintaxis, creo que son una lectura más sencilla que un estándar if/else.

    Para los no iniciados, un operador ternario es una forma condensada de if/else. Están escritos para que lo que se debe verificar sea primero, luego separe ?qué ejecutar si la verificación es verdadera y luego para :distinguir qué se debe ejecutar si la verificación es falsa.

    isThisTrue ? doYesCode() : doNoCode();

    Nuestros alternadores de atributos funcionan verificando si un atributo está configurado en "true"y, de ser así, configúrelo en "false"; de lo contrario, configúrelo en "true".

    ¿Qué sucede al cambiar el tamaño de la página?

    Si un usuario cambia el tamaño de la ventana del navegador, existe una alta probabilidad de que cambie la altura de nuestro contenido. Por lo tanto, es posible que desee volver a ejecutar la configuración de la altura de los contenedores en ese escenario. Ahora que estamos considerando tales eventualidades, parece un buen momento para refactorizar un poco las cosas.

    Podemos crear una función para establecer las alturas y otra función para manejar las interacciones. Luego agregue dos oyentes en la ventana; uno para cuando se carga el documento, como se mencionó anteriormente, y luego otro para escuchar el evento de cambio de tamaño.

    Un poco más A11Y

    Es posible agregar un poco más de consideración a la accesibilidad haciendo uso de los aria-expandedatributos aria-controlsy aria-labelledby. Esto dará una mejor indicación a la tecnología asistida cuando los cajones se hayan abierto/expandido. Agregamos aria-expanded="false"al marcado de nuestro botón junto a aria-controls="IDofcontent", donde IDofcontentestá el valor de una identificación que agregamos al contenedor de contenido.

    Luego usamos otro operador ternario para alternar el aria-expandedatributo al hacer clic en JavaScript.

    Todos juntos

    Con la carga de la página, múltiples cajones, trabajo adicional de A11Y y manejo de eventos de cambio de tamaño, nuestro código JavaScript se ve así:

    var containers;function initDrawers() { // Get the containing elements containers = document.querySelectorAll(".container"); setHeights(); wireUpTriggers(); window.addEventListener("resize", setHeights);}window.addEventListener("load", initDrawers);function setHeights() { containers.forEach(container = { // Get content let content = container.querySelector(".content"); content.removeAttribute("aria-hidden"); // Height of content to show/hide let heightOfContent = content.getBoundingClientRect().height; // Set a CSS custom property with the height of content container.style.setProperty("--containerHeight", `${heightOfContent}px`); // Once height is read and set setTimeout(e = { container.classList.add("height-is-set"); content.setAttribute("aria-hidden", "true"); }, 0); });}function wireUpTriggers() { containers.forEach(container = { // Get each trigger element let btn = container.querySelector(".trigger"); // Get content let content = container.querySelector(".content"); btn.addEventListener("click", () = { btn.setAttribute("aria-expanded", btn.getAttribute("aria-expanded") === "false" ? "true" : "false"); container.setAttribute( "data-drawer-showing", container.getAttribute("data-drawer-showing") === "true" ? "false" : "true" ); content.setAttribute( "aria-hidden", content.getAttribute("aria-hidden") === "true" ? "false" : "true" ); }); });}

    También puedes jugar con él en CodePen aquí:

    Cajón fácil de mostrar/ocultar (varios) de Ben Frain en CodePen .

    Resumen

    Es posible seguir refinando y atendiendo cada vez más situaciones durante algún tiempo, pero la mecánica básica de crear un cajón de apertura y cierre confiable para su contenido ahora debería estar a su alcance. Con suerte, usted también es consciente de algunos de los peligros. El detailselemento no se puede animar, max-height: autono hace lo que esperaba, no puede agregar de manera confiable un valor de altura máxima masivo y esperar que todos los paneles de contenido se abran como se esperaba.

    Para reiterar nuestro enfoque aquí: mida el contenedor, almacene su altura como una propiedad personalizada de CSS, oculte el contenido y luego use un simple interruptor para cambiar entre max-height0 y la altura que almacenó en la propiedad personalizada.

    Puede que no sea el método con mejor rendimiento, pero he descubierto que para la mayoría de las situaciones es perfectamente adecuado y se beneficia de ser comparativamente sencillo de implementar.

    (dm, yk, il)Explora más en

    • experiencia de usuario
    • interfaz de usuario
    • javascript
    • CSS





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Cree sus propios paneles de contenido que se expanden y contraen

    Cree sus propios paneles de contenido que se expanden y contraen

    Patrones de diseño para interfaces de IA, con Vitaly Friedman SmashingConf Friburgo 2024 Índice Enfoques

    programar

    es

    https://aprendeprogramando.es/static/images/programar-cree-sus-propios-paneles-de-contenido-que-se-expanden-y-contraen-1008-0.jpg

    2024-05-21

     

    Cree sus propios paneles de contenido que se expanden y contraen
    Cree sus propios paneles de contenido que se expanden y contraen

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20