스켈레톤
스켈레톤 (Skeleton)
리소스가 로딩되는 동안 자리 표시자(placeholder) 역할을 해주는 컴포넌트입니다. 데이터가 오기 전에 레이아웃을 미리 잡아 화면 흔들림을 줄여줘요.
출처: 문서
본문
언제 사용하나요 (When To Use)
- 리소스를 불러오는 데 오래 걸릴 때
- List나 Card처럼 컴포넌트가 많은 정보를 담고 있을 때
- 데이터를 처음 로딩할 때만 동작합니다
- 어떤 상황에서는 Spin으로 대체할 수도 있지만, 더 나은 사용자 경험을 제공할 수 있어요
예제 (Examples)
기본 (Basic)
가장 간단한 Skeleton 사용법입니다.
import React from 'react';
import { Skeleton } from 'antd';
const App: React.FC = () => <Skeleton />;
export default App;
복합 조합 (Complex combination)
아바타와 여러 문단이 결합된 복합 형태입니다.
import React from 'react';
import { Skeleton } from 'antd';
const App: React.FC = () => <Skeleton avatar paragraph={{ rows: 4 }} />;
export default App;
활성 애니메이션 (Active Animation)
활성(active) 애니메이션을 표시합니다.
import React from 'react';
import { Skeleton } from 'antd';
const App: React.FC = () => <Skeleton active />;
export default App;
Button/Avatar/Input/Image/Node
Skeleton Button, Avatar, Input, Image, Node입니다.
import React, { useState } from 'react';
import { DotChartOutlined } from '@ant-design/icons';
import type { RadioChangeEvent } from 'antd';
import { Divider, Flex, Form, Radio, Skeleton, Space, Switch } from 'antd';
type SizeType = 'large' | 'medium' | 'small';
type ButtonShapeType = 'circle' | 'square' | 'round' | 'default';
type AvatarShapeType = 'circle' | 'square';
const App: React.FC = () => {
const [active, setActive] = useState(false);
const [block, setBlock] = useState(false);
const [size, setSize] = useState<SizeType>('medium');
const [buttonShape, setButtonShape] = useState<ButtonShapeType>('default');
const [avatarShape, setAvatarShape] = useState<AvatarShapeType>('circle');
const handleActiveChange = (checked: boolean) => {
setActive(checked);
};
const handleBlockChange = (checked: boolean) => {
setBlock(checked);
};
const handleSizeChange = (e: RadioChangeEvent) => {
setSize(e.target.value);
};
const handleShapeButton = (e: RadioChangeEvent) => {
setButtonShape(e.target.value);
};
const handleAvatarShape = (e: RadioChangeEvent) => {
setAvatarShape(e.target.value);
};
return (
<Flex gap="medium" vertical>
<Space>
<Skeleton.Button active={active} size={size} shape={buttonShape} block={block} />
<Skeleton.Avatar active={active} size={size} shape={avatarShape} />
<Skeleton.Input active={active} size={size} />
</Space>
<Skeleton.Button active={active} size={size} shape={buttonShape} block={block} />
<Skeleton.Input active={active} size={size} block={block} />
<Space>
<Skeleton.Image active={active} />
<Skeleton.Node active={active} style={{ width: 160 }} />
<Skeleton.Node active={active}>
<DotChartOutlined style={{ fontSize: 40, color: '#bfbfbf' }} />
</Skeleton.Node>
</Space>
<Divider />
<Form layout="inline" style={{ margin: '16px 0' }}>
<Space size={16} wrap>
<Form.Item label="Active">
<Switch checked={active} onChange={handleActiveChange} />
</Form.Item>
<Form.Item label="Button and Input Block">
<Switch checked={block} onChange={handleBlockChange} />
</Form.Item>
<Form.Item label="Size">
<Radio.Group value={size} onChange={handleSizeChange}>
<Radio.Button value="large">Large</Radio.Button>
<Radio.Button value="medium">Medium</Radio.Button>
<Radio.Button value="small">Small</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Button Shape">
<Radio.Group value={buttonShape} onChange={handleShapeButton}>
<Radio.Button value="default">Default</Radio.Button>
<Radio.Button value="square">Square</Radio.Button>
<Radio.Button value="round">Round</Radio.Button>
<Radio.Button value="circle">Circle</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="Avatar Shape">
<Radio.Group value={avatarShape} onChange={handleAvatarShape}>
<Radio.Button value="square">Square</Radio.Button>
<Radio.Button value="circle">Circle</Radio.Button>
</Radio.Group>
</Form.Item>
</Space>
</Form>
</Flex>
);
};
export default App;
하위 컴포넌트 포함 (Contains sub component)
Skeleton이 하위 컴포넌트를 포함하고 있습니다.
import React, { useState } from 'react';
import { Button, Skeleton, Space } from 'antd';
const App: React.FC = () => {
const [loading, setLoading] = useState<boolean>(false);
const showSkeleton = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
}, 3000);
};
return (
<Space vertical style={{ width: '100%' }} size={16}>
<Skeleton loading={loading}>
<h4 style={{ marginBottom: 16 }}>Ant Design, a design language</h4>
<p>
We supply a series of design principles, practical patterns and high quality design
resources (Sketch and Axure), to help people create their product prototypes beautifully
and efficiently.
</p>
</Skeleton>
<Button onClick={showSkeleton} disabled={loading}>
Show Skeleton
</Button>
</Space>
);
};
export default App;
리스트 (List)
리스트 컴포넌트에서 Skeleton을 사용합니다.
import React, { useState } from 'react';
import type Icon from '@ant-design/icons';
import { LikeOutlined, MessageOutlined, StarOutlined } from '@ant-design/icons';
import { Avatar, List, Skeleton, Switch } from 'antd';
interface IconTextProps {
icon: typeof Icon;
text: React.ReactNode;
}
const listData = Array.from({ length: 3 }).map((_, i) => ({
href: 'https://ant.design',
title: `ant design part ${i + 1}`,
avatar: `https://api.dicebear.com/10.x/lorelei/svg?seed=${i}`,
description:
'Ant Design, a design language for background applications, is refined by Ant UED Team.',
content:
'We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.',
}));
const IconText: React.FC<IconTextProps> = ({ icon, text }) => (
<>
{React.createElement(icon, { style: { marginInlineEnd: 8 } })}
{text}
</>
);
const App: React.FC = () => {
const [loading, setLoading] = useState(true);
const onChange = (checked: boolean) => {
setLoading(!checked);
};
return (
<>
<Switch checked={!loading} onChange={onChange} style={{ marginBottom: 16 }} />
<List
itemLayout="vertical"
size="large"
dataSource={listData}
renderItem={(item) => (
<List.Item
key={item.title}
actions={
!loading
? [
<IconText icon={StarOutlined} text="156" key="list-vertical-star-o" />,
<IconText icon={LikeOutlined} text="156" key="list-vertical-like-o" />,
<IconText icon={MessageOutlined} text="2" key="list-vertical-message" />,
]
: undefined
}
extra={
!loading && (
<img
draggable={false}
width={272}
alt="logo"
src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png"
/>
)
}
>
<Skeleton loading={loading} active avatar>
<List.Item.Meta
avatar={<Avatar src={item.avatar} />}
title={<a href={item.href}>{item.title}</a>}
description={item.description}
/>
{item.content}
</Skeleton>
</List.Item>
)}
/>
</>
);
};
export default App;
커스텀 시맨틱 DOM 스타일링 (Custom semantic dom styling)
classNames와 styles에 객체 또는 함수를 넘겨서 Skeleton의 시맨틱 DOM 스타일을 커스터마이즈할 수 있습니다.
import React from 'react';
import { Flex, Skeleton } from 'antd';
import type { GetProp, SkeletonProps } from 'antd';
import { createStaticStyles } from 'antd-style';
const classnames = createStaticStyles(({ css }) => ({
root: css`
border-radius: 10px;
padding: 12px;
`,
header: css`
margin-bottom: 12px;
`,
}));
const paragraphStyles = createStaticStyles(({ css }) => ({
paragraph: css`
& > li {
background-color: rgba(229, 243, 254, 0.5);
}
`,
}));
const styles: SkeletonProps['styles'] = {
avatar: {
border: '1px solid #aaa',
},
title: {
border: '1px solid #aaa',
},
};
const stylesFn: SkeletonProps['styles'] = (info): GetProp<SkeletonProps, 'styles', 'Return'> => {
if (info.props.active) {
return {
root: {
border: '1px solid rgba(229, 243, 254, 0.3)',
},
title: {
backgroundColor: 'rgba(229, 243, 254, 0.5)',
height: 20,
borderRadius: 20,
},
};
}
return {};
};
const App: React.FC = () => {
return (
<Flex gap="medium">
<Skeleton classNames={classnames} styles={styles} avatar paragraph={false} />
<Skeleton
classNames={{ ...classnames, paragraph: paragraphStyles.paragraph }}
styles={stylesFn}
active
/>
</Flex>
);
};
export default App;
API
Common props ref:Common props
공통 API (Common API)
Skeleton
| Property | Description | Type | Default | Version | Global Config |
|---|---|---|---|---|---|
| active | Show animation effect | boolean | false | × | |
| avatar | Show avatar placeholder | boolean | SkeletonAvatar | false | × | |
| loading | Display the skeleton when true | boolean | - | × | |
| paragraph | Show paragraph placeholder | boolean | SkeletonParagraphProps | true | × | |
| round | Show paragraph and title radius when true | boolean | false | × | |
| title | Show title placeholder | boolean | SkeletonTitleProps | true | × |
SkeletonTitleProps
| Property | Description | Type | Default |
|---|---|---|---|
| width | Set the width of title | number | string | - |
SkeletonParagraphProps
| Property | Description | Type | Default |
|---|---|---|---|
| rows | Set the row count of paragraph | number | - |
| width | Set the width of paragraph. When width is an Array, it can set the width of each row. Otherwise only set the last row width | number | string | Array<number | string> | - |
Skeleton.Avatar
| Property | Description | Type | Default |
|---|---|---|---|
| active | Show animation effect, only valid when used avatar independently | boolean | false |
| shape | Set the shape of avatar | circle | square |
circle |
| size | Set the size of avatar | number | large | medium | small |
medium |
Skeleton.Button
| Property | Description | Type | Default | Version |
|---|---|---|---|---|
| active | Show animation effect | boolean | false | |
| block | Option to fit button width to its parent width | boolean | false | 4.17.0 |
| shape | Set the shape of button | circle | round | square | default |
- | |
| size | Set the size of button | large | medium | small |
medium |
Skeleton.Input
| Property | Description | Type | Default |
|---|---|---|---|
| active | Show animation effect | boolean | false |
| size | Set the size of input | large | medium | small |
medium |
시맨틱 DOM (Semantic DOM)
Skeleton
https://ant.design/components/skeleton/semantic.md
Skeleton.Element
https://ant.design/components/skeleton/semantic_element.md
디자인 토큰 (Design Token)
컴포넌트 토큰 (Component Token - Skeleton)
| Token Name | Description | Type | Default Value |
|---|---|---|---|
| blockRadius | Border radius of skeleton | number | 4 |
| gradientFromColor | Start color of gradient | string | rgba(0,0,0,0.06) |
| gradientToColor | End color of gradient | string | rgba(0,0,0,0.15) |
| paragraphLiHeight | Line height of paragraph skeleton | number | 16 |
| paragraphMarginTop | Margin top of paragraph skeleton | number | 28 |
| titleHeight | Height of title skeleton | string | number | 16 |
글로벌 토큰 (Global Token)
| Token Name | Description | Type | Default Value |
|---|---|---|---|
| borderRadiusSM | SM size border radius, used in small size components, such as Button, Input, Select and other input components in small size | number | |
| controlHeight | The height of the basic controls such as buttons and input boxes in Ant Design | number | |
| controlHeightLG | LG component height | number | |
| controlHeightSM | SM component height | number | |
| controlHeightXS | XS component height | number | |
| marginSM | Control the margin of an element, with a medium-small size. | number | |
| padding | Control the padding of the element. | number |