One, foreword
In the past two months, our mobile terminal project has reconstructed UI components, form components, Layout Layout, pop-up prompt, navigation, Card and other basic components, which have been completed and applied to daily function development.
In the first phase of development, we have basically completed the common components of the foundation. In the second phase, some components that are not commonly used but can enhance interactive experience will be included in the development plan, such as skeleton screen and step bar. In this first installment of the component development series, let’s implement a skeleton screen component.
Note: Mobile projects use the React framework, component development uses hooks function components.
Two, know it
2.1 What is skeleton screen
The Skeleton Screen shows the user the outline of the page before the data is loaded, and then renders the page after the requested data is returned, adding the data content to display.
How the page data is not loaded will be displayed blank, skeleton screen’s main role is to replace the white screen, showing the rough structure of the page until the page data is fully returned.
2.2 When to Use
1) White screen may appear when data is loaded for the first time, so skeleton screen can be used instead of white screen display;
2) For a list with a large amount of data, the skeleton screen can be used for temporary display before each data return;
3) Some authorization middle pages, general authorization middle page does not have content, so there will be a short blank, can use skeleton screen instead of blank page display;
The above situations will cause blank pages. Using skeleton screen instead of display can improve user experience. Visually, you can see that there is content on the page and the content is spread all over the screen.
3. Know why
3.1 Graphic Components
Above is the actual skeleton screen I finished. Because the skeleton screen is a temporary placeholder for the actual content of the page, the structure of a common skeleton screen is similar to a common list structure, including the head, title, and paragraph. In order to achieve a basic skeleton screen, these three parts are basically covered, and the style of these three pieces of content can be right angles or rounded corners. In order to enhance user experience, animation effects can be added, in which the head picture is not necessary and can not be displayed. Headings and paragraphs are required, but the display length can be changed. Since the skeleton screen is temporary, once the data is loaded, the actual content needs to be rendered and the skeleton screen needs to be hidden, so the skeleton screen display can have control switches.
The above paragraph, including the basic skeleton screen development of all the key points, but also to help us repeatedly clear development is the idea. The component content consists of three parts: image object, title object, and paragraph. Component parameter props: Image object, title object, paragraph object, display switch, animation, and display style.
3.2 Food and grass first
The art of war has a cloud: “soldiers and horses do not move the grain first”, group price development also needs to be orderly. Front, we illustrated the skeleton screen components, clear about its structure and style, different styles and different structure combination, each other can rank the different structure, and control of these structures is the key component of props, so we want to develop a component, the first step is to set up its first props to participate, this also is we say “hay”.
PropTypes
When you use a custom component in a project, you need to type check the props of the component. React provides a library that validates the props types of components and makes certain restrictions. For a more detailed introduction to PropTypes, see the validators provided by PropTypes in my other article. The following lists the props type verification of skeleton screen components and interprets them one by one.
Skeleton.propTypes = {
avatar: PropTypes.oneOfType([PropTypes.bool, PropTypes.Object]), // Whether to display the avatar placeholder
title: PropTypes.bool, // Whether to display the title placeholder map
paragraph: PropTypes.oneOfType([PropTypes.bool, PropTypes.Object]), // Whether to display paragraph placeholders
show: PropTypes.bool, // Whether to display skeleton screen. If false is passed, child component contents will be displayed
active: PropTypes.bool, // Whether to enable animation
round: PropTypes.bool, // Whether to display headings and paragraphs in rounded corners
};
Skeleton.defaultProps = {
avatar: false.title: true.paragraph: true.show: true.active: false.round: false};Copy the code
avatar
Whether to display the profile picture placeholder.
Checksum types are Booleans and objects.
1) Boolean value: controls whether the image module is displayed. The default value is false- no display, and the optional value is true- display.
2) Object: controls the image display style, including: active- animation effect or not, default false- no animation effect; Shape – avatar style, default circle- circle, optional square- rectangle; Size specifies the size of the profile picture. The default value is default. Large specifies the size of the profile picture.
title
Whether to display a title placeholder map.
The check type is a Boolean value.
Boolean: controls whether the image module is displayed. The default value is true- display, and the optional value is false- no display.
paragraph
Whether to display paragraph placeholders
Checksum types are Booleans and objects.
1) Boolean value: controls whether the paragraph module is displayed. The default value is true- display, and the optional value is false- no display.
2) Object: control paragraph display style, including: rows- set the number of paragraphs; Width – Sets the width of the paragraph, if it is an array, the width of the corresponding paragraph line, if it is not the width of the last line.
show
Whether the skeleton screen shows the switch.
The check type is a Boolean value.
Boolean: controls whether the skeleton screen is displayed. The default value is true- display, and the optional value is false- no display.
active
Whether the skeleton screen is animated.
The check type is a Boolean value.
Boolean: Controls whether animation is enabled for skeleton screen style. Default is false- animation is not enabled, and optional is true- animation is enabled.
round
Skeleton screen style is rounded.
The check type is a Boolean value.
Boolean: Controls whether the skeleton screen style is rounded. Default: false- not rounded, optional: true- rounded.
3.3 Reorganize troops and horses
With the props parameter set up, we are ready to perform the next step of retooling.
As we mentioned earlier, the basic skeleton screen is divided into three parts: head, title, and paragraph, with the option to hide the skeleton screen once the actual content is loaded. So the arrangement of the troops and horses should look like this:
/ * *@name The class prefix * /
const prefixCls = 'fly-skeleton';
/ * *@name The root element class */
const rootCls = classnames(prefixCls, className, {
[`${prefixCls}--round`]: round,
[`${prefixCls}--active`]: active,
});
/ * *@name The content class * /
const contentCls = classnames(`${prefixCls}--content`);
return (
<>
{show ? (
<div className={rootCls}>
{avatarContent()}
<div className={contentCls}>
{titleContent()}
{paragraphContent()}
</div>
</div>
) : (
children
)}
</>
);
Copy the code
When show is set to true, the skeleton screen content is displayed. Otherwise, the contents of the subcomponents wrapped by the skeleton screen component content are displayed, that is, the real content of the page. The head, title and paragraph are respectively refined into methods, so that the structure of the component is clear and easy to maintain. Now, let’s see how these three pieces work.
Head portrait
The functions of the profile picture module are as follows:
/ * *@name Whether to show your avatar */
consthasAvatar = !! avatar;/ * *@name Size enumeration */
const sizgClsObj = {
large: 'lg'.small: 'sm'.default: null};/** * gets the object attribute value *@param {void} No *@return {render} Display content */
const getTypeOfObject = prop= > {
if (prop && typeof prop === 'object') {
return prop;
}
return {};
};
/** ** head display *@param {void} No *@return {render} Display content */
const avatarContent = () = > {
if (hasAvatar) {
/ * *@name Avatar object data */
let avatarObj = {
active: false.shape: 'circle'.size: 'default'. getTypeOfObject(avatar), };/ * *@name Head the parent container class */
const headCls = classnames(`${prefixCls}__header`);
/ * *@name Face the class * /
const avatarCls = `${prefixCls}__avatar`;
/ * *@name The class size * /
const sizeCls = sizgClsObj[avatarObj.size] && classnames(`${avatarCls}--${sizgClsObj[avatarObj.size]}`);
/ * *@name Shape class * /
const shapeCls = classnames(`${avatarCls}--${avatarObj.shape}`);
/ * *@name Size inline style */
const sizeStyle =
typeof avatarObj.size === 'number'
? {
width: avatarObj.size,
height: avatarObj.size,
lineHeight: `${avatarObj.size}px`}, {};return (
<div className={headCls}>
<span className={classnames(avatarCls, sizeCls.shapeCls.className)} style={{ . sizeStyle}} / >
</div>); }};Copy the code
In the above code, the following functions are mainly done:
1) Show control
Display is controlled by the Avatar parameter. If the props on the skeleton screen component does not have the Avatar parameter, the avatar module is not displayed. Otherwise, the avatar module is displayed.
2) Head size
Control the size of the avatar by using the size attribute in the avatar parameter.
The size value is default by default. Fixed variable values such as large- large and small- small can be set. Specific values can also be set. When setting specific values, the inline style will be used to override them.
3) Portrait style
Control the avatar style through the Shape attribute in the Avatar parameter.
The default value of shape is circle, which is a rounded corner style, and the display style of the head is round. If shape is set to square, the display will be rectangular.
The title
The functions of the title module are as follows:
/ * *@name Whether to display the title */
consthasTitle = !! title;/** * gets the object attribute value *@param {void} No *@return {render} Display content */
const getTypeOfObject = prop= > {
if (prop && typeof prop === 'object') {
return prop;
}
return {};
};
/** ** *@param {void} No *@return {render} Display content */
const titleContent = () = > {
if (hasTitle) {
/ * *@name Title class * /
const titleCls = classnames(`${prefixCls}__title`);
/ * *@name The headline style * /
const titleStyle = {
width: !hasAvatar ? '35%' : '50%'. getTypeOfObject(title), };return <h3 className={titleCls} style={titleStyle} />; }};Copy the code
The title module is simpler to implement than the avatar:
1) Show control
The title parameter controls whether the title is displayed or not. By default, the title is displayed. If you want to hide the title, you can set the title value to false.
2) Title width
The title width is controlled by the width property in the title parameter.
If the width variable is set to a specific value, the title width takes the set value. If no value is set, the default width is 50% if there is an avatar, and 35% if there is no avatar. If there is an avatar, the width of the title parent container will be smaller, so the corresponding value should be larger than the value without the avatar.
The paragraph
The function of the paragraph module is as follows:
/ * *@name Whether to display paragraphs */
consthasParagraph = !! paragraph;/** * gets the object attribute value *@param {void} No *@return {render} Display content */
const getTypeOfObject = prop= > {
if (prop && typeof prop === 'object') {
return prop;
}
return {};
};
/** * paragraph - gets the paragraph width *@param {number} Index The index of the paragraph *@param {object} Data object * for the obj paragraph@return {number} The calculated width */
const getParagraphWidth = (index, obj) = > {
const { width, rows = 2 } = obj;
// =>true: If width is an array, set the corresponding width for each row
if (Array.isArray(width)) {
return width[index];
}
// =>true: If width is not an array, set it to the width of the last line
if (rows - 1 === index) {
return width;
}
return undefined;
};
/** **@param {void} No *@return {render} Display content */
const paragraphContent = () = > {
if (hasParagraph) {
/ * *@name Paragraph class * /
const paragraphCls = classnames(`${prefixCls}__paragraph`);
let paragraphObj = {};
// =>true: there is a title but no avatar, default 3 lines, other 2 lines
if(! hasAvatar && hasTitle) { paragraphObj.rows =3;
} else {
paragraphObj.rows = 2;
}
// => true: There is no title or avatar, and the width of the last paragraph is 61%
if(! hasAvatar || ! hasTitle) { paragraphObj.width ='61%';
}
// =>true: Paragraph If the parameter has a value, the default value is overwrittenparagraphObj = { ... paragraphObj, ... getTypeOfObject(paragraph), };/ * *@name Line array of paragraphs */
const rowList = [...Array(paragraphObj.rows)].map((_, index) = > <li key={index} style={{ width: getParagraphWidth(index.paragraphObj) }} />);
return <ul className={paragraphCls}>{rowList}</ul>; }};Copy the code
The implementation of paragraphs is relatively complex, and the main processing is for the number of paragraphs:
1) Show control
The paragraph parameter controls whether to display the title. By default, the title is displayed. If you want to hide the title, you can set the value of title to false.
2) Paragraph width
The paragraph width is controlled by the width attribute in the Paragraph parameter. If width is set to an array and the width of each line corresponds to the value in the array, if width is a numeric or string value, then the value of the last line of the paragraph is set.
3) The number of lines of a paragraph
The number of lines in a paragraph is controlled by the rows attribute in the Paragraph parameter. If the rows property has a value, the number of paragraph rows is the value of rows. If the rows property has no value, the number of paragraph rows is set to 3 if no head has a header, and 2 if not.
3.4 Excellent Art of War
By now, the “provisions” have been prepared, and the skeleton screen component is successfully completed. Next, try using it in your pages and make the skeleton screen a great way to improve the user experience in your daily development.
Four, component use
4.1 complete API
Skeleton
attribute | instructions | type | The default value |
---|---|---|---|
paragraph | Whether to display paragraph placeholders | boolean | SkeletonParagraphProps |
title | Whether to display a title placeholder map | boolean | SkeletonTitleProps |
avatar | Whether to display the profile picture placeholder | boolean | SkeletonAvatarProps |
show | Whether to display skeleton screen or display child component content when false is passed | boolean | true |
active | Whether to enable animation | boolean | false |
round | Whether to display headings and paragraphs in rounded corners | boolean | false |
SkeletonParagraphProps
attribute | instructions | type | The default value |
---|---|---|---|
width | Set the width of the paragraph, or the width of each line if it is an array, or the width of the last line if it is an array | number | string |
rows | Sets the number of lines in the paragraph | number | – |
SkeletonTitleProps
attribute | instructions | type | The default value |
---|---|---|---|
width | Set the title width | number | string |
SkeletonAvatarProps
attribute | instructions | type | The default value |
---|---|---|---|
active | Whether to enable animation only works if the avatar skeleton is used alone | boolean | false |
shape | Sets the shape of the avatar, with the optional value circle | square | string |
size | This parameter is optional. Number specifies the size of the profile picture | large | small |
4.2 Complete Code
2 components
skeleton.less
@textColorGrey: rgba(201.201.201.0.2);
@textColorGrey2: rgba(186.186.186.0.3);
.fly-skeleton {
display: table;
width: 100%;
&--active {
.fly-skeleton__avatar..fly-skeleton__title..fly-skeleton__paragraph > li {
background: linear-gradient(90deg, @textColorGrey 25%, @textColorGrey2 37%, @textColorGrey 63%);
background-size: 400% 100%;
animation: fly-skeleton__loading 1.2 s ease infinite;
}
}
&--round {
.fly-skeleton__title..fly-skeleton__paragraph > li {
border-radius: 100px; & -}}content {
display: table-cell;
width: 100%;
vertical-align: top;
.fly-skeleton__title {
margin-top: 8px;
}
}
&__avatar {
flex-shrink: 0;
width: 32px;
height: 32px;
margin-right: 15px;
background-color: @textColorGrey;
display: inline-block;
&--circle {
border-radius: 50%;
}
&--sm {
width: 24px;
height: 24px;
line-height: 24px;
}
&--lg {
width: 40px;
height: 40px;
line-height: 40px;
}
}
&__title {
height: 16px;
background-color: @textColorGrey;
& + .fly-skeleton__paragraph {
margin-top: 20px;
}
}
&__paragraph {
li {
width: 100%;
height: 16px;
list-style: none;
background: @textColorGrey;
border-radius: 4px;
& + li {
margin-top: 12px; }}}}@keyframes fly-skeleton__loading {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%; }}Copy the code
Skeleton.jsx
/ * * *@description Skeleton screen *@author * / one by one
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import './skeleton.less';
const Skeleton = ({ ...props }) = > {
const { avatar, title, paragraph, round, active, show, children, className } = props;
/ * *@name The class prefix * /
const prefixCls = 'fly-skeleton';
/ * *@name Whether to show your avatar */
consthasAvatar = !! avatar;/ * *@name Whether to display the title */
consthasTitle = !! title;/ * *@name Whether to display paragraphs */
consthasParagraph = !! paragraph;/ * *@name Size enumeration */
const sizgClsObj = {
large: 'lg'.small: 'sm'.default: null};/** * gets the object attribute value *@param {void} No *@return {render} Display content */
const getTypeOfObject = prop= > {
if (prop && typeof prop === 'object') {
return prop;
}
return {};
};
/** ** head display *@param {void} No *@return {render} Display content */
const avatarContent = () = > {
if (hasAvatar) {
/ * *@name Avatar object data */
let avatarObj = {
active: false.shape: 'circle'.size: 'default'. getTypeOfObject(avatar), };/ * *@name Head the parent container class */
const headCls = classnames(`${prefixCls}__header`);
/ * *@name Face the class * /
const avatarCls = `${prefixCls}__avatar`;
/ * *@name The class size * /
const sizeCls = sizgClsObj[avatarObj.size] && classnames(`${avatarCls}--${sizgClsObj[avatarObj.size]}`);
/ * *@name Shape class * /
const shapeCls = classnames(`${avatarCls}--${avatarObj.shape}`);
/ * *@name Size inline style */
const sizeStyle =
typeof avatarObj.size === 'number'
? {
width: avatarObj.size,
height: avatarObj.size,
lineHeight: `${avatarObj.size}px`}, {};return (
<div className={headCls}>
<span className={classnames(avatarCls, sizeCls.shapeCls.className)} style={{ . sizeStyle}} / >
</div>); }};/** ** *@param {void} No *@return {render} Display content */
const titleContent = () = > {
if (hasTitle) {
/ * *@name Title class * /
const titleCls = classnames(`${prefixCls}__title`);
/ * *@name The headline style * /
const titleStyle = {
width: !hasAvatar ? '38%' : '50%'. getTypeOfObject(title), };return <h3 className={titleCls} style={titleStyle} />; }};/** * paragraph - gets the paragraph width *@param {number} Index The index of the paragraph *@param {object} Data object * for the obj paragraph@return {number} The calculated width */
const getParagraphWidth = (index, obj) = > {
const { width, rows = 2 } = obj;
// =>true: If width is an array, set the corresponding width for each row
if (Array.isArray(width)) {
return width[index];
}
// =>true: If width is not an array, set it to the width of the last line
if (rows - 1 === index) {
return width;
}
return undefined;
};
/** **@param {void} No *@return {render} Display content */
const paragraphContent = () = > {
if (hasParagraph) {
/ * *@name Paragraph class * /
const paragraphCls = classnames(`${prefixCls}__paragraph`);
let paragraphObj = {};
// =>true: there is a title but no avatar, default 3 lines, other 2 lines
if(! hasAvatar && hasTitle) { paragraphObj.rows =3;
} else {
paragraphObj.rows = 2;
}
// => true: There is no title or avatar, and the width of the last paragraph is 61%
if(! hasAvatar || ! hasTitle) { paragraphObj.width ='61%';
}
// =>true: Paragraph If the parameter has a value, the default value is overwrittenparagraphObj = { ... paragraphObj, ... getTypeOfObject(paragraph), };/ * *@name Line array of paragraphs */
const rowList = [...Array(paragraphObj.rows)].map((_, index) = > <li key={index} style={{ width: getParagraphWidth(index.paragraphObj) }} />);
return <ul className={paragraphCls}>{rowList}</ul>; }};/ * *@name The root element class */
const rootCls = classnames(prefixCls, className, {
[`${prefixCls}--round`]: round,
[`${prefixCls}--active`]: active,
});
/ * *@name The content class * /
const contentCls = classnames(`${prefixCls}--content`);
/** * Skeleton display *@param {void} No *@return {render} Display content */
const skeletonContent = () = > {
if (show) {
return (
<div className={rootCls}>
{avatarContent()}
<div className={contentCls}>
{titleContent()}
{paragraphContent()}
</div>
</div>
);
}
return children;
};
return <>{skeletonContent()}</>;
};
Skeleton.propTypes = {
paragraph: PropTypes.oneOfType([PropTypes.bool, PropTypes.Object]), // Whether to display paragraph placeholders
title: PropTypes.bool, // Whether to display the title placeholder map
avatar: PropTypes.oneOfType([PropTypes.bool, PropTypes.Object]), // Whether to display the avatar placeholder
show: PropTypes.bool, // Whether to display skeleton screen. If false is passed, child component contents will be displayed
active: PropTypes.bool, // Whether to enable animation
round: PropTypes.bool, // Whether to display headings and paragraphs in rounded corners
};
Skeleton.defaultProps = {
paragraph: true.title: true.avatar: false.show: true.active: false.round: false};export default Skeleton;
Copy the code
4.2.2 Page Reference
style.less
.fly-skeleton {
&__content {
padding: 15px;
background: #fff;
min-height: 100vh;
padding-bottom: 50px;
}
&__children {
display: flex;
align-items: flex-start;
line-height: 20px;
font-size: 14px;
margin-top: 10px;
&--avatar {
margin-right: 15px;
width: 30px;
height: 30px;
border-radius: 50%;
overflow: hidden;
img {
display: block;
width: 100%;
height: 100%;
}
}
&--title {
margin-top: 8px;
}
&--desc {
margin-top: 20px;
}
}
&__button {
text-align: center;
width: 100px;
padding: 010px; font-size: 14px; line-height: 24px; border-radius: 5px; color: #fff; background-color: #45b7f5; }}Copy the code
skeleton-test.jsx
/ * * *@description Skeleton screen display *@author * / one by one
import React, { useEffect, useState } from 'react';
import { Skeleton, Switch } from 'fly';
import './style.less';
const SkeletonTest = () = > {
const title = 'Skeleton Screen '; / / title
const [skeletonShow, setSkeletonShow] = useState(true);
useEffect(() = > {
// Set the title
document.title = title; } []);// Subcomponent displays the switch control method
const onChangeSwitch = () = >{ setSkeletonShow(! skeletonShow); };return (
<div className='fly-skeleton__content'>
<div className='text-xxxl mb20'>{title}</div>
<div className='mb10'>Basic usage</div>
<Skeleton />
<div className='mb10 mt10'>Display picture</div>
<div className='text-sm text-darkgray mt5 mb10'>Set the avatar attribute to display the avatar.</div>
<Skeleton avatar />
<div className='mb10 mt10'>Animation effects</div>
<div className='text-sm text-darkgray mt5 mb10'>Set the active property to animate.</div>
<Skeleton avatar active />
<div className='mb10 mt10'>Display child component content</div>
<div className='text-sm text-darkgray mt5 mb10'>When the show property is set to false, the child components of Skeleton can be displayed.</div>{/ *<Switch value={! skeletonShow} onChange={onChangeSwitch} />* /}<div onClick={onChangeSwitch} className='fly-skeleton__button'>Control the display</div>
<Skeleton avatar active show={skeletonShow} className='mt10'>
<div className='fly-skeleton__children'>
<div className='fly-skeleton__children--avatar'>
<img src='https://sf3-ttcdn-tos.pstatp.com/img/user-avatar/c6c1a335a3b48adc43e011dd21bfdc60~300x300.image' />
</div>
<div className='fly-skeleton__children--content'>
<div className='fly-skeleton__children--title'>This is a title,</div>
<div className='fly-skeleton__children--desc'>This is paragraph 11111111111111111<br />This is paragraph 222222222</div>
</div>
</div>
</Skeleton>
<div className='mb10 mt10'>Head portrait style</div>
<div className='text-sm text-darkgray mt5 mb10'>Set the size attribute of the Avatar to adjust the avatar size, and set the shape value to adjust the avatar shape.</div>
<Skeleton avatar={{ size: 50.shape: 'square' }} />
<div className='mb10 mt10'>Set the width of the title</div>
<div className='text-sm text-darkgray mt5 mb10'>Set the width property of the title property to adjust the title width. The value can be percentage, specific pixel value, specific value, such as 70%, 150px, 150.</div>
<Skeleton title={{ width: 150}} / >
<div className='mb10 mt10'>Rounded headings and paragraphs</div>
<div className='text-sm text-darkgray mt5 mb10'>Set the round property to round paragraphs and headings.</div>
<Skeleton avatar round />
<div className='mb10 mt10'>Multi-line paragraph</div>
<div className='text-sm text-darkgray mt5 mb10'>Set the value of the rows attribute in the Paragraph property to a specific value to adjust the number of lines in the paragraph.</div>
<Skeleton avatar paragraph={{ rows: 4}} / >
<div className='mb10 mt10'>Paragraph width is configurable</div>
<div className='text-sm text-darkgray mt5 mb10'>Set the width property of the Paragraph property to an array value to adjust the width of each line paragraph.</div>
<Skeleton avatar paragraph={{ rows: 4.width: ['90% ', '90% ', '90% ', '60% ']}} / >
</div>
);
};
export default SkeletonTest;
Copy the code
Five, the summary
Down the whole process, we have realized a basic skeleton screen components, skeleton screen also have a systematic understanding, if they try to achieve will have ideas how to do.
However, this is only a basic implementation, not perfect, for example, does not support the use of avatars, buttons and other elements alone. After completion, I went to antD’s official website to have a look at its source code, and found that ANTD’s functions are more perfect, worthy of being a dachang project. Inspired by ANTD, I also improved my code. So, we can in the daily free time, look at the source code of some big factories, their function is more powerful, consider the problem is more comprehensive, realize the idea is also more excellent.
Was the beginning difficult? Sometimes it’s hard, but with this difficult and good start, the rest is easy and smooth, and component development will take care of itself. So, come on.
Thanks again to all the open source projects that allow learners like me to access technological advances.