Implementando pantallas de esqueleto en React
Publicado: 2022-03-10Los spinners y los cargadores han sido tradicionalmente la forma de decirles a los usuarios que el contenido tardará un tiempo en cargarse. Si bien este enfoque es excelente, se está volviendo obsoleto rápidamente en el desarrollo moderno. Las pantallas Skeleton se están convirtiendo en el reemplazo perfecto para los cargadores tradicionales porque se enfocan en el progreso en lugar de los tiempos de espera, lo que reduce la frustración del tiempo de carga.
En este artículo, no repasaremos los conceptos básicos de CSS React o la sintaxis de JavaScript, por lo que no es necesario que sea un experto en ninguno de estos lenguajes para seguirlo.
Los expertos en UI y UX nos enseñan que, mientras los usuarios esperan que se cargue el contenido en una página, debemos mantenerlos interesados.
La idea detrás de usar spinners para involucrar a los usuarios antes de que se cargue el contenido es genial; sin embargo, el resultado puede ser menos que ideal porque la mayoría de los usuarios se aburrirán mirando una rueda giratoria ficticia animada como si fuera un reloj. Luke Wroblewski elabora sobre esto.
Las pantallas de esqueleto ofrecen una mejor experiencia de usuario al reducir la frustración del tiempo de carga. Al centrarse en el progreso en lugar de los tiempos de espera, crea la ilusión para los usuarios de que la información se mostrará de forma incremental en la pantalla. Bill Chung en su investigación lo confirma.
¿Qué es una pantalla de esqueleto?
Una pantalla esqueleto es una versión de la interfaz de usuario que no contiene contenido real; en cambio, imita el diseño de la página al mostrar sus elementos en una forma similar al contenido real a medida que se carga y está disponible (es decir, cuando la latencia de la red lo permite).
Una pantalla esqueleto es esencialmente una estructura alámbrica de la página, con cuadros de marcador de posición para texto e imágenes.
¿Qué tiene de especial una pantalla Skeleton?
Una interfaz de usuario esquelética se asemeja a la interfaz de usuario real de la página, por lo que los usuarios comprenderán qué tan rápido se cargará la aplicación web o móvil incluso antes de que se muestre el contenido. Aquí hay un par de razones por las que podría considerar usar pantallas de esqueleto en su próximo proyecto:
- imitar el diseño de una página es más fácil con una pantalla de esqueleto,
- los contenidos se cargan progresivamente (no todos a la vez).
Las pantallas de esqueleto también se conocen como:
- elementos fantasma,
- marcadores de posición de contenido,
- cargadores de contenido
Blockchain.com, YouTube, Facebook, Medium y otras grandes empresas tecnológicas muestran pantallas esqueléticas mientras su contenido se carga para impulsar la UX.
blockchain.com
Medio
Tipos de pantallas de esqueleto
Hay diferentes tipos de pantallas de esqueleto. Los principales son marcadores de posición de texto y marcadores de posición de imagen (o color).
La mayoría de los desarrolladores prefieren usar marcadores de posición de texto como la interfaz de usuario esquelética en sus páginas porque son fáciles de crear y el desarrollador no requiere ningún detalle sobre la esencia del contenido real; en cambio, el esqueleto imita la interfaz de usuario.
Los marcadores de posición de color son más difíciles de crear porque requieren detalles sobre el contenido.
Algunos paquetes populares facilitan la implementación de pantallas de esqueleto en aplicaciones web. Echemos un vistazo más de cerca a ambos:
- Marcador de posición de reacción
- Esqueleto de carga de reacción
Veremos los pros y los contras de cada paquete, antes de considerar cuál usar para nuestra aplicación.
Marcador de posición de reacción
ventajas
- Los componentes de marcador de posición se utilizan para crear una interfaz de usuario de esqueleto personalizada.
- Se admite la animación de pulsos (es decir, el efecto de movimiento en un elemento).
- Viene con una API basada en componentes.
Contras
- Los componentes del esqueleto se mantienen por separado, por lo que la actualización de los estilos de un componente posiblemente también requiera la actualización del componente del esqueleto.
- La curva de aprendizaje no es lineal porque hay múltiples componentes para diferentes necesidades.
El siguiente es un ejemplo de un componente de esqueleto que utiliza el paquete react-placeholder
:
import { TextBlock, RectShape } from 'react-placeholder/lib/placeholders'; import ReactPlaceholder from 'react-placeholder'; const GhostPlaceholder = () => ( <div className='my-placeholder'> <RectShape color='gray' style={{width: 25, height: 70}} /> <TextBlock rows={6} color='blue'/> </div> ); <ReactPlaceholder ready={ready} customPlaceholder={<GhostPlaceholder />}> <MyComponent /> </ReactPlaceholder>
Al importar TextBlock
y RectShape
desde react-placeholder/lib/placeholder
y ReactPlaceholder
desde react-placeholder
, hemos creado un componente funcional llamado GhostPlaceholder
. GhostPlaceholder
tiene un div, y dentro del div hemos usado el componente RectShape, que describe las dimensiones de un rectángulo, pasa el valor de cualquier color y define los estilos del rectángulo.
A continuación, usamos el componente TextBlock
para establecer los valores de las filas y el color. El componente TextBlock
define el número de filas y el color del texto.
Pasamos MyComponent
como elemento secundario del componente ReactPlaceholder
, que recibe ready
y el componente GhostPlaceholder
como valores para sus props ready
y customPlaceholder
.
MyComponent
se cargará cuando se muestre la interfaz de usuario de la pantalla de esqueleto.
Para obtener más información, consulte la documentación.
Esqueleto de carga de reacción
ventajas
- Está basado en API y tiene un componente con accesorios para todas las personalizaciones.
- Se puede usar como un componente de esqueleto separado y también dentro de cualquier componente directamente, por lo que es flexible.
- Admite temas y animación Pulse.
Contras
- Es fácil de implementar para una interfaz de usuario de esqueleto simple, pero complicado para esqueletos más complejos.
- Tener un componente de esqueleto separado hará que sea más difícil de mantener cuando cambien la interfaz de usuario y los estilos.
El siguiente es un ejemplo de React Loading Skeleton:
import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; const SkeletonComponent = () => ( <SkeletonTheme color="#202020" highlightColor="#444"> <section> <Skeleton height={50} width={50} /> </section> </SkeletonTheme> );
Importamos Skeleton
y SkeletonTheme
de la biblioteca react-loading-skeleton
, luego creamos un componente funcional que representa el componente SkeletonTheme
, con color
y hightlightColor
como propiedades.
El componente SkeletonTheme
se utiliza para crear temas (por ejemplo, agregar efectos de color a la interfaz de usuario del esqueleto).
Finalmente, dentro de la sección, definimos el componente Skeleton
, con las propiedades de alto y ancho y sus valores apropiados pasados.
Creación de una interfaz de usuario de pantalla de esqueleto similar a YouTube
Vamos a crear una pantalla de esqueleto similar a YouTube, utilizando React Loading Skeleton, para mostrar cómo funciona una interfaz de usuario de esqueleto.
configurar reaccionar
La forma más fácil de configurar React es usar Create React App, que es “una forma oficial de crear aplicaciones React de una sola página. Ofrece una configuración de construcción moderna sin configuración”.
Lo usaremos para iniciar la aplicación que construiremos. Desde su terminal, ejecute el siguiente comando:
npx create-react-app skeleton-screens && cd skeleton-screens
Una vez que se haya completado la instalación, inicie el servidor React ejecutando npm start
:
Crear la interfaz de usuario de YouTube sin una pantalla de esqueleto
Primero, ingresemos datos ficticios de YouTube. Normalmente se utilizarían puntos finales reales en lugar de datos ficticios, pero en este tutorial utilizaremos datos ficticios.
Cree un archivo en su carpeta src/
y asígnele el nombre data.js
, agregue el siguiente código.
const dummyData= [ { section: "Recommended", channel: "CNN", items: [ { id: "fDObf2AeAP4", image: "https://img.youtube.com/vi/fDObf2AeAP4/maxresdefault.jpg", title: "75 million Americans ordered to stay home", views: "1.9M views", published: "3 days agos" }, { id: "3AzIgAa0Cm8", image: "https://img.youtube.com/vi/3AzIgAa0Cm8/maxresdefault.jpg", title: "Gupta: The truth about using chloroquine to fight coronavirus pandemic", views: "128K views", published: "4 hours ago" }, { id: "92B37aXykYw", image: "https://img.youtube.com/vi/92B37aXykYw/maxresdefault.jpg", title: "Willie Jones STUNS Simon Cowell In Pitch Perfect Performance of 'Your Man'!", views: "2.47 million views", published: "1 month ago" }, { id: "J6rVaFzOEP8", image: "https://img.youtube.com/vi/J6rVaFzOEP8/maxresdefault.jpg", title: "Guide To Becoming A Self-Taught Software Developer", views: "104K views", published: "17 days ago" }, { id: "Wbk8ZrfU3EM", image: "https://img.youtube.com/vi/Wbk8ZrfU3EM/maxresdefault.jpg", title: "Tom Hanks and Rita Wilson test positive for coronavirus", views: "600k views", published: "1 week ago" }, { id: "ikHpFgKJax8", image: "https://img.youtube.com/vi/ikHpFgKJax8/maxresdefault.jpg", title: "Faces Of Africa- The Jerry Rawlings story", views: "2.3 million views", published: "2014" } ] }, { section: "Breaking News", channel: "CGTN America", items: [ { id: "tRLDPy1A8pI", image: "https://img.youtube.com/vi/tRLDPy1A8pI/maxresdefault.jpg", title: "Is Trump blaming China for COVID-19? You decide.", views: "876k views", published: "9 days ago" }, { id: "2ulH1R9hlG8", image: "https://img.youtube.com/vi/2ulH1R9hlG8/maxresdefault.jpg", title: "Journalist still goes to office during pandemic, see her daily routine", views: "873 views", published: "3 hours ago" }, { id: "TkfQ9MaIgU", image: "https://img.youtube.com/vi/_TkfQ9MaIgU/maxresdefault.jpg", title: "How are small businesses going to survive the economic downturn of the COVID-19 era?", views: "283 views", published: "4 day ago" } ] } ]; export default dummyData;
Para replicar el formato de YouTube, creamos datos ficticios que tienen una variedad de objetos, con propiedades como ID, imagen, título, número de vistas y fecha de publicación.
A continuación, creemos nuestra interfaz de usuario de YouTube. Tendremos tres componentes:
Card | Contiene los detalles de la miniatura del video, el título, el número de vistas, la fecha de publicación y el canal. |
CardList | Devuelve todas las cartas seguidas. |
App | Monta nuestro objeto dummyData , carga la interfaz de usuario básica durante dos segundos y devuelve el componente CardList . |
Dentro de su carpeta src
, cree una carpeta y asígnele el nombre components
. Dentro de la carpeta de components
, cree un archivo Card.js
, agregue el siguiente código:
import React from "react"; const Card = ({ item, channel }) => { return ( <li className="card"> <a href={`https://www.youtube.com/watch?v=${item.id}`} target="_blank" rel="noopener noreferrer" className="card-link" > <img src={item.image} alt={item.title} className="card-image" /> <img src={item.image} alt={item.title} className="channel-image" /> <h4 className="card-title">{item.title}</h4> <p className="card-channel"> <i>{channel}</i> </p> <div className="card-metrics"> {item.views} • {item.published} </div> </a> </li> ); }; export default Card;
Creamos un componente Card
. En su interior, importamos React
desde react
y deconstruimos el item
y los accesorios del channel
para que puedan usarse en el componente Card
. Cada componente de elemento Card
que muestra un video mostrará la miniatura, el número de vistas, la fecha de publicación y el título.
Componente de lista de tarjetas
Dentro de la carpeta de components
, cree un archivo CardList.js y agréguele el siguiente código:
import React from "react"; import Card from "./Card"; const CardList = ({ list }) => { return ( <ul className="list"> {list.items.map((item, index) => { return <Card key={index} item={item} channel={list.channel} />; })} </ul> ); }; export default CardList;
En este componente, hemos importado el componente Card
que creamos. La tarjeta acepta el item
y los accesorios del channel
, que obtenemos mediante el mapeo a través de list.items
. Luego exportamos este componente como CardList
, porque lo utilizaremos en nuestro componente App
.
Nota : La matriz de elementos que se asigna en este componente es la matriz de objetos en nuestro dummyData
.
Componente de la aplicación
Dentro del archivo app.js en el directorio src/
, elimine el código que está allí y agregue lo siguiente.
import React, { useState, useEffect } from "react"; import "./App.css"; import dummyData from "./data"; import CardList from "./components/CardList"; const App = () => { const [videos, setVideos] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { setLoading(true); const timer = setTimeout(() => { setVideos(dummyData); setLoading(false); }, 5000); return () => clearTimeout(timer); }, []); return ( <div className="App"> { videos.map((list, index) => { return ( <section key={index}> <h2 className="section-title">{list.section}</h2> <CardList list={list} /> <hr /> </section> ); })} </div> ); }; export default App;
En este componente, hemos importado los ganchos useState
y useEffect
junto con React
y los otros archivos que hemos creado y que serán necesarios en el componente de la App
.
Debido a que nuestros datos son datos ficticios, debemos simularlos como los datos de la API cargando el contenido después de un tiempo de espera de dos segundos, utilizando el método setTimeout
de JavaScript.
Luego, en el componente de la App
, creamos un estado de video y establecemos el estado en una matriz vacía usando useState
.
Para cargar nuestros datos ficticios, usaremos el gancho useEffect
. En nuestro enlace, creamos un temporizador variable que contiene la función setTimeout
()
. Dentro de la función, establecemos nuestro estado de video en nuestro objeto dummyData
y nos aseguramos de que los datos se carguen después de dos segundos y, por último, cancelamos el temporizador mientras se desmonta.
Finalmente, mapeamos nuestro estado de video y devolvemos el elemento de sección que contiene la list-section
y el componente CardList
con sus accesorios de lista.
Agregar CSS
Hasta ahora, hemos usado muchas clases sin CSS real. Dentro de la carpeta src
, elimine todo en App.css
y reemplácelo con el siguiente código;
.App { max-width: 960px; margin: 0 auto; font-size: 16px; } .list { display: flex; justify-content: space-between; flex-wrap: wrap; list-style: none; padding: 0; } .section-title { margin-top: 30px; } .card { width: calc(33% - 10px); margin: 20px 0; } .card-link { color: inherit; text-decoration: none; } .card-image { width: 100%; } .channel-image { border-radius: 100%; padding: 0, 10px, 0, 0; width: 40px; height: 40px; } .card-title { margin-top: 10px; margin-bottom: 0; } .card-channel { margin-top: 5px; margin-bottom: 5px; font-size: 14px; } /* Tablets */ @media (max-width: 1000px) { .App { max-width: 600px; } .card { width: calc(50% - 22px); } } /* Mobiles \*/ @media (max-width: 640px) { .App { max-width: 100%; padding: 0 15px; } .card { width: 100%; } }
Veamos cómo se ve nuestra interfaz de usuario de YouTube sin la pantalla de esqueleto. Puede ver que cuando se carga la página, aparece una pantalla blanca durante dos segundos y luego los datos se cargan rápidamente.
Usando el esqueleto de carga de React
A diferencia de otras bibliotecas en las que crearía meticulosamente una pantalla de esqueleto para que coincida con los tamaños de fuente, las alturas de línea y los márgenes de su contenido, el componente Skeleton
está diseñado para usarse directamente en sus componentes, en lugar del contenido que se está cargando.
Repasemos algunas razones por las que hemos elegido React Loading Skeleton sobre otros.
Tematización
React Loading Skeleton admite temas. Por lo tanto, puede cambiar fácilmente los colores de todos los componentes del esqueleto utilizando SkeletonTheme
y pasar valores a los props
de color.
A continuación se muestra un ejemplo que muestra cómo funciona:
import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; <SkeletonTheme color="grey" highlightColor="#444"> <p> <Skeleton height={250} width={300} count={1} /> </p> </SkeletonTheme> <SkeletonTheme color="#990" highlightColor="#550"> <p> <Skeleton height={250} width={300} count={1} /> </p> </SkeletonTheme>
Duración
Además de los accesorios de height
, width
y color
, también podemos especificar un accesorio de duration
.
<Skeleton duration={2} />
La duración predeterminada es 1.2
. Esto determina cuánto tiempo lleva hacer un ciclo de la animación del esqueleto.
Para obtener más información, consulte la documentación.
Implementación de la interfaz de usuario de Skeleton Screen
Ahora, instalaremos react-loading-skeleton
. Ejecute el siguiente comando en su terminal para instalar el paquete:
npm install react-loading-skeleton
Componente de esqueleto
Vamos a crear un componente esqueleto para nuestros datos de video. Dentro de nuestra carpeta de components
, cree un archivo SkeletonCard.js
y agregue el siguiente código:
import React from "react"; import Skeleton from "react-loading-skeleton"; const SkeletonCard = () => { return ( <section> <h2 className="section-title"> <Skeleton height={30} width={300} /> </h2> <ul className="list"> {Array(9) .fill() .map((item, index) => ( <li className="card" key={index}> <Skeleton height={180} /> <h4 className="card-title"> <Skeleton circle={true} height={50} width={50} /> <Skeleton height={36} width={`80%`} /> </h4> <p className="card-channel"> <Skeleton width={`60%`} /> </p> <div className="card-metrics"> <Skeleton width={`90%`} /> </div> </li> ))} </ul> </section> ); }; export default SkeletonCard;
Hemos creado una lista desordenada. En su interior, hemos utilizado el método Array.fill()
. Debido a que tenemos nueve elementos de datos ficticios, hemos usado el método Array.fill()
para recorrer la longitud de nuestro objeto de items
y lo llenamos sin valor de índice, por lo tanto, hacemos que nuestra matriz esté vacía . Consulte la documentación de Array.fill para saber cómo funciona.
A continuación, mapeamos a través de nuestra matriz vacía para devolver una lista que contiene las propiedades del esqueleto y especificamos el valor de cada una de las propiedades del esqueleto.
Aquí, la height
indica la longitud de un rectángulo de esqueleto y el width
se refiere a la anchura, mientras que el circle
crea la parte redondeada de la interfaz de usuario de esqueleto.
React Loading Skeleton viene con una animación Pulse predeterminada, lo que lo hace útil. Puede crear una animación Pulse que se adapte a su proyecto, pero si me pregunta, me quedaría con la predeterminada.
Finalmente, el código fuente completo está disponible.
Ahora tenemos una interfaz de usuario de pantalla de esqueleto completamente funcional. Nuestro ejemplo muestra el esqueleto durante cinco segundos antes de mostrar el contenido.
Veamos nuestro resultado hasta ahora:
Conclusión
Las pantallas esqueléticas mejoran enormemente la experiencia del usuario al evitar la frustración de enfrentarse a una pantalla completamente en blanco y al darle al usuario una impresión de cómo se verá el contenido antes de que se cargue.
Si no se siente cómodo con ninguno de los paquetes que hemos visto, puede crear su propia interfaz de usuario esquelética haciendo rectángulos y círculos que imiten el diseño de la página.
Comparta sus comentarios y experiencia en la sección de comentarios a continuación. ¡Me encantaría ver qué se te ocurre!
El repositorio de apoyo para este artículo está disponible en Github.
Referencias
- "Todo lo que necesita saber sobre las pantallas de esqueleto", Bill Chung, UX Collective
- “Esqueleto cargando páginas con React”, Anthony Panagi, Octopus Wealth
- "Pantallas esqueléticas con React y React Native", Chris Dolphin, Alligator.io
- “Implementación de la carga del esqueleto en React”, Adrian Bece, DEV