反馈类

骨架屏

在需要等待加载内容的位置提供的占位组件。

概述

  • Avatar:占位头像,默认为圆形,默认尺寸:Avatar medium: width: 48px,height: 48px。支持 size、shape 属性,取值见 API 参考。
  • Image:占位图像,默认尺寸:width: 100%,height: 100%。
  • Title:占位标题,默认尺寸:width: 100%, height: 24px。
  • Paragraph:占位内容部分,默认尺寸:width: 100%,height: 16px,margin-bottom: 10px。
  • Button:占位按钮,默认尺寸:width: 115px,height: 32px。

注意:默认样式均可通过 className 或 style 进行自定义。

代码演示

如何引入

import { Skeleton } from '@fibrous/semi-ui-react';

基本使用

显示加载内容




代码
import { useState } from 'react';
import { Skeleton, Switch, Avatar, Button } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const [loading, setLoading] = useState(true);
    const showContent = () => {
        setLoading(!loading);
    };
    return (
        <>
            <span style={{ display: 'flex', alignItems: 'center' }}>
                <Switch aria-labelledby="skeleton-basic-toggle" onChange={() => showContent()} />
                <span id="skeleton-basic-toggle" style={{ marginLeft: '10px' }}>
                    显示加载内容
                </span>
            </span>
            <br />
            <Skeleton placeholder={<Skeleton.Avatar />} loading={loading}>
                <Avatar color="blue" style={{ marginBottom: 10 }}>
                    U
                </Avatar>
            </Skeleton>
            <br />
            <Skeleton style={{ width: 200, height: 150 }} placeholder={<Skeleton.Image />} loading={loading}>
                <img src="/assets/placeholder/avatar-a.svg" height="150" alt="avatar" />
            </Skeleton>
            <br />
            <Skeleton style={{ width: 80 }} placeholder={<Skeleton.Title style={{ marginBottom: 10 }} />} loading={loading}>
                <h4 style={{ marginBottom: 0 }}>Semi UI</h4>
            </Skeleton>
            <Skeleton style={{ width: 240 }} placeholder={<Skeleton.Paragraph rows={2} />} loading={loading}>
                <p style={{ width: 240 }}>精心打磨每一个组件的用户体验,从用户的角度考虑每个组件的使用场景。</p>
            </Skeleton>
            <br />
            <Skeleton placeholder={<Skeleton.Button />} loading={loading}>
                <Button>Button</Button>
            </Skeleton>
        </>
    );
}

组合使用

图片和标题。

代码
import { Skeleton } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const placeholder = (
        <div>
            <Skeleton.Image style={{ width: 200, height: 150 }} />
            <Skeleton.Title style={{ width: 120, marginTop: 10 }} />
        </div>
    );

    return (
        <Skeleton placeholder={placeholder} loading>
            <img src="/assets/placeholder/avatar-a.svg" height="150" alt="avatar" />
            <h4>Semi UI</h4>
        </Skeleton>
    );
}

统计数字。

代码
import { Skeleton, Descriptions } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const placeholder = (
        <div>
            <Skeleton.Paragraph rows={1} style={{ width: 80, marginBottom: 10 }} />
            <Skeleton.Title style={{ width: 120 }} />
        </div>
    );

    const data = [{ key: '实际用户数量', value: '1,480,000' }];

    return (
        <Skeleton placeholder={placeholder} loading>
            <Descriptions data={data} row />
        </Skeleton>
    );
}

头像和标题。

代码
import { Skeleton, Avatar } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const placeholder = (
        <div style={{ display: 'flex', alignItems: 'center' }}>
            <Skeleton.Avatar style={{ marginRight: 12 }} />
            <Skeleton.Title style={{ width: 120 }} />
        </div>
    );

    return (
        <Skeleton placeholder={placeholder} loading>
            <Avatar color="blue" style={{ marginRight: 12 }}>
                UI
            </Avatar>
            <span>Semi UI</span>
        </Skeleton>
    );
}

居中段落和按钮。

代码
import type { CSSProperties } from 'react';
import { Skeleton, Button } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const style: CSSProperties = {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        width: '300px',
        marginBottom: '10px',
    };

    const placeholder = (
        <div style={style}>
            <Skeleton.Paragraph style={style} rows={3} />
            <Skeleton.Button />
        </div>
    );

    return (
        <Skeleton placeholder={placeholder} loading style={{ textAlign: 'center' }}>
            <div style={{ textAlign: 'center' }}>
                <p>Hi, Bytedance dance dance.</p>
                <p>Hi, Bytedance dance dance.</p>
                <Button>Button</Button>
            </div>
        </Skeleton>
    );
}

头像、标题和段落。

代码
import { Skeleton, Avatar } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const style = {
        display: 'flex',
        alignItems: 'flex-start',
    };

    const placeholder = (
        <div style={style}>
            <Skeleton.Avatar style={{ marginRight: 12 }} />
            <div>
                <Skeleton.Title style={{ width: 120, marginBottom: 12, marginTop: 12 }} />
                <Skeleton.Paragraph style={{ width: 240 }} rows={3} />
            </div>
        </div>
    );

    return (
        <Skeleton placeholder={placeholder} loading>
            <div style={style}>
                <Avatar color="blue" style={{ marginRight: 12 }}>
                    UI
                </Avatar>
                <div>
                    <h3>Semi UI</h3>
                    <p>Hi, Bytedance dance dance.</p>
                    <p>Hi, Bytedance dance dance.</p>
                    <p>Hi, Bytedance dance dance.</p>
                </div>
            </div>
        </Skeleton>
    );
}

表格。

代码
import type { ReactNode } from 'react';
import { Skeleton, Table, type ColumnProps } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const data = {
        columns: [
            {
                title: 'Name',
                dataIndex: 'name',
            },
            {
                title: 'Age',
                dataIndex: 'age',
            },
            {
                title: 'Address',
                dataIndex: 'address',
            },
        ],
        content: [
            {
                key: '1',
                name: 'John Brown',
                age: 32,
                address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
            },
            {
                key: '2',
                name: 'Jim Green',
                age: 42,
                address: 'London No. 1 Lake Park',
            },
            {
                key: '3',
                name: 'Joe Black',
                age: 32,
                address: 'Sidney No. 1 Lake Park',
            },
            {
                key: '4',
                name: 'Disabled User',
                age: 99,
                address: 'Sidney No. 1 Lake Park',
            },
        ],
    };

    const skData = {
        columns: [1, 2, 3].map((key) => {
            const item: ColumnProps = {};
            item.title = <Skeleton.Title style={{ width: '0' }} />;
            item.dataIndex = `${key}`;
            return item;
        }),
        dataSource: [1, 2, 3, 4].map((key) => {
            const item: Record<string, ReactNode> = {};
            item.key = key;
            [1, 2, 3].forEach((i) => {
                const width = 50 * i;
                item[i] = <Skeleton.Paragraph style={{ width: width }} rows={1} />;
            });
            return item;
        }),
    };

    const placeholder = (
        <div style={{ position: 'relative' }}>
            <Table style={{ backgroundColor: 'var(--semi-color-bg-1)' }} columns={skData.columns} dataSource={skData.dataSource} pagination={false} />
            <div style={{ position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }} />
        </div>
    );

    return (
        <Skeleton placeholder={placeholder} loading>
            <div>
                <Table columns={data.columns} dataSource={data.content} pagination={false} />
            </div>
        </Skeleton>
    );
}

加载动画

通过设置 active 属性可以展示动画效果。

代码
import { Skeleton, Avatar } from '@fibrous/semi-ui-react';

export default function SkeletonDemo() {
    const style = {
        display: 'flex',
        alignItems: 'flex-start',
    };

    const placeholder = (
        <div style={style}>
            <Skeleton.Avatar style={{ marginRight: 12 }} />
            <div>
                <Skeleton.Title style={{ width: 120, marginBottom: 12, marginTop: 12 }} />
                <Skeleton.Paragraph style={{ width: 240 }} rows={3} />
            </div>
        </div>
    );

    return (
        <Skeleton placeholder={placeholder} loading active>
            <div style={style}>
                <Avatar color="blue" style={{ marginRight: 12 }}>
                    UI
                </Avatar>
                <div>
                    <h3>Semi UI</h3>
                    <p>Hi, Bytedance dance dance.</p>
                    <p>Hi, Bytedance dance dance.</p>
                    <p>Hi, Bytedance dance dance.</p>
                </div>
            </div>
        </Skeleton>
    );
}

API 参考

Skeleton

Props

属性说明类型默认值
active是否展示动画效果。boolean-
children加载结束后显示的内容,loading 为 false 时原样渲染,不包外层元素。ReactNode-
className占位根元素的类名,loading 为 false 时不渲染占位根元素,因此不生效。string-
loading为 true 时显示 placeholder,反之显示子内容。booleantrue
placeholder加载等待时的占位元素。ReactNode-
ref占位根 div 的引用,loading 为 false 时不挂载。TypeReact.Ref<HTMLDivElement>-
style占位根元素的样式,loading 为 false 时不生效。CSSProperties-

其他原生属性(如 id、aria-* 与事件)落在占位根 div 上。loading 为 false 时组件只渲染子内容,不再有根元素,这些属性随占位一起丢弃。根元素的 aria-busy="true" 不能被关闭。

Skeleton.Avatar

Props

属性说明类型默认值
className占位头像的类名。string-
prefixCls类名前缀,默认为 semi-skeleton。string-
ref占位头像的引用。TypeReact.Ref<HTMLDivElement>-
shape占位头像的形状,可选 circle 或 square,未设置时为 circle。'circle' | 'square'-
size占位头像的大小,可选 extra-extra-small、extra-small、small、medium、large 或 extra-large,未设置时为 medium。Type'extra-extra-small' | 'extra-small' | 'small' | 'medium' | 'large' | 'extra-large'-
style占位头像的样式。CSSProperties-
type替换占位块自带的类型,同时决定它的类名;不为 avatar 时 size、shape 不生效。string-

其他原生属性(如 id、aria-* 与事件)落在占位块上,aria-hidden="true" 可被传入的同名属性覆盖。

Skeleton.Image

Props

属性说明类型默认值
className占位图像的类名。string-
prefixCls类名前缀,默认为 semi-skeleton。string-
ref占位图像的引用。TypeReact.Ref<HTMLDivElement>-
style占位图像的样式。CSSProperties-
type替换占位块自带的类型,同时决定它的类名。string-

Skeleton.Title

Props

属性说明类型默认值
className占位标题的类名。string-
prefixCls类名前缀,默认为 semi-skeleton。string-
ref占位标题的引用。TypeReact.Ref<HTMLDivElement>-
style占位标题的样式。CSSProperties-
type替换占位块自带的类型,同时决定它的类名。string-

Skeleton.Button

Props

属性说明类型默认值
className占位按钮的类名。string-
prefixCls类名前缀,默认为 semi-skeleton。string-
ref占位按钮的引用。TypeReact.Ref<HTMLDivElement>-
style占位按钮的样式。CSSProperties-
type替换占位块自带的类型,同时决定它的类名。string-

Skeleton.Image、Skeleton.Title、Skeleton.Button 没有 size、shape,其他原生属性同样落在占位块上。

Skeleton.Paragraph

Props

属性说明类型默认值
prefixCls类名前缀。string"semi-skeleton"
ref占位段落根元素 ul 的引用。TypeReact.Ref<HTMLUListElement>-
rows占位段落的行数,每行渲染为一个 li。number4

className、style 与原生属性落在 ul 根元素上。

Accessibility

  • 加载时 Skeleton 的外层带有 aria-busy="true",占位块与段落占位带有 aria-hidden="true",读屏只知道内容正在加载;使用方传入的同名属性优先于占位块上的 aria-hidden,aria-busy 不能被关闭。
  • 加载结束后直接渲染子内容,不再有 Skeleton 的外层元素。

文案规范

  • 不变的固定内容直接展示固定内容,可变的内容使用骨架屏展示

设计变量

下表是组件内部的 Sass 变量,不能覆写。定制样式请覆写 --semi-* CSS 变量(见 Design Token 页),或写不分层的 CSS 规则。
Sass 变量值用法
$animation_duration-skeleton-highlight1400ms骨架屏高亮动画时长
$animation_function-skeleton-highlightease
$color-skeleton_default-bg-defaultvar(--semi-color-fill-0)骨架屏背景色 - 默认
$color-skeleton_loading-gradient-bg-activevar(--semi-color-fill-1)骨架屏背景色 - 动画开启后
$width-skeleton_avatar_extra_extra_small20px头像骨架屏尺寸 - 极小
$width-skeleton_avatar_extra_small24px头像骨架屏尺寸 - 超小
$width-skeleton_avatar_small32px头像骨架屏尺寸 - 小
$width-skeleton_avatar_medium48px头像骨架屏尺寸 - 中
$width-skeleton_avatar_large72px头像骨架屏尺寸 - 大
$width-skeleton_avatar_extra_large128px头像骨架屏尺寸 - 超大
$width-skeleton_button115px按钮骨架屏宽度
$height-skeleton_li16px段落骨架屏高度
$height-skeleton_title$height-control-small标题骨架屏高度
$height-skeleton_button$height-control-default按钮骨架屏高度
$spacing-skeleton_li-marginBottom10px按钮骨架屏高度
$radius-skeleton_itemvar(--semi-border-radius-small)骨架屏圆角
$radius-skeleton_livar(--semi-border-radius-small)骨架屏圆角