스켈레톤
스켈레톤 (Skeleton)
데이터가 로드되기 전에 콘텐츠의 자리표시자 미리보기를 표시해서 로드 시간의 답답함을 줄여줘요.
출처: 문서
본문
데이터가 로드되기 전에 콘텐츠의 자리표시자 미리보기를 표시해서 로드 시간의 답답함을 줄여요.
컴포넌트의 데이터가 즉시 준비되지 않을 수 있어요. 스켈레톤을 사용하면 페이지의 인지된 반응성을 개선할 수 있어요. 무언가가 즉시 일어나고 있는 것처럼 느껴지면서, 정보가 점차 화면에 표시되게 되죠 (Avoid The Spinner 참고).
사용법 (Usage)
이 컴포넌트는 컴포넌트 안에서 직접 사용하도록 설계됐어요. 예를 들어:
{
item ? (
<img
style={{
width: 210,
height: 118,
}}
alt={item.title}
src={item.src}
/>
) : (
<Skeleton variant="rectangular" width={210} height={118} />
);
}
변형 (Variants)
이 컴포넌트는 4가지 모양 변형을 지원해요:
text(기본값): 한 줄의 텍스트를 나타내요 (글꼴 크기로 높이를 조절할 수 있어요).circular,rectangular,rounded: 서로 다른 border radius를 제공해서 크기를 자유롭게 제어할 수 있게 해 줘요.
import Skeleton from '@mui/material/Skeleton';
import Stack from '@mui/material/Stack';
export default function Variants() {
return (
<Stack spacing={1}>
{/* For variant="text", adjust the height via font-size */}
<Skeleton variant="text" sx={{ fontSize: '1rem' }} />
{/* For other variants, adjust the size with `width` and `height` */}
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={210} height={60} />
<Skeleton variant="rounded" width={210} height={60} />
</Stack>
);
}
애니메이션 (Animations)
기본적으로 스켈레톤은 펄스(pulse) 효과를 내지만, 애니메이션을 웨이브(wave)로 바꾸거나 완전히 끌 수 있어요.
import Box from '@mui/material/Box';
import Skeleton from '@mui/material/Skeleton';
export default function Animations() {
return (
<Box sx={{ width: 300 }}>
<Skeleton />
<Skeleton animation="wave" />
<Skeleton animation={false} />
</Box>
);
}
펄스 예제 (Pulsate example)
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Skeleton from '@mui/material/Skeleton';
const data = [
{
src: 'https://i.ytimg.com/vi/pLqipJNItIo/hqdefault.jpg?sqp=-oaymwEYCNIBEHZIVfKriqkDCwgBFQAAiEIYAXAB&rs=AOn4CLBkklsyaw9FxDmMKapyBYCn9tbPNQ',
title: 'Don Diablo @ Tomorrowland Main Stage 2019 | Official…',
channel: 'Don Diablo',
views: '396k views',
createdAt: 'a week ago',
},
{
src: 'https://i.ytimg.com/vi/_Uu12zY01ts/hqdefault.jpg?sqp=-oaymwEZCPYBEIoBSFXyq4qpAwsIARUAAIhCGAFwAQ==&rs=AOn4CLCpX6Jan2rxrCAZxJYDXppTP4MoQA',
title: 'Queen - Greatest Hits',
channel: 'Queen Official',
views: '40M views',
createdAt: '3 years ago',
},
{
src: 'https://i.ytimg.com/vi/kkLk2XWMBf8/hqdefault.jpg?sqp=-oaymwEYCNIBEHZIVfKriqkDCwgBFQAAiEIYAXAB&rs=AOn4CLB4GZTFu1Ju2EPPPXnhMZtFVvYBaw',
title: 'Calvin Harris, Sam Smith - Promises (Official Video)',
channel: 'Calvin Harris',
views: '130M views',
createdAt: '10 months ago',
},
];
interface MediaProps {
loading?: boolean;
}
function Media(props: MediaProps) {
const { loading = false } = props;
return (
<Grid container wrap="nowrap">
{(loading ? Array.from(new Array(3)) : data).map((item, index) => (
<Box key={index} sx={{ width: 210, marginRight: 0.5, my: 5 }}>
{item ? (
<img
style={{ width: 210, height: 118 }}
alt={item.title}
src={item.src}
/>
) : (
<Skeleton variant="rectangular" width={210} height={118} />
)}
{item ? (
<Box sx={{ pr: 2 }}>
<Typography gutterBottom variant="body2">
{item.title}
</Typography>
<Typography
variant="caption"
sx={{ display: 'block', color: 'text.secondary' }}
>
{item.channel}
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{`${item.views} • ${item.createdAt}`}
</Typography>
</Box>
) : (
<Box sx={{ pt: 0.5 }}>
<Skeleton />
<Skeleton width="60%" />
</Box>
)}
</Box>
))}
</Grid>
);
}
export default function YouTube() {
return (
<Box sx={{ overflow: 'hidden' }}>
<Media loading />
<Media />
</Box>
);
}
웨이브 예제 (Wave example)
import * as React from 'react';
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import CardContent from '@mui/material/CardContent';
import CardMedia from '@mui/material/CardMedia';
import Avatar from '@mui/material/Avatar';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import Skeleton from '@mui/material/Skeleton';
interface MediaProps {
loading?: boolean;
}
function Media(props: MediaProps) {
const { loading = false } = props;
return (
<Card sx={{ maxWidth: 345, m: 2 }}>
<CardHeader
avatar={
loading ? (
<Skeleton animation="wave" variant="circular" width={40} height={40} />
) : (
<Avatar
alt="Ted talk"
src="https://pbs.twimg.com/profile_images/877631054525472768/Xp5FAPD5_reasonably_small.jpg"
/>
)
}
action={
loading ? null : (
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
)
}
title={
loading ? (
<Skeleton
animation="wave"
height={10}
width="80%"
style={{ marginBottom: 6 }}
/>
) : (
'Ted'
)
}
subheader={
loading ? (
<Skeleton animation="wave" height={10} width="40%" />
) : (
'5 hours ago'
)
}
/>
{loading ? (
<Skeleton sx={{ height: 190 }} animation="wave" variant="rectangular" />
) : (
<CardMedia
component="img"
height="140"
image="https://pi.tedcdn.com/r/talkstar-photos.s3.amazonaws.com/uploads/72bda89f-9bbf-4685-910a-2f151c4f3a8a/NicolaSturgeon_2019T-embed.jpg?w=512"
alt="Nicola Sturgeon on a TED talk stage"
/>
)}
<CardContent>
{loading ? (
<React.Fragment>
<Skeleton animation="wave" height={10} style={{ marginBottom: 6 }} />
<Skeleton animation="wave" height={10} width="80%" />
</React.Fragment>
) : (
<Typography variant="body2" component="p" sx={{ color: 'text.secondary' }}>
{
"Why First Minister of Scotland Nicola Sturgeon thinks GDP is the wrong measure of a country's success:"
}
</Typography>
)}
</CardContent>
</Card>
);
}
export default function Facebook() {
return (
<div>
<Media loading />
<Media />
</div>
);
}
크기 유추 (Inferring dimensions)
width와 height props를 받는 것 외에도, 컴포넌트가 크기를 유추할 수 있어요.
높이가 em 단위로 설정되기 때문에 타이포그래피에 특히 잘 작동해요.
<Typography variant="h1">{loading ? <Skeleton /> : 'h1'}</Typography>
import Typography, { TypographyProps } from '@mui/material/Typography';
import Skeleton from '@mui/material/Skeleton';
import Grid from '@mui/material/Grid';
const variants = [
'h1',
'h3',
'body1',
'caption',
] as readonly TypographyProps['variant'][];
function TypographyDemo(props: { loading?: boolean }) {
const { loading = false } = props;
return (
<div>
{variants.map((variant) => (
<Typography component="div" key={variant} variant={variant}>
{loading ? <Skeleton /> : variant}
</Typography>
))}
</div>
);
}
export default function SkeletonTypography() {
return (
<Grid container spacing={8}>
<Grid size="grow">
<TypographyDemo loading />
</Grid>
<Grid size="grow">
<TypographyDemo />
</Grid>
</Grid>
);
}
타이포그래피에서 크기를 유추할 때는 variant="text"를 사용하고, 타이포그래피 기반 크기를 유지하면서 다른 모양이 필요하다면 sx prop으로 border radius를 커스터마이즈하세요.
하지만 다른 컴포넌트의 경우엔 너비와 높이를 반복하고 싶지 않을 수 있어요. 이런 경우 children을 전달하면 그것들로부터 너비와 높이를 유추해요.
loading ? (
<Skeleton variant="circular">
<Avatar />
</Skeleton>
) : (
<Avatar src={data.avatar} />
);
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Avatar from '@mui/material/Avatar';
import Grid from '@mui/material/Grid';
import Skeleton from '@mui/material/Skeleton';
const Image = styled('img')({
width: '100%',
});
function SkeletonChildrenDemo(props: { loading?: boolean }) {
const { loading = false } = props;
return (
<div>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ margin: 1 }}>
{loading ? (
<Skeleton variant="circular">
<Avatar />
</Skeleton>
) : (
<Avatar src="https://pbs.twimg.com/profile_images/877631054525472768/Xp5FAPD5_reasonably_small.jpg" />
)}
</Box>
<Box sx={{ width: '100%' }}>
{loading ? (
<Skeleton width="100%">
<Typography>.</Typography>
</Skeleton>
) : (
<Typography>Ted</Typography>
)}
</Box>
</Box>
{loading ? (
<Skeleton variant="rectangular" width="100%">
<div style={{ paddingTop: '57%' }} />
</Skeleton>
) : (
<Image
src="https://pi.tedcdn.com/r/talkstar-photos.s3.amazonaws.com/uploads/72bda89f-9bbf-4685-910a-2f151c4f3a8a/NicolaSturgeon_2019T-embed.jpg?w=512"
alt=""
/>
)}
</div>
);
}
export default function SkeletonChildren() {
return (
<Grid container spacing={8}>
<Grid size="grow">
<SkeletonChildrenDemo loading />
</Grid>
<Grid size="grow">
<SkeletonChildrenDemo />
</Grid>
</Grid>
);
}
색상 (Color)
컴포넌트의 색상은 background-color CSS 속성을 변경해 커스터마이즈할 수 있어요.
검은 배경 위에서는 특히 유용합니다 (그렇지 않으면 스켈레톤이 보이지 않게 되니까요).
import Skeleton from '@mui/material/Skeleton';
import Box from '@mui/material/Box';
export default function SkeletonColor() {
return (
<Box
sx={{
bgcolor: '#121212',
p: 8,
width: '100%',
display: 'flex',
justifyContent: 'center',
}}
>
<Skeleton
sx={{ bgcolor: 'grey.900' }}
variant="rectangular"
width={210}
height={118}
/>
</Box>
);
}
접근성 (Accessibility)
스켈레톤 화면은 전통적인 스피너 방식의 대안을 제공해요. 추상적인 위젯을 보여주는 대신, 스켈레톤 화면은 앞으로 올 내용에 대한 기대감을 만들고 인지 부하를 줄여줘요.
스켈레톤의 배경색은 양호한 조건(좋은 주변광, 좋은 화면, 시각 장애 없음)에서 보이도록 가장 낮은 휘도(luminance)를 사용해요.
ARIA
없음.
키보드 (Keyboard)
스켈레톤은 포커스 가능하지 않아요.
Skeleton API
데모 (Demos)
이 React 컴포넌트 사용에 대한 예제와 세부 사항은 컴포넌트 데모 페이지를 확인하세요:
Import
import Skeleton from '@mui/material/Skeleton';
// or
import { Skeleton } from '@mui/material';
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
| animation | 'pulse' | 'wave' | false |
'pulse' |
No | |
| children | node |
- | No | |
| classes | object |
- | No | Override or extend the styles applied to the component. |
| component | elementType |
- | No | |
| height | number | string |
- | No | |
| sx | Array<func | object | bool> | func | object |
- | No | The system prop that allows defining system overrides as well as additional CSS styles. |
| variant | 'circular' | 'rectangular' | 'rounded' | 'text' | string |
'text' |
No | |
| width | number | string |
- | No |
Note: The
refis forwarded to the root element (HTMLSpanElement).
Any other props supplied will be provided to the root element (native element).
Theme default props
MuiSkeleton을 사용해 테마로 이 컴포넌트의 기본 props를 변경할 수 있어요.
CSS
Rule name
| Global class | Rule name | Description |
|---|---|---|
| - | circular | Styles applied to the root element if variant="circular". |
| - | fitContent | Styles applied when the component is passed children and no width. |
| - | heightAuto | Styles applied when the component is passed children and no height. |
| - | pulse | Styles applied to the root element if animation="pulse". |
| - | rectangular | Styles applied to the root element if variant="rectangular". |
| - | root | Styles applied to the root element. |
| - | rounded | Styles applied to the root element if variant="rounded". |
| - | text | Styles applied to the root element if variant="text". |
| - | wave | Styles applied to the root element if animation="wave". |
| - | withChildren | Styles applied when the component is passed children. |
Source code
이 페이지에서 정보를 찾지 못했다면, 컴포넌트 구현을 살펴보고 더 자세한 내용을 확인해 보세요.