「前端UI组件」如何实现一个骨架屏组件

简介: 用技术实现梦想,用梦想打开前端技术之门。今天分享如何实现一个骨架屏组件。

前言

在过去的两个月中,我们的移动端项目重构UI组件,表单组件、Layout布局、弹框提示、导航、Card卡片等基础组件已经完成并应用到日常功能开发中。

一期的开发中,我们把基础的常用的组件基本都完成了。二期计划将一些不太常用但是能提升交互体验组件纳入开发计划,比如骨架屏,比如步骤条等。组件开发系列第一篇,让我们一起来实现一个骨架屏组件的开发吧。

项目使用的react框架,组件开发使用的hooks函数式组件。


知其然

何为骨架屏

Skeleton Screen(骨架屏)就是在页面数据尚未加载前先给用户展示出页面的大致结构,直到请求数据返回后再渲染页面,补充进需要显示的数据内容。

如何页面数据没有加载完毕,会展示空白,骨架屏的主要作用的替代白屏,展示页面的大致结构,直到页面数据完全返回。


何时使用

  • 首次加载数据时,可能出现白屏,可以使用骨架屏替代白屏展示;
  • 数据量较大的列表中,每次数据返回前,可以使用骨架屏做临时展示;
  • 某些授权中间页,一般授权中间页并没有内容,所以会出现短暂的空白,可以使用骨架屏替代空白页展示。

以上几种情况会造成页面空白,使用骨架屏代替展示,可以提升用户体验,视觉上是可以看到页面有内容的,且内容铺满了屏幕。


知其所以然

图解组件

image.jpeg

上图是我完成的骨架屏的实际效果。因为骨架屏是替代页面实际内容暂时的占位,所以一个常见骨架屏的结构和常见的列表结构很像,包括头像、标题、段落三个部分。想实现一个基本的骨架屏,也基本涵盖了这三个部分,而这三块内容的风格可以是直角也可以是圆角,为了增加用户体验,可以增加动画效果,其中头像是非必须的,可以不展示,标题和段落是必须的,但是展示长度可以变化。既然骨架屏是暂时的,那么一旦数据加载完成,实际内容需要渲染出来,骨架屏需要被隐藏,因此骨架屏展示可以有控制开关。

上面这段话,包含了基础的骨架屏开发的全部关键点,也帮我们屡清楚了开发是思路。 组件内容三部分:头像、标题、段落,组件传参props:头像对象、标题对象、段落对象、展示开关、动画效果、展示风格。


粮草先行

兵法有云:“兵马未动粮草先行”,组价开发也需要有序进行。前面,我们图解了骨架屏组件,清楚了它的结构和样式,不同样式和不同结构相互组合,能排列出不同的展示结构,而控制这些结构的关键在于组件的props传参,所以我们想开发一个组件,第一步要先定一下它的props传参,这也就是我们说的"粮草"。


PropTypes

在项目中使用自定义组件时,需要对组件的props进行类型检测。而React提供了专门的库,可以校验组件的props类型,也可以做一些特定的限制。关于PropTypes的详细介绍,可以看我另一篇文章【知识点】PropTypes提供的验证器,这里不做详细介绍。下面列出骨架屏组件的props类型校验,并做一一解读。

Skeleton.propTypes= {
avatar: PropTypes.oneOfType([PropTypes.bool, PropTypes.Object]), // 是否显示头像占位图title: PropTypes.bool, // 是否显示标题占位图paragraph: PropTypes.oneOfType([PropTypes.bool, PropTypes.Object]), // 是否显示段落占位图show: PropTypes.bool, // 是否显示骨架屏,传 false 时会展示子组件内容active: PropTypes.bool, // 是否开启动画round: PropTypes.bool, // 是否将标题和段落显示为圆角风格};
Skeleton.defaultProps= {
avatar: false,
title: true,
paragraph: true,
show: true,
active: false,
round: false,
};


avatar

是否显示头像占位图。

校验类型是布尔值和对象。

布尔值:控制图像模块是否展示,默认为false-不展示,可选true-展示。


对象:控制图像展示风格,包括:active-是否有动画效果,默认false-没有动画效果。shape-头像风格,默认circle-圆形,可选square-矩形;size-头像大小,默认default,可选large-较大, small-较小,number-具体值。


title

是否显示标题占位图。

校验类型是布尔值。

布尔值:控制图像模块是否展示,默认为true-展示,可选false-不展示。


paragraph

是否显示段落占位图

校验类型是布尔值和对象。

布尔值:控制段落模块是否展示,默认为true-展示,可选false-不展示。


对象:控制段落展示风格,包括:rows-设置段落的行数;width-设置段落的宽度,若为数组时则为对应段落每行宽度,反之则是最后一行的宽度。


show

骨架屏是否展示的开关。

校验类型是布尔值。

布尔值:控制骨架屏是否展示,默认为true-展示,可选false-不展示。


active

骨架屏是否开启动画效果。

校验类型是布尔值。

布尔值:控制骨架屏风格是否开启动画效果,默认为false-不开启动画效果,可选true-开启动画效果。


round

骨架屏风格是否是圆角。

校验类型是布尔值。

布尔值:控制骨架屏风格是否是圆角,默认为false-不是圆角,可选true-是圆角。



整顿兵马

props传参已设置好,接下来就可以进行下一步对“兵马”的整顿了。

正如我们前面所讲的,基础的骨架屏分为三个部分:头像、标题、段落,且实际内容加载完成之后可以选择隐藏骨架屏。所以“兵马”的布阵应该是这样的:

/** @name class前缀 */constprefixCls='fly-skeleton';
/** @name 根元素class */constrootCls=classnames(prefixCls, className, {
[`${prefixCls}--round`]: round,
[`${prefixCls}--active`]: active,
});
/** @name 内容class */constcontentCls=classnames(`${prefixCls}--content`);
return (
<>    {show? (
<divclassName={rootCls}>        {avatarContent()}
<divclassName={contentCls}>          {titleContent()}
          {paragraphContent()}
</div></div>    ) : (
children    )}
</>);


当参数show设置为true时,展示骨架屏内容,反之展示骨架屏组件内容包裹的子组件的内容即页面真实内容。其中头像、标题、段落又分别提炼成了方法,这样的处理,让组件的结构很清楚,易于维护。下面,我们来看这三块内容到底是怎么实现的。


头像

头像模块的功能实现如下:

/** @name 是否展示头像 */consthasAvatar=!!avatar;
/** @name 尺寸枚举 */constsizgClsObj= {
large: 'lg',
small: 'sm',
default: null,
};
/** * 获取对象属性值 * @param {void} 无 * @return {render} 展示内容 */constgetTypeOfObject=prop=> {
if (prop&&typeofprop==='object') {
returnprop;
  }
return {};
};
/** * 头像展示 * @param {void} 无 * @return {render} 展示内容 */constavatarContent= () => {
if (hasAvatar) {
/** @name 头像对象数据 */letavatarObj= {
active: false,
shape: 'circle',
size: 'default',
...getTypeOfObject(avatar),
    };
/** @name 头像父容器class */constheadCls=classnames(`${prefixCls}__header`);
/** @name 头像class */constavatarCls=`${prefixCls}__avatar`;
/** @name 尺寸class */constsizeCls=sizgClsObj[avatarObj.size] &&classnames(`${avatarCls}--${sizgClsObj[avatarObj.size]}`);
/** @name 形状class */constshapeCls=classnames(`${avatarCls}--${avatarObj.shape}`);
/** @name 尺寸内联样式 */constsizeStyle=typeofavatarObj.size==='number'? {
width: avatarObj.size,
height: avatarObj.size,
lineHeight: `${avatarObj.size}px`,
          }
        : {};
return (
<divclassName={headCls}><spanclassName={classnames(avatarCls, sizeCls, shapeCls, className)} style={{ ...sizeStyle }} /></div>    );
  }
};

在上面的代码中,主要做了以下功能:

1.展示控制

通过avatar参数控制展示与否,如果骨架屏组件上的props没有avatar参数,则不展示头像模块,反之则展示;

2.头像大小

通过avatar参数中的size属性控制头像大小。

size值默认是default,可以设置固定变量值如:large-较大,small-较小,也可是设置具体数值,当设置具体数值的时候,会使用内联样式进行覆盖,将头像的宽度和高度的值设置为当前数值,单位均为像素。

3.头像风格

通过avatar参数中的shape属性控制头像风格。

shape的值默认是circle,也就是圆角风格,头像展示样式为圆形,如果shape的值设置为square,那么展示效果会为矩形。


标题

标题模块的功能实现如下:

/** @name 是否展示标题 */consthasTitle=!!title;
/** * 获取对象属性值 * @param {void} 无 * @return {render} 展示内容 */constgetTypeOfObject=prop=> {
if (prop&&typeofprop==='object') {
returnprop;
  }
return {};
};
/** * 标题展示 * @param {void} 无 * @return {render} 展示内容 */consttitleContent= () => {
if (hasTitle) {
/** @name 标题class */consttitleCls=classnames(`${prefixCls}__title`);
/** @name 标题style */consttitleStyle= {
width: !hasAvatar?'35%' : '50%',
...getTypeOfObject(title),
    };
return<h3className={titleCls} style={titleStyle} />;
  }
};

标题模块的实现相较于头像会简单一些:

1.展示控制

通过title参数控制展示与否,默认展示标题,如果想隐藏标题,可以设置title的值为false;

2.标题宽度

通过title参数中的width属性控制标题宽度。

如果width变量设置了具体值,标题宽度取设置的值。不设置值的情况下,如果有头像则宽度的值默认为50%,如果没有头像则宽度的值默认为35%,当有头像的情况下,标题父容器的宽度会变小,所以对应值设置要大于没有头像的值。


段落

段落模块的功能实现如下:

/** @name 是否展示段落 */consthasParagraph=!!paragraph;
/** * 获取对象属性值 * @param {void} 无 * @return {render} 展示内容 */constgetTypeOfObject=prop=> {
if (prop&&typeofprop==='object') {
returnprop;
  }
return {};
};
/** * 段落-获取段落宽度 * @param {number} index 段落的索引 * @param {object} obj 段落的数据对象 * @return {number} 计算之后的宽度 */constgetParagraphWidth= (index, obj) => {
const { width, rows=2 } =obj;
// =>true: 如果width的值是数组时,设置每行对应宽度if (Array.isArray(width)) {
returnwidth[index];
  }
// =>true: 如果width的值不是数组时,设置为最后一行的宽度if (rows-1===index) {
returnwidth;
  }
returnundefined;
};
/** * 段落展示 * @param {void} 无 * @return {render} 展示内容 */constparagraphContent= () => {
if (hasParagraph) {
/** @name 段落class */constparagraphCls=classnames(`${prefixCls}__paragraph`);
letparagraphObj= {};
// =>true: 有标题但是没有头像,默认3行,其他是2行if (!hasAvatar&&hasTitle) {
paragraphObj.rows=3;
    } else {
paragraphObj.rows=2;
    }
// => true: 没有标题或者没有头像,最后一个段落的宽度是61%if (!hasAvatar||!hasTitle) {
paragraphObj.width='61%';
    }
// =>true: paragraph传参有值时,对默认值进行覆盖paragraphObj= {
...paragraphObj,
...getTypeOfObject(paragraph),
    };
/** @name 段落的行数组 */constrowList= [...Array(paragraphObj.rows)].map((_, index) =><likey={index} style={{ width: getParagraphWidth(index, paragraphObj) }} />);return<ulclassName={paragraphCls}>{rowList}</ul>;  }
};

段落的实现相对复杂一些,其中主要处理是针对段落行数的处理:

1.展示控制

通过paragraph参数控制展示与否,默认展示标题,如果想隐藏标题,可以设置title的值为false;

2.段落宽度

通过paragraph参数中的width属性控制段落宽度。如果width的值设置为数组,每行的宽度对应为数组中的值,如width值是具体数值或者字符串类型的值,那么段落最后一行的值会被设置。

3.段落的行数

通过paragraph参数中的rows属性控制段落的行数。如果rows属性有值,那么段落行数为rows的值,如果rows属性没有设置值,那么没有头像有标题的情况下,段落行数设置为3行,反之设置为2行。


优秀兵法

到此,”粮草兵马“皆已备齐,骨架屏组件圆满完成。接下来可以尝试在页面中使用它了,让骨架屏成为我们在日常开发中提升用户体验的优秀”兵法“。


组件使用

完整API

Skeleton

属性

说明

类型

默认值

paragraph

是否显示段落占位图

boolean | SkeletonParagraphProps

true

title

是否显示标题占位图

boolean | SkeletonTitleProps

true

avatar

是否显示头像占位图

boolean | SkeletonAvatarProps

false

show

是否显示骨架屏,传 false 时会展示子组件内容

boolean

true

active

是否开启动画

boolean

false

round

是否将标题和段落显示为圆角风格

boolean

false


SkeletonParagraphProps

属性

说明

类型

默认值

width

设置段落的宽度,如果是数组则为每行的宽度,反之为最后一行的宽度

number | string | Array<number | string>

-

rows

设置段落的行数

number

-


SkeletonTitleProps

属性

说明

类型

默认值

width

设置标题宽度

number | string

-


SkeletonAvatarProps

属性

说明

类型

默认值

active

是否开启动画效果,仅在单独使用头像骨架时生效

boolean

false

shape

设置头像的形状,可选值circle | square

string

circle

size

设置头像的大小,可选值number | large | small | default

number | string

default



总结

整个流程下来,我们就实现了一个基础的骨架屏组件,对骨架屏也有了系统的了解,如果自己尝试去实现也会有思路怎么做。

但是,这只是实现了基础功能,并不完善,比如不支持头像、按钮等元素的单独使用。我在完成之后,去antd的官网看了一下它的源码,发现antd的功能做的更加完善,不愧是大厂的项目。从antd处获取的灵感,也让我完善了一下我的代码。所以,我们可以在日常的空闲时间,看一些大厂的源码,他们的功能更加强大、考虑问题更全面、实现思路也更优秀。

开头难吗?有时候挺难的,但是有了这个困难且良好的开头,后面的事就变得简单且顺利了,组件开发也会自然而然,水到渠成。所以,诸君加油。

再次感谢所有的开源项目,可以让像我一样的学习者获取技术上的进步。

目录
相关文章
|
1月前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
188 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
2月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
133 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
1月前
|
JavaScript 数据安全/隐私保护
Vue Amazing UI 组件库(Vue3+TypeScript+Vite 等最新技术栈开发)
Vue Amazing UI 是一个基于 Vue 3、TypeScript、Vite 等最新技术栈开发构建的现代化组件库,包含丰富的 UI 组件和常用工具函数,并且持续不断维护更新中。另外,组件库全量使用 TypeScript,支持自动按需引入和 Tree Shaking 等,能够显著提升开发效率,降低开发成本。
86 5
Vue Amazing UI 组件库(Vue3+TypeScript+Vite 等最新技术栈开发)
|
2天前
|
安全 API 开发者
深入探索ArkUI中的@LocalBuilder装饰器:构建高效可维护的UI组件
在ArkUI框架中,组件化开发至关重要。@LocalBuilder作为自API version 12引入的装饰器,专注于组件内部私有构建,确保封装性与安全性。本文解析其工作原理、参数传递机制及与@Builder的区别,结合典型场景代码示例,助开发者掌握这一工具。通过状态驱动UI更新、复杂组件组合等实践,优化性能并提升代码可维护性,推动高效UI架构构建。
14 0
|
2月前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
107 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
26天前
|
人工智能 自然语言处理 前端开发
Flame:开源AI设计图转代码模型!生成React组件,精准还原UI+动态交互效果
Flame 是一款开源的多模态 AI 模型,能够将 UI 设计图转换为高质量的现代前端代码,支持 React 等主流框架,具备动态交互、组件化开发等功能,显著提升前端开发效率。
359 1
|
3月前
「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件
本篇将带你实现一个自定义天气预报组件。用户可以通过选择不同城市来获取相应的天气信息,页面会显示当前城市的天气图标、温度及天气描述。这一功能适合用于动态展示天气信息的小型应用。
185 38
|
3月前
|
人工智能 开发框架 JavaScript
LowCodeEngine:阿里开源的企业级低代码开发平台,提供预制的 UI 组件和模板,覆盖完整的研发周期
LowCodeEngine 是阿里巴巴开源的低代码开发框架,旨在通过拖拽、配置等简单操作,帮助开发者快速构建复杂的系统页面,提升开发效率和质量。
223 4
LowCodeEngine:阿里开源的企业级低代码开发平台,提供预制的 UI 组件和模板,覆盖完整的研发周期
|
4月前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
319 8
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
4月前
|
UED
「Mac畅玩鸿蒙与硬件31」UI互动应用篇8 - 自定义评分星级组件
本篇将带你实现一个自定义评分星级组件,用户可以通过点击星星进行评分,并实时显示评分结果。为了让界面更具吸引力,我们还将添加一只小猫图片作为评分的背景装饰。
123 6

热门文章

最新文章