이미지
이미지 (Image)
사진이나 일러스트를 보여 주고, 클릭하면 확대 프리뷰까지 지원하는 컴포넌트예요. 큰 이미지 로딩 중에는 플레이스홀더를, 로딩 실패 시에는 폴백 이미지를 보여 줄 수 있어요.
출처: 문서
본문
언제 사용하나요 (When To Use)
- 사진을 표시해야 할 때 사용해요.
- 큰 이미지를 로딩할 때 표시하거나, 로딩 실패 시 오류 허용(fault tolerant) 처리를 할 때 사용해요.
예시 (Examples)
기본 사용법 (Basic Usage)
이미지를 클릭하면 확대돼요.
import React from 'react';
import { Image } from 'antd';
const App: React.FC = () => (
<Image
width={200}
alt="basic"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
);
export default App;
점진적 로딩 (Progressive Loading)
placeholder prop으로 플레이스홀더를 설정해요. placeholder가 { progress: true }이면 수채화 잉크 같은 로딩 애니메이션을 보여 주고, { progress: { percent: number } }이면 진행률 바를 보여 줘요. 커스텀 React 노드를 플레이스홀더로 넘길 수도 있어요.
import React, { useEffect, useState } from 'react';
import { Button, Flex, Image, theme } from 'antd';
const GeneratingProgress: React.FC = () => {
const { token } = theme.useToken();
const [percent, setPercent] = useState(0);
const [status, setStatus] = useState<'idle' | 'generating' | 'complete'>('idle');
const imageStyles = {
root: { borderRadius: token.borderRadiusLG },
image: { borderRadius: token.borderRadiusLG },
cover: { borderRadius: token.borderRadiusLG },
};
useEffect(() => {
if (status === 'generating' && percent < 100) {
const timer = setTimeout(() => {
setPercent((prev) => Math.min(prev + Math.random() * 8 + 2, 100));
}, 200);
return () => clearTimeout(timer);
} else if (status === 'generating' && percent >= 100) {
const timer = setTimeout(() => {
setStatus('complete');
}, 200);
return () => clearTimeout(timer);
}
}, [status, percent]);
const handleStart = () => {
setPercent(0);
setStatus('generating');
};
const imageNode =
status === 'complete' ? (
<Image
width={200}
height={200}
styles={imageStyles}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
) : (
<Image
width={200}
height={200}
styles={imageStyles}
placeholder={{
progress: {
percent: Math.round(percent),
render: (progress, p) => (
<>
{progress}
<div style={{ marginTop: 8 }}>Generating {p}%</div>
</>
),
},
}}
/>
);
return (
<Flex vertical gap={8}>
<Button type="primary" onClick={handleStart} disabled={status === 'generating'}>
Generate
</Button>
{imageNode}
</Flex>
);
};
const App: React.FC = () => {
const { token } = theme.useToken();
const [random, setRandom] = useState<number>(() => Date.now());
const imageStyles = {
root: { borderRadius: token.borderRadiusLG },
image: { borderRadius: token.borderRadiusLG },
cover: { borderRadius: token.borderRadiusLG },
};
return (
<>
<Flex gap={16} wrap>
<Image width={200} height={200} styles={imageStyles} placeholder={{ progress: true }} />
<Image
width={200}
height={200}
styles={imageStyles}
placeholder={{ progress: { render: () => 'loading...' } }}
/>
<Image
width={200}
height={200}
styles={imageStyles}
placeholder={{ progress: { percent: 50 } }}
/>
<Image
width={200}
height={200}
styles={imageStyles}
placeholder={{
progress: {
percent: 75,
render: (progress, p) => (
<>
{progress}
<div style={{ marginTop: 8 }}>Generating {p}%</div>
</>
),
},
}}
/>
</Flex>
<Flex gap={16} wrap style={{ marginTop: 16 }}>
<Flex vertical gap={8}>
<Button
type="primary"
onClick={() => {
setRandom(Date.now());
}}
>
Reload Image
</Button>
<Image
width={200}
height={200}
alt="basic image"
styles={imageStyles}
src={`https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?${random}`}
placeholder={
<div
style={{
width: '100%',
height: '100%',
background: 'rgba(255, 255, 255, 0.3)',
backdropFilter: 'blur(10px)',
borderRadius: token.borderRadiusLG,
}}
/>
}
/>
</Flex>
<GeneratingProgress />
</Flex>
</>
);
};
export default App;
오류 허용 (Fault tolerant)
로딩에 실패하면 대체 이미지 플레이스홀더를 표시해요.
import React from 'react';
import { Image } from 'antd';
const App: React.FC = () => (
<Image
alt="basic image"
width={200}
height={200}
src="error"
fallback="data:image/png;base64,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"
/>
);
export default App;
여러 이미지 프리뷰 (Multiple image preview)
좌우 전환 버튼을 클릭하면 여러 이미지를 프리뷰할 수 있어요.
import React from 'react';
import { Image } from 'antd';
const App: React.FC = () => (
<Image.PreviewGroup
preview={{
onChange: (current, prev) => console.log(`current index: ${current}, prev index: ${prev}`),
}}
>
<Image
alt="svg image"
width={200}
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
/>
<Image
width={200}
alt="svg image"
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
/>
</Image.PreviewGroup>
);
export default App;
한 이미지에서 프리뷰 (Preview from one image)
하나의 이미지에서 여러 장의 컬렉션을 프리뷰해요.
import React from 'react';
import { Image } from 'antd';
const App: React.FC = () => (
<Image.PreviewGroup
items={[
'https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp',
'https://gw.alipayobjects.com/zos/antfincdn/cV16ZqzMjW/photo-1473091540282-9b846e7965e3.webp',
'https://gw.alipayobjects.com/zos/antfincdn/x43I27A55%26/photo-1438109491414-7198515b166b.webp',
]}
>
<Image
alt="webp image"
width={200}
src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp"
/>
</Image.PreviewGroup>
);
export default App;
커스텀 프리뷰 이미지 (Custom preview image)
프리뷰에 다른 이미지를 설정할 수 있어요.
import React from 'react';
import { Image } from 'antd';
const App: React.FC = () => (
<Image
width={200}
alt="basic image"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
preview={{
src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}}
/>
);
export default App;
제어되는 프리뷰 (Controlled Preview)
프리뷰를 제어(controlled) 상태로 만들 수 있어요.
import React, { useState } from 'react';
import { Button, Image, InputNumber } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [scaleStep, setScaleStep] = useState(0.5);
return (
<>
<div>
scaleStep:{' '}
<InputNumber
min={0.1}
max={5}
defaultValue={0.5}
step={0.1}
onChange={(val) => setScaleStep(val!)}
/>
</div>
<br />
<Button type="primary" onClick={() => setOpen(true)}>
show image preview
</Button>
<Image
width={200}
style={{ display: 'none' }}
alt="basic image"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
preview={{
open,
scaleStep,
src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
onOpenChange: (nextOpen) => {
setOpen(nextOpen);
},
}}
/>
</>
);
};
export default App;
커스텀 툴바 렌더 (Custom toolbar render)
툴바를 커스터마이즈해서 원본 이미지 다운로드, 뒤집기·회전한 이미지 다운로드 같은 버튼을 추가할 수 있어요.
import React from 'react';
import {
DownloadOutlined,
LeftOutlined,
RightOutlined,
RotateLeftOutlined,
RotateRightOutlined,
SwapOutlined,
UndoOutlined,
ZoomInOutlined,
ZoomOutOutlined,
} from '@ant-design/icons';
import { Image, Space } from 'antd';
import { createStyles } from 'antd-style';
const useStyles = createStyles((props) => {
const { css, iconPrefixCls, cssVar } = props;
return {
wrapper: css`
padding: 0 ${cssVar.paddingLG};
color: ${cssVar.colorWhite};
font-size: ${cssVar.fontSizeXL};
background-color: rgba(0, 0, 0, 0.1);
border-radius: 100px;
.${iconPrefixCls} {
padding: ${cssVar.paddingSM};
cursor: pointer;
&:hover {
opacity: 0.3;
}
&[disabled] {
opacity: 0.3;
cursor: not-allowed;
}
}
`,
};
});
const imageList = [
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
];
// you can download flipped and rotated image
// https://codesandbox.io/s/zi-ding-yi-gong-ju-lan-antd-5-7-0-forked-c9jvmp
const App: React.FC = () => {
const { styles } = useStyles();
const [current, setCurrent] = React.useState(0);
// or you can download flipped and rotated image
// https://codesandbox.io/s/zi-ding-yi-gong-ju-lan-antd-5-7-0-forked-c9jvmp
const onDownload = () => {
const url = imageList[current];
const suffix = url.slice(url.lastIndexOf('.'));
const filename = Date.now() + suffix;
fetch(url)
.then((response) => response.blob())
.then((blob) => {
const blobUrl = URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(blobUrl);
link.remove();
});
};
return (
<Image.PreviewGroup
preview={{
actionsRender: (
_,
{
transform: { scale },
actions: {
onActive,
onFlipY,
onFlipX,
onRotateLeft,
onRotateRight,
onZoomOut,
onZoomIn,
onReset,
},
},
) => (
<Space size={12} className={styles.wrapper}>
<LeftOutlined disabled={current === 0} onClick={() => onActive?.(-1)} />
<RightOutlined
disabled={current === imageList.length - 1}
onClick={() => onActive?.(1)}
/>
<DownloadOutlined onClick={onDownload} />
<SwapOutlined rotate={90} onClick={onFlipY} />
<SwapOutlined onClick={onFlipX} />
<RotateLeftOutlined onClick={onRotateLeft} />
<RotateRightOutlined onClick={onRotateRight} />
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
<UndoOutlined onClick={onReset} />
</Space>
),
onChange: (index) => {
setCurrent(index);
},
}}
>
{imageList.map((item, index) => (
<Image alt={`image-${index}`} key={item} src={item} width={200} />
))}
</Image.PreviewGroup>
);
};
export default App;
커스텀 프리뷰 렌더 (Custom preview render)
프리뷰 콘텐츠를 커스터마이즈할 수 있어요.
import React from 'react';
import { Image } from 'antd';
const App: React.FC = () => (
<Image
width={200}
alt="basic image"
preview={{
imageRender: () => (
<video
muted
width="100%"
controls
src="https://mdn.alipayobjects.com/huamei_iwk9zp/afts/file/A*uYT7SZwhJnUAAAAAAAAAAAAADgCCAQ"
/>
),
actionsRender: () => null,
}}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
);
export default App;
프리뷰 마스크 (preview mask)
마스크 효과예요.
import React from 'react';
import { Image, Space } from 'antd';
const App: React.FC = () => {
return (
<Space>
<Image
width={100}
alt="blur preview"
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
preview={{
mask: { blur: true },
cover: (
<Space vertical align="center">
blur
</Space>
),
}}
/>
<Image
alt="Dimmed mask preview"
width={100}
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
preview={{
cover: (
<Space vertical align="center">
Dimmed mask
</Space>
),
}}
/>
<Image
width={100}
alt="No mask preview"
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
preview={{
mask: false,
cover: (
<Space vertical align="center">
No mask
</Space>
),
}}
/>
</Space>
);
};
export default App;
시맨틱 DOM 스타일링 (Custom semantic dom styling)
classNames와 styles로 객체나 함수를 전달해 Image의 시맨틱 DOM 스타일을 커스터마이즈할 수 있어요.
import React from 'react';
import { Flex, Image } from 'antd';
import type { GetProp, ImageProps } from 'antd';
import { createStaticStyles } from 'antd-style';
const classNames = createStaticStyles(({ css }) => ({
root: css`
padding: 4px;
border-radius: 8px;
overflow: hidden;
`,
}));
const styles: ImageProps['styles'] = {
image: {
borderRadius: '4px',
},
};
const stylesFn: ImageProps['styles'] = (info): GetProp<ImageProps, 'styles', 'Return'> => {
if (info.props.preview) {
return {
root: {
border: '2px solid #A594F9',
borderRadius: 8,
padding: 4,
transition: 'all 0.3s ease',
},
image: {
borderRadius: 4,
filter: 'grayscale(50%)',
},
};
}
return {};
};
const App: React.FC = () => {
const sharedProps: ImageProps = {
src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
width: 160,
alt: 'Example image',
classNames,
};
return (
<Flex gap="medium">
<Image {...sharedProps} styles={styles} />
<Image {...sharedProps} styles={stylesFn} preview={{ open: false }} />
</Flex>
);
};
export default App;
중첩 (nested)
Modal 안에 중첩해서 사용할 수 있어요.
import React, { useState } from 'react';
import { Button, Divider, Image, Modal } from 'antd';
const App: React.FC = () => {
const [show1, setShow1] = useState(false);
const [show2, setShow2] = useState(false);
const [show3, setShow3] = useState(false);
return (
<>
<Button
onClick={() => {
setShow1(true);
}}
>
showModal
</Button>
<Modal
open={show1}
afterOpenChange={(open) => {
setShow1(open);
}}
onCancel={() => {
setShow1(false);
}}
onOk={() => setShow1(false)}
>
<Button
onClick={() => {
setShow2(true);
}}
>
test2
</Button>
<Modal
open={show2}
afterOpenChange={(open) => {
setShow2(open);
}}
onCancel={() => {
setShow2(false);
}}
onOk={() => setShow2(false)}
>
<Button
onClick={() => {
setShow3(true);
}}
>
test3
</Button>
<Modal
open={show3}
afterOpenChange={(open) => {
setShow3(open);
}}
onCancel={() => {
setShow3(false);
}}
onOk={() => setShow3(false)}
>
<Image
width={200}
alt="svg image"
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
/>
<Divider />
<Image.PreviewGroup
preview={{
onChange: (current, prev) =>
console.log(`current index: ${current}, prev index: ${prev}`),
}}
>
<Image
width={200}
alt="svg image"
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
/>
<Image
width={200}
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
/>
</Image.PreviewGroup>
</Modal>
</Modal>
</Modal>
</>
);
};
export default App;
API
공통 props는 Common props를 참고해요.
Image
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) | 글로벌 설정 |
|---|---|---|---|---|---|
| alt | 이미지 설명이에요. | string | - | × | |
| classNames | 컴포넌트 내부의 각 시맨틱 구조에 대한 class를 지정해요. 객체 또는 함수를 지원해요. | Record<SemanticDOM, string> | (info: { props })=> Record<SemanticDOM, string> | - | 6.0.0 | |
| fallback | 로드 실패 시 대체 URL이에요. | string | - | 5.28.0 | |
| height | 이미지 높이 | string | number | - | × | |
| placeholder | 로딩 플레이스홀더예요. ReactNode 또는 설정 객체를 지원해요. | PlaceholderType | - | × | |
| preview | 프리뷰 설정이에요. false로 설정하면 비활성화돼요. | boolean | PreviewType | true | preview.closeIcon: 5.14.0, preview.mask: 6.0.0, preview.mask.closable: 6.4.0 |
|
| src | 이미지 URL | string | - | × | |
| styles | 컴포넌트 내부의 각 시맨틱 구조에 대한 인라인 스타일을 지정해요. 객체 또는 함수를 지원해요. | Record<SemanticDOM, CSSProperties> | (info: { props })=> Record<SemanticDOM, CSSProperties> | - | 6.0.0 | |
| width | 이미지 너비 | string | number | - | × | |
| onError | 로딩 오류 발생 시 콜백이에요. | (event: Event) => void | - | × |
그 외 속성은 <img> 참고해요.
PlaceholderType
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| progress | 진행률 설정이에요. true로 설정하면 그라데이션 애니메이션을, { percent: number }로 설정하면 진행률을 보여 주고, render는 커스텀 렌더링이에요. |
boolean | ImageProgressConfig | - |
ImageProgressConfig
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| percent | 진행률 값 | number | - | |
| render | 커스텀 렌더링이에요. 기본 진행률 UI와 퍼센트를 받아요. | (progress: React.ReactNode, percent: number) => React.ReactNode | - |
PreviewType
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| actionsRender | 커스텀 툴바 렌더 | (originalNode: React.ReactElement, info: ToolbarRenderInfoType) => React.ReactNode | - | |
| closeIcon | 커스텀 닫기 아이콘 | React.ReactNode | - | |
| cover | 커스텀 프리뷰 마스크 | React.ReactNode | CoverConfig | - | CoverConfig support after v6.0 |
| focusTrap | 프리뷰가 열릴 때 포커스를 프리뷰 안에 가둘지 여부예요. | boolean | true | 6.4.0 |
| 프리뷰를 닫을 때 자식 요소를 파괴해요. (제거되어 더 이상 지원하지 않아요.) | boolean | false | ||
| 프리뷰 이미지를 강제 렌더링해요. (제거되어 더 이상 지원하지 않아요.) | boolean | - | ||
| getContainer | 프리뷰가 마운트될 컨테이너를 지정해요. 여전히 전체 화면이며, false면 현재 위치에 마운트돼요. | string | HTMLElement | (() => HTMLElement) | false | - | |
| imageRender | 커스텀 프리뷰 콘텐츠 | (originalNode: React.ReactElement, info: { transform: TransformType, image: ImgInfo }) => React.ReactNode | - | |
| mask | 프리뷰 마스크 효과 | boolean | { enabled?: boolean, blur?: boolean, closable?: boolean } | true | mask.closable: 6.4.0 |
| 썸네일 마스크 class 이름이에요. 'classNames.cover'를 대신 사용해 주세요. | string | - | ||
| maxScale | 최대 확대 배율 | number | 50 | |
| minScale | 최소 확대 배율 | number | 1 | |
| movable | 프리뷰 이미지가 뷰포트보다 클 때 드래그할 수 있는지 여부예요. | boolean | true | |
| open | 프리뷰를 표시할지 여부예요. | boolean | - | |
| rootClassName | 프리뷰의 루트 DOM class 이름이에요. 이미지와 프리뷰 래퍼 모두에 적용돼요. | string | - | |
| scaleStep | 각 단계의 확대 배율이 1 + scaleStep이에요. | number | 0.5 | |
| src | 커스텀 프리뷰 src | string | - | |
| styles | 커스텀 시맨틱 구조 스타일 | Record<SemanticDOM, CSSProperties> | - | |
| wheel | 마우스 휠 확대를 활성화할지 여부예요. | boolean | true | 6.6.0 |
| 커스텀 툴바예요. 'actionsRender'를 대신 사용해 주세요. | (originalNode: React.ReactElement, info: Omit<ToolbarRenderInfoType, 'current' | 'total'>) => React.ReactNode | - | ||
| 표시 여부예요. 'open'을 대신 사용해 주세요. | boolean | - | ||
| onOpenChange | 프리뷰 열림 상태 변경 시 콜백 | (open: boolean) => void | - | |
| onTransform | 프리뷰 변환 변경 시 콜백 | { transform: TransformType, action: TransformAction } | - | |
| 'visible' 변경 시 콜백이에요. 'onOpenChange'를 대신 사용해 주세요. | (visible: boolean, prevVisible: boolean) => void | - |
PreviewGroup
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| classNames | 컴포넌트 내부의 각 시맨틱 구조에 대한 class를 지정해요. 객체 또는 함수를 지원해요. | Record<SemanticDOM, string> | (info: { props })=> Record<SemanticDOM, string> | - | |
| fallback | 로드 오류 시 대체 URL | string | - | |
| items | 프리뷰 항목 배열 | string[] | { src: string, crossOrigin: string, ... }[] | - | |
| preview | 프리뷰 설정이에요. false로 설정하면 비활성화돼요. | boolean | PreviewGroupType | true |
PreviewGroupType
| 속성 (Property) | 설명 (Description) | 타입 (Type) | 기본값 (Default) | 버전 (Version) |
|---|---|---|---|---|
| actionsRender | 커스텀 툴바 렌더 | (originalNode: React.ReactElement, info: ToolbarRenderInfoType) => React.ReactNode | - | |
| closeIcon | 커스텀 닫기 아이콘 | React.ReactNode | - | |
| countRender | 커스텀 프리뷰 개수 렌더 | (current: number, total: number) => React.ReactNode | - | |
| focusTrap | 프리뷰가 열릴 때 포커스를 프리뷰 안에 가둘지 여부예요. | boolean | true | 6.4.0 |
| current | 현재 프리뷰 이미지의 인덱스 | number | - | |
| 프리뷰 이미지를 강제 렌더링해요. (제거되어 더 이상 지원하지 않아요.) | boolean | - | ||
| getContainer | 프리뷰가 마운트될 컨테이너를 지정해요. 여전히 전체 화면이며, false면 현재 위치에 마운트돼요. | string | HTMLElement | (() => HTMLElement) | false | - | |
| imageRender | 커스텀 프리뷰 콘텐츠 | (originalNode: React.ReactElement, info: { transform: TransformType, image: ImgInfo, current: number }) => React.ReactNode | - | |
| mask | 프리뷰 마스크 효과 | boolean | { enabled?: boolean, blur?: boolean, closable?: boolean } | true | mask.closable: 6.4.0 |
| 썸네일 마스크 class 이름이에요. 'classNames.cover'를 대신 사용해 주세요. | string | - | ||
| minScale | 최소 확대 배율 | number | 1 | |
| maxScale | 최대 확대 배율 | number | 50 | |
| movable | 프리뷰 이미지가 뷰포트보다 클 때 드래그할 수 있는지 여부예요. | boolean | true | |
| open | 프리뷰를 표시할지 여부예요. | boolean | - | |
| 프리뷰의 루트 DOM class 이름이에요. 이미지와 프리뷰 래퍼 모두에 적용돼요. 'classNames.root'를 대신 사용해 주세요. | string | - | ||
| styles | 커스텀 시맨틱 구조 스타일 | Record<SemanticDOM, CSSProperties> | - | |
| wheel | 마우스 휠 확대를 활성화할지 여부예요. | boolean | true | 6.6.0 |
| scaleStep | 각 단계의 확대 배율이 1 + scaleStep이에요. | number | 0.5 | |
| 커스텀 툴바예요. 'actionsRender'를 대신 사용해 주세요. | (originalNode: React.ReactElement, info: ToolbarRenderInfoType) => React.ReactNode | - | ||
| 표시 여부예요. 'open'을 대신 사용해 주세요. | boolean | - | ||
| onOpenChange | 프리뷰 열림 상태 변경 시 콜백으로, 현재 프리뷰 인덱스를 포함해요. | (open: boolean, info: { current: number }) => void | - | |
| onChange | 프리뷰 이미지 변경 시 콜백 | (current: number, prevCurrent: number) => void | - | |
| onTransform | 프리뷰 변환 변경 시 콜백 | { transform: TransformType, action: TransformAction } | - | |
| 'visible' 변경 시 콜백이에요. 'onOpenChange'를 대신 사용해 주세요. | (visible: boolean, prevVisible: boolean, current: number) => void | - |
인터페이스 (Interface)
TransformType
{
x: number;
y: number;
rotate: number;
scale: number;
flipX: boolean;
flipY: boolean;
}
TransformAction
type TransformAction =
| 'flipY'
| 'flipX'
| 'rotateLeft'
| 'rotateRight'
| 'zoomIn'
| 'zoomOut'
| 'close'
| 'prev'
| 'next'
| 'wheel'
| 'doubleClick'
| 'move'
| 'dragRebound'
| 'reset';
ToolbarRenderInfoType
{
icons: {
flipYIcon: React.ReactNode;
flipXIcon: React.ReactNode;
rotateLeftIcon: React.ReactNode;
rotateRightIcon: React.ReactNode;
zoomOutIcon: React.ReactNode;
zoomInIcon: React.ReactNode;
};
actions: {
onActive?: (index: number) => void; // support after 5.21.0
onFlipY: () => void;
onFlipX: () => void;
onRotateLeft: () => void;
onRotateRight: () => void;
onZoomOut: () => void;
onZoomIn: () => void;
onReset: () => void; // support after 5.17.3
onClose: () => void;
};
transform: TransformType,
current: number;
total: number;
image: ImgInfo
}
ImgInfo
{
url: string;
alt: string;
width: string | number;
height: string | number;
}
CoverConfig
type CoverConfig = {
coverNode?: React.ReactNode; // The custom node of preview mask
placement?: 'top' | 'bottom' | 'center'; // Set the position of the preview mask display.
};
시맨틱 DOM (Semantic DOM)
시맨틱 DOM 구조는 https://ant.design/components/image/semantic.md 에서 확인할 수 있어요.
디자인 토큰 (Design Token)
컴포넌트 토큰 (Image) (Component Token)
| 토큰 이름 (Token Name) | 설명 (Description) | 타입 (Type) | 기본값 (Default Value) |
|---|---|---|---|
| previewOperationColor | 프리뷰 동작 아이콘의 색 | string | rgba(255,255,255,0.65) |
| previewOperationColorDisabled | 프리뷰 동작 아이콘의 비활성 색 | string | rgba(255,255,255,0.25) |
| previewOperationHoverColor | hover된 프리뷰 동작 아이콘의 색 | string | rgba(255,255,255,0.85) |
| previewOperationSize | 프리뷰 동작 아이콘의 크기 | number | 18 |
| progressAnimationDuration | 로딩 애니메이션의 기본 지속 시간 | string | 3s |
| zIndexPopup | 프리뷰 팝업의 z-index | number | 1080 |
글로벌 토큰 (Global Token)
| 토큰 이름 (Token Name) | 설명 (Description) | 타입 (Type) | 기본값 (Default Value) |
|---|---|---|---|
| borderRadiusXS | XS 크기 테두리 반경이에요. Segmented, Arrow 등 작은 테두리 반경을 가진 컴포넌트에 사용돼요. | number | |
| colorBgBase | 배경색 그라데이션의 기본 변수를 파생하는 데 사용돼요. v5에서 배경색 map 토큰을 만드는 배경색 파생 알고리즘 레이어를 추가했어요. 하지만 이 Seed Token을 코드에서 직접 사용하지 마세요! | string | |
| colorBgContainerDisabled | 비활성 상태에서 컨테이너의 배경색을 제어해요. | string | |
| colorBgMask | 마스크의 배경색이에요. 마스크 아래의 콘텐츠를 덮는 데 사용되며, Modal, Drawer, Image 등 컴포넌트가 이 토큰을 사용해요. | string | |
| colorPrimaryBorder | 메인 색 그라데이션 아래의 스트로크 색이에요. Slider 같은 컴포넌트의 stroke에 사용돼요. | string | |
| colorTextLightSolid | 배경색이 있는 텍스트의 강조 색을 제어해요. Primary Button 컴포넌트의 텍스트 등. | string | |
| colorTextSecondary | 2단계 텍스트 색으로, 라벨 텍스트, 메뉴 텍스트 선택 상태처럼 텍스트 색을 강조하지 않는 시나리오에서 주로 쓰여요. | string | |
| controlHeightLG | LG 컴포넌트 높이 | number | |
| fontSize | 디자인 시스템에서 가장 널리 쓰이는 글자 크기로, 여기서 텍스트 그라데이션이 파생돼요. | number | |
| lineWidthFocus | 컴포넌트가 포커스 상태일 때 선의 너비를 제어해요. | number | |
| margin | 중간 크기의 요소 여백을 제어해요. | number | |
| marginSM | 중간-작은 크기의 요소 여백을 제어해요. | number | |
| marginXL | 특대 크기의 요소 여백을 제어해요. | number | |
| marginXS | 작은 크기의 요소 여백을 제어해요. | number | |
| motionDurationMid | 동작 속도, 중간 속도예요. 중간 요소의 애니메이션 상호작용에 사용돼요. | string | |
| motionDurationSlow | 동작 속도, 느린 속도예요. 큰 요소의 애니메이션 상호작용에 사용돼요. | string | |
| motionEaseInOut | 미리 정의된 모션 곡선이에요. | string | |
| motionEaseOut | 미리 정의된 모션 곡선이에요. | string | |
| paddingLG | 요소의 큰 패딩을 제어해요. | number | |
| paddingSM | 요소의 작은 패딩을 제어해요. | number |