Implementando telas de esqueleto no React
Publicados: 2022-03-10Spinners e loaders têm sido tradicionalmente a maneira de informar aos usuários que o conteúdo vai demorar um pouco para carregar. Embora essa abordagem seja ótima, ela está rapidamente se tornando obsoleta no desenvolvimento moderno. As telas de esqueleto estão se tornando o substituto perfeito para os carregadores tradicionais porque se concentram no progresso e não nos tempos de espera, reduzindo assim a frustração no tempo de carregamento.
Neste artigo, não abordaremos o básico da sintaxe CSS React ou JavaScript, portanto, você não precisa ser um especialista em nenhuma dessas linguagens para acompanhar.

Especialistas em UI e UX nos ensinam que, enquanto os usuários esperam o conteúdo carregar em uma página, devemos mantê-los engajados.
A ideia por trás do uso de spinners para envolver os usuários antes do carregamento do conteúdo é ótima; no entanto, o resultado pode ser menos do que o ideal, porque a maioria dos usuários ficará entediada olhando para um spinner animado fictício como se fosse um relógio. Luke Wroblewski elabora sobre isso.
As telas de esqueleto oferecem uma melhor experiência do usuário, reduzindo a frustração no tempo de carregamento. Ao focar no progresso em vez de nos tempos de espera, ele cria a ilusão para os usuários de que as informações serão exibidas de forma incremental na tela. Bill Chung em sua pesquisa confirma isso.
O que é uma tela de esqueleto?
Uma tela de esqueleto é uma versão da interface do usuário que não contém conteúdo real; em vez disso, ele imita o layout da página mostrando seus elementos em uma forma semelhante ao conteúdo real à medida que é carregado e disponibilizado (ou seja, quando a latência da rede permite).
Uma tela de esqueleto é essencialmente um wireframe da página, com caixas de espaço reservado para texto e imagens.
O que é único sobre uma tela de esqueleto?
Uma interface de usuário de esqueleto se assemelha à interface de usuário real da página, para que os usuários entendam a rapidez com que o aplicativo da Web ou para dispositivos móveis será carregado antes mesmo que o conteúdo seja exibido. Aqui estão algumas razões pelas quais você pode querer considerar o uso de telas de esqueleto em seu próximo projeto:
- imitar o layout de uma página é mais fácil com uma tela de esqueleto,
- conteúdo carrega progressivamente (não todos de uma vez).
As telas de esqueleto também são chamadas de:
- elementos fantasmas,
- marcadores de conteúdo,
- carregadores de conteúdo.
Blockchain.com, YouTube, Facebook, Medium e outras grandes empresas de tecnologia exibem telas de esqueleto enquanto seu conteúdo é carregado para impulsionar o UX.
Blockchain.com

Médio


Tipos de telas de esqueleto
Existem diferentes tipos de telas de esqueleto. Os principais são marcadores de posição de texto e marcadores de posição de imagem (ou cor).
A maioria dos desenvolvedores prefere usar espaços reservados de texto como o esqueleto da interface do usuário em suas páginas porque são fáceis de construir e o desenvolvedor não exige nenhum detalhe sobre a substância do conteúdo real; em vez disso, o esqueleto imita a interface do usuário.
Espaços reservados de cores são mais difíceis de construir porque exigem detalhes sobre o conteúdo.
Alguns pacotes populares facilitam a implementação de telas de esqueleto em aplicativos da web. Vamos dar uma olhada mais de perto em ambos:
- Reagir espaço reservado
- Reagir Carregando Esqueleto
Veremos os prós e contras de cada pacote, antes de considerar qual usar para nosso aplicativo.
Reagir espaço reservado
Prós
- Os componentes de espaço reservado são usados para criar uma interface do usuário de esqueleto personalizada.
- Animação de pulso (ou seja, efeito de movimento em um elemento) é suportada.
- Ele vem com uma API baseada em componentes.
Contras
- Os componentes do esqueleto são mantidos separadamente, portanto, a atualização dos estilos de um componente possivelmente requer a atualização do componente do esqueleto também.
- A curva de aprendizado não é linear porque existem vários componentes para diferentes necessidades.
O seguinte é um exemplo de um componente esqueleto usando o pacote 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>
Importando TextBlock
e RectShape
de react react-placeholder/lib/placeholder
e ReactPlaceholder
de react react-placeholder
, criamos um componente funcional chamado GhostPlaceholder
. GhostPlaceholder
tem um div, e dentro do div usamos o componente RectShape, que descreve as dimensões de um retângulo, passa o valor de qualquer cor e define os estilos do retângulo.
Em seguida, usamos o componente TextBlock
para definir os valores das linhas e da cor. O componente TextBlock
define o número de linhas e a cor do texto.
Passamos MyComponent
como um filho do componente ReactPlaceholder
, que recebe ready
e o componente GhostPlaceholder
como valores para suas props ready
e customPlaceholder
.
O MyComponent
será carregado quando a interface do usuário da tela de esqueleto for mostrada.
Para saber mais, consulte a documentação.
Reagir Carregando Esqueleto
Prós
- É baseado em API e possui um componente com adereços para toda a personalização.
- Ele pode ser usado como um componente de esqueleto separado e também dentro de qualquer componente diretamente, por isso é flexível.
- Ele suporta temas e animação Pulse.
Contras
- É fácil de implementar para uma interface simples de esqueleto, mas complicado para esqueletos mais complexos.
- Ter um componente de esqueleto separado dificultará a manutenção quando a interface do usuário e os estilos forem alterados.
O seguinte é um exemplo 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
e SkeletonTheme
da biblioteca react-loading-skeleton
e, em seguida, criamos um componente funcional que renderiza o componente SkeletonTheme
, com color
e hightlightColor
como propriedades.
O componente SkeletonTheme
é usado para criar temas (por exemplo, adicionar efeitos de cor à interface do usuário do esqueleto).
Finalmente, dentro da seção, definimos o componente Skeleton
, com as propriedades height e width e seus valores apropriados passados.
Criando uma interface do usuário de tela de esqueleto semelhante ao YouTube
Vamos criar uma tela de esqueleto semelhante ao YouTube, usando React Loading Skeleton, para mostrar como uma interface de usuário de esqueleto funciona.
Configurar o React
A maneira mais fácil de configurar o React é usar o Create React App, que é “uma maneira oficialmente suportada de criar aplicativos React de página única. Ele oferece uma configuração de construção moderna sem configuração.”
Vamos usá-lo para inicializar o aplicativo que vamos construir. No seu terminal, execute o comando abaixo:
npx create-react-app skeleton-screens && cd skeleton-screens
Depois que a instalação estiver concluída, inicie o servidor React executando npm start
:

Crie a interface do usuário do YouTube sem uma tela de esqueleto
Primeiro, vamos inserir dados fictícios do YouTube. Os endpoints reais normalmente seriam usados em vez de dados fictícios, mas neste tutorial usaremos dados fictícios.
Crie um arquivo em sua pasta src/
e nomeie-o data.js
, adicione o seguinte código a ele.
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 o formato do YouTube, criamos dados fictícios que possuem uma matriz de objetos, com propriedades como ID, imagem, título, número de visualizações e data de publicação.

Em seguida, vamos criar nossa IU do YouTube. Teremos três componentes:
Card | Contém os detalhes da miniatura do vídeo, título, número de visualizações, data de publicação e canal. |
CardList | Retorna todos os cartões em uma linha. |
App | Monta nosso objeto dummyData , carrega o esqueleto da interface do usuário por dois segundos e retorna o componente CardList . |
Dentro da sua pasta src
, crie uma pasta e nomeie-a components
. Dentro da pasta de components
, crie um arquivo Card.js
, adicione o seguinte código a ele:
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;
Criamos um componente Card
. Dentro dele, importamos o React
do react
e desconstruímos o item
e os adereços do channel
para que possam ser usados no componente Card
. Cada componente de item Card
que exibe um vídeo mostrará a miniatura, o número de visualizações, a data de publicação e o título.
Componente CardList
Dentro da pasta de components
, crie um arquivo CardList.js e adicione o seguinte código a ele:
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;
Neste componente, importamos o componente Card
que criamos. O cartão aceita as props item
e channel
, que obtemos mapeando através do list.items
. Em seguida, exportamos esse componente como CardList
, porque o usaremos em nosso componente App
.
Nota : O array de itens que é mapeado neste componente é o array de objetos em nosso dummyData
.
Componente do aplicativo
Dentro do arquivo app.js no diretório src/
, exclua o código que está lá e adicione o seguinte a ele.
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;
Neste componente, importamos os useState
e useEffect
junto com o React
e os outros arquivos que criamos e que serão necessários no componente App
.
Como nossos dados são dados fictícios, precisamos simular como os dados da API carregando o conteúdo após um tempo limite de dois segundos, usando o método JavaScript setTimeout
.
Em seguida, no componente App
, criamos um estado de vídeo e definimos o estado como um array vazio usando useState
.
Para carregar nossos dados fictícios, usaremos o gancho useEffect
. Em nosso gancho, criamos uma variável timer que contém a função setTimeout
()
. Dentro da função, definimos nosso estado de vídeo para nosso objeto dummyData
e garantimos que os dados sejam carregados após dois segundos e, por último, cancelamos o cronômetro durante a desmontagem.
Por fim, mapeamos nosso estado de vídeo e retornamos o elemento section que contém a list-section
e o componente CardList
com suas props list.
Adicionando CSS
Até agora, usamos muitas classes sem CSS real. Dentro da pasta src
, exclua tudo em App.css
e substitua-o pelo código a seguir;
.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%; } }
Vamos ver como é a nossa interface do YouTube sem a tela de esqueleto. Você pode ver que quando a página é carregada, uma tela branca aparece por dois segundos e, em seguida, os dados são carregados imediatamente.

Usando React Loading Skeleton
Ao contrário de outras bibliotecas nas quais você criaria meticulosamente uma tela de esqueleto para corresponder aos tamanhos de fonte, alturas de linha e margens do seu conteúdo, o componente Skeleton
foi projetado para ser usado diretamente em seus componentes, no lugar do conteúdo que está sendo carregado.
Vamos examinar algumas razões pelas quais escolhemos o React Loading Skeleton em vez de outros.
Tema
React Loading Skeleton suporta temas. Assim, você pode alterar facilmente as cores de todos os componentes do esqueleto usando SkeletonTheme
e passar valores para as props
de cor .
Abaixo está um exemplo mostrando como 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>

Duração
Além das props height
, width
e color
, também podemos especificar uma prop de duration
.
<Skeleton duration={2} />
A duração é padronizada para 1.2
. Isso determina quanto tempo leva para fazer um ciclo da animação do esqueleto.
Para saber mais, confira a documentação.
Implementando a interface do usuário da tela de esqueleto
Agora, vamos instalar react-loading-skeleton
. Execute o seguinte comando em seu terminal para instalar o pacote:
npm install react-loading-skeleton
Componente de esqueleto
Vamos criar um componente esqueleto para nossos dados de vídeo. Dentro da nossa pasta de components
, crie um arquivo SkeletonCard.js
e adicione o seguinte 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;
Criamos uma lista não ordenada. Dentro dele, usamos o método Array.fill()
. Como temos nove itens de dados fictícios, usamos o método Array.fill()
para percorrer o comprimento de nosso objeto items
e o preenchemos sem nenhum valor de índice, portanto, tornando nosso array vazio . Consulte a documentação do Array.fill para saber como funciona.
Em seguida, mapeamos nosso array vazio para retornar uma lista contendo as propriedades do esqueleto e especificamos o valor de cada uma das propriedades do esqueleto.
Aqui, a height
conota o comprimento de um retângulo de esqueleto e a width
se refere à largura, enquanto o circle
cria a parte arredondada da interface do usuário do esqueleto.
React Loading Skeleton vem com animação Pulse padrão, o que o torna útil. Você poderia criar uma animação Pulse para se adequar ao seu projeto, mas se você me perguntar, eu ficaria com o padrão.
Finalmente, o código-fonte completo está disponível.
Agora temos uma interface de usuário de tela de esqueleto totalmente funcional. Nosso exemplo mostra o esqueleto por cinco segundos antes de mostrar o conteúdo.
Vejamos nosso resultado até agora:

Conclusão
As telas de esqueleto melhoram tremendamente a experiência do usuário, evitando a frustração de enfrentar uma tela totalmente em branco e dando ao usuário uma impressão de como será o conteúdo antes de ser carregado.
Se você não estiver confortável com nenhum dos pacotes que analisamos, você pode criar sua própria interface de usuário de esqueleto criando retângulos e círculos que imitam o layout da página.
Por favor, compartilhe seus comentários e experiências na seção de comentários abaixo. Eu adoraria ver o que você inventa!
O repositório de suporte para este artigo está disponível no Github.
Referências
- “Tudo o que você precisa saber sobre telas de esqueleto”, Bill Chung, UX Collective
- “Skeleton Loading Pages With React”, Anthony Panagi, Octopus Wealth
- “Telas de esqueleto com React e React Native”, Chris Dolphin, Alligator.io
- “Implementing Skeleton Loading In React ”, Adrian Bece, DEV