在 React 中實現骨架屏幕

已發表: 2022-03-10
快速總結 ↬在本教程中,您將了解什麼是骨架屏幕 UI 和某些類型的骨架屏幕庫,以及它們的優缺點。 我們將使用 React Loading Skeleton 構建一個類似於 YouTube 的骨架屏幕 UI。 然後,您可以自己嘗試使用您選擇的骨架屏幕 React 包。

Spinners 和 loader 傳統上是告訴用戶內容需要一段時間才能加載的方式。 雖然這種方法很棒,但它在現代開發中很快就過時了。 骨架屏幕正在成為傳統加載器的完美替代品,因為它們專注於進度而不是等待時間,從而減少了加載時間的挫敗感。

在本文中,我們不會介紹 CSS React 或 JavaScript 語法的基礎知識,因此您無需成為這兩種語言的專家即可學習。

loader 和骨架屏 UI 的區別
loader和骨架屏UI的區別(大預覽)

UI 和 UX 專家告訴我們,當用戶等待內容加載到頁面上時,我們應該讓他們保持參與。

在內容加載之前使用微調器吸引用戶的想法很棒; 但是,結果可能不太理想,因為大多數用戶會厭倦地盯著像時鐘一樣的虛擬動畫微調器。 Luke Wroblewski 對此進行了詳細闡述。

骨架屏幕通過減少加載時間的挫敗感來提供更好的用戶體驗。 通過關注進度而不是等待時間,它為用戶創造了一種錯覺,即信息將逐漸顯示在屏幕上。 Bill Chung 在他的研究中證實了這一點。

什麼是骨架屏?

骨架屏幕是不包含實際內容的 UI 版本; 相反,它通過在加載和變為可用時(即在網絡延遲允許時)以類似於實際內容的形狀顯示其元素來模仿頁面的佈局。

骨架屏幕本質上是頁面的線框,帶有用於文本和圖像的佔位符框。

跳躍後更多! 繼續往下看↓

骨架屏幕有什麼獨特之處?

骨架 UI 類似於頁面的實際 UI,因此用戶甚至可以在內容顯示之前了解 Web 或移動應用程序的加載速度。 以下是您可能要考慮在下一個項目中使用骨架屏幕的幾個原因:

  • 使用骨架屏幕更容易模仿頁面的佈局,
  • 內容逐漸加載(不是一次全部加載)。

骨架屏幕也稱為:

  • 鬼元素,
  • 內容佔位符,
  • 內容加載器。

Blockchain.com、YouTube、Facebook、Medium 和其他大型科技公司在加載內容時顯示骨架屏幕以提升用戶體驗。

區塊鍊網

Blockchain.com 骨架屏 UI
Blockchain.com 的部分加載狀態(注意在圖形分析中如何使用骨架)(大預覽)

中等的

中型骨架屏UI
Medium 的骨架 UI(大預覽)

領英

LinkedIn 骨架屏幕 UI
2018年LinkedIn首頁加載狀態(大圖預覽)

骨架屏幕的類型

有不同種類的骨架屏。 主要的是文本佔位符和圖像(或顏色)佔位符。

大多數開發人員更喜歡在他們的頁面上使用文本佔位符作為骨架 UI,因為它們易於構建,並且開發人員不需要任何有關實際內容實質的細節; 相反,骨架模仿 UI。

顏色佔位符更難構建,因為它們需要有關內容的詳細信息。

一些流行的包使在 Web 應用程序中實現骨架屏幕更容易。 讓我們仔細看看它們:

  • 反應佔位符
  • 反應加載骨架

在考慮將哪個包用於我們的應用程序之前,我們將查看每個包的優缺點。

反應佔位符

優點

  • 佔位符組件用於創建自定義骨架 UI。
  • 支持脈衝動畫(即元素上的運動效果)。
  • 它帶有一個基於組件的 API。

缺點

  • 骨架組件是單獨維護的,因此更新組件的樣式可能也需要更新骨架組件。
  • 學習曲線不是線性的,因為有多個組件可以滿足不同的需求。

以下是使用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>

react-placeholder/lib/placeholder導入TextBlockRectShape ,從react-placeholder -placeholder 導入ReactPlaceholder ,我們創建了一個名為GhostPlaceholder的功能組件。 GhostPlaceholder有一個 div,在 div 中我們使用了 RectShape 組件,它描述了矩形的尺寸,傳遞任何顏色的值,並定義了矩形的樣式。

接下來,我們使用TextBlock組件來設置行和顏色的值。 TextBlock組件定義文本的行數和顏色。

我們將MyComponent作為ReactPlaceholder組件的子組件傳遞,該組件接收readyGhostPlaceholder組件作為其readycustomPlaceholder道具的值。

MyComponent將在顯示骨架屏幕 UI 時加載。

要了解更多信息,請查看文檔。

反應加載骨架

優點

  • 它是基於 API 的,它有一個組件,其中包含用於所有自定義的 props。
  • 它可以作為單獨的骨架組件使用,也可以直接在任何組件內部使用,因此非常靈活。
  • 它支持主題和脈衝動畫。

缺點

  • 對於簡單的骨架 UI,實現起來很容易,但對於更複雜的骨架,實現起來就很複雜。
  • 當 UI 和样式發生變化時,擁有一個單獨的骨架組件將使其更難維護。

以下是 React 加載骨架的示例:

 import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; const SkeletonComponent = () => ( <SkeletonTheme color="#202020" highlightColor="#444"> <section> <Skeleton height={50} width={50} /> </section> </SkeletonTheme> );

我們從react-loading-skeleton庫中導入了SkeletonSkeletonTheme ,然後創建了一個渲染SkeletonTheme組件的功能組件,其中colorhightlightColor作為屬性。

SkeletonTheme組件用於主題化(例如,向骨架 UI 添加顏色效果)。

最後,在該部分中,我們定義了Skeleton組件,並傳入了 height 和 width 屬性及其適當的值。

構建類似 YouTube 的骨架屏幕 UI

讓我們使用 React Loading Skeleton 創建一個類似於 YouTube 的骨架屏幕,以展示骨架 UI 的工作原理。

設置反應

設置 React 最簡單的方法是使用 Create React App,這是“一種官方支持的創建單頁 React 應用程序的方法。 它提供了無需配置的現代構建設置。”

我們將使用它來引導我們將要構建的應用程序。 從您的終端,運行以下命令:

 npx create-react-app skeleton-screens && cd skeleton-screens

安裝完成後,通過運行npm start React 服務器:

React 應用程序 - 腳手架 React 應用程序
React 歡迎頁面(大預覽)

創建沒有骨架屏幕的 YouTube 用戶界面

首先,讓我們輸入 YouTube 虛擬數據。 通常會使用真實端點而不是虛擬數據,但在本教程中,我們將使用虛擬數據。

src/文件夾中創建一個文件,並將其命名為data.js ,向其中添加以下代碼。

 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;

為了複製 YouTube 的格式,我們創建了具有對像數組的虛擬數據,這些對象具有 ID、圖像、標題、觀看次數和發布日期等屬性。

接下來,讓我們創建我們的 YouTube 用戶界面。 我們將包含三個組件:

Card 保存視頻縮略圖、標題、觀看次數、發布日期和頻道的詳細信息。
CardList 返回連續的所有卡片。
App 掛載我們的dummyData對象,加載骨架 UI 兩秒鐘,然後返回CardList組件。

在您的src文件夾中,創建一個文件夾並將其命名為components 。 在components文件夾中,創建一個Card.js文件,向其中添加以下代碼:

 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;

我們創建了一個Card組件。 在其中,我們從react導入了React ,我們解構了itemchannel props,以便它們可以在Card組件中使用。 每個顯示一個視頻的Card項目組件都將顯示縮略圖、觀看次數、發布日期和標題。

卡片列表組件

components文件夾中,創建一個CardList.js文件並向其中添加以下代碼:

 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;

在這個組件中,我們已經導入了我們創建的Card組件。 卡片接受我們通過list.items映射獲得的itemchannel道具。 然後我們將此組件導出為CardList ,因為我們將在App組件中使用它。

注意在這個組件中映射的 items 數組是我們dummyData中的對像數組。

應用組件

src/目錄中的app.js文件中,刪除那裡的代碼並將以下內容添加到其中。

 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;

在這個組件中,我們將useStateuseEffect鉤子與React以及我們創建的其他文件一起導入,這些文件將在App組件中使用。

因為我們的數據是虛擬數據,所以我們需要像 API 數據一樣模擬它,方法是在兩秒超時後加載內容,使用 JavaScript 的setTimeout方法。

接下來,在App組件中,我們創建一個視頻狀態,並使用useState將狀態設置為一個空數組。

要加載我們的虛擬數據,我們將使用useEffect掛鉤。 在我們的鉤子中,我們創建了一個變量 timer 來保存setTimeout ()函數。 在函數內部,我們將視頻狀態設置為dummyData對象,並確保數據在兩秒後加載,最後,我們在卸載時取消計時器。

最後,我們映射視頻狀態並返回包含list-sectionCardList組件及其列表道具的 section 元素。

添加 CSS

到目前為止,我們已經使用了很多沒有實際 CSS 的類。 在src文件夾中,刪除App.css中的所有內容,並將其替換為以下代碼;

 .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%; } }

讓我們看看我們的 YouTube 用戶界面在沒有骨架屏幕的情況下是什麼樣子的。 可以看到,頁面加載的時候,出現白屏兩秒,然後數據加載很快。

無骨架屏的類似 YouTube 的 UI
無骨架屏的類似 YouTube 的 UI(大預覽)

使用 React 加載骨架

與其他需要精心製作骨架屏幕以匹配內容的字體大小、行高和邊距的庫不同, Skeleton組件旨在直接在您的組件中使用,以代替正在加載的內容。

讓我們回顧一下為什麼我們選擇 React Loading Skeleton 而不是其他的一些原因。

主題化

React Loading Skeleton 支持主題化。 因此,您可以使用SkeletonTheme輕鬆更改所有骨架組件的顏色,並將值傳遞給 color props

下面是一個例子,展示了它是如何工作的:

 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> 
主題效果在行動
實際中的主題效果(大預覽)

期間

除了heightwidthcolor屬性,我們還可以指定一個duration屬性。

 <Skeleton duration={2} />

持續時間默認為1.2 。 這決定了完成一個骨架動畫週期需要多長時間。

要了解更多信息,請查看文檔。

實現骨架屏幕 UI

現在,我們將安裝react-loading-skeleton 。 在終端中運行以下命令來安裝軟件包:

 npm install react-loading-skeleton

骨架組件

讓我們為視頻數據創建一個骨架組件。 在我們的components文件夾中,創建一個SkeletonCard.js文件,並添加以下代碼:

 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;

我們創建了一個無序列表。 在其中,我們使用了Array.fill()方法。 因為我們有九個虛擬數據項,所以我們使用Array.fill()方法循環遍歷我們的items對象的長度,並用沒有索引值填充它,從而使我們的數組為。 請參閱 Array.fill 文檔以了解其工作原理。

接下來,我們通過我們的空數組進行映射以返回一個包含骨架屬性的列表,並且我們指定了每個骨架屬性的值。

這裡, height表示骨架矩形的長度, width表示寬度,而circle表示骨架 UI 的圓形部分。

React Loading Skeleton 帶有默認的 Pulse 動畫,這讓它很方便。 您可以創建適合您項目的 Pulse 動畫,但如果您問我,我會堅持使用默認值。

最後,完整的源代碼可用。

我們現在有一個功能齊全的骨架屏幕 UI。 我們的示例在顯示內容之前顯示骨架五秒鐘。

讓我們看看到目前為止的結果:

類似 YouTube 的 UI 加上骨架屏 UI
我們類似於 YouTube 的骨架 UI(大預覽)

結論

骨架屏幕通過避免面對完全空白屏幕的挫敗感並讓用戶在加載之前對內容的外觀有一個印象,極大地改善了用戶體驗。

如果您對我們看過的任何軟件包都不滿意,您可以通過製作模仿頁面佈局的矩形和圓形來創建自己的骨架 UI。

請在下面的評論部分分享您的反饋和經驗。 我很想看看你想出了什麼!

本文的支持 repo 可在 Github 上找到。

參考

  • “關於骨架屏幕你需要知道的一切”,Bill Chung,UX Collective
  • “使用 React 加載頁面的骨架”,Anthony Panagi,Octopus Wealth
  • “帶有 React 和 React Native 的骨架屏幕”,Chris Dolphin,Alligator.io
  • “在 React 中實現骨架加載”,Adrian Bece,DEV