이미지

이미지 (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
destroyOnClose 프리뷰를 닫을 때 자식 요소를 파괴해요. (제거되어 더 이상 지원하지 않아요.) boolean false
forceRender 프리뷰 이미지를 강제 렌더링해요. (제거되어 더 이상 지원하지 않아요.) 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
maskClassName 썸네일 마스크 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
toolbarRender 커스텀 툴바예요. 'actionsRender'를 대신 사용해 주세요. (originalNode: React.ReactElement, info: Omit<ToolbarRenderInfoType, 'current' | 'total'>) => React.ReactNode -
visible 표시 여부예요. 'open'을 대신 사용해 주세요. boolean -
onOpenChange 프리뷰 열림 상태 변경 시 콜백 (open: boolean) => void -
onTransform 프리뷰 변환 변경 시 콜백 { transform: TransformType, action: TransformAction } -
onVisibleChange '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 -
forceRender 프리뷰 이미지를 강제 렌더링해요. (제거되어 더 이상 지원하지 않아요.) 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
maskClassName 썸네일 마스크 class 이름이에요. 'classNames.cover'를 대신 사용해 주세요. string -
minScale 최소 확대 배율 number 1
maxScale 최대 확대 배율 number 50
movable 프리뷰 이미지가 뷰포트보다 클 때 드래그할 수 있는지 여부예요. boolean true
open 프리뷰를 표시할지 여부예요. boolean -
rootClassName 프리뷰의 루트 DOM class 이름이에요. 이미지와 프리뷰 래퍼 모두에 적용돼요. 'classNames.root'를 대신 사용해 주세요. string -
styles 커스텀 시맨틱 구조 스타일 Record<SemanticDOM, CSSProperties> -
wheel 마우스 휠 확대를 활성화할지 여부예요. boolean true 6.6.0
scaleStep 각 단계의 확대 배율이 1 + scaleStep이에요. number 0.5
toolbarRender 커스텀 툴바예요. 'actionsRender'를 대신 사용해 주세요. (originalNode: React.ReactElement, info: ToolbarRenderInfoType) => React.ReactNode -
visible 표시 여부예요. 'open'을 대신 사용해 주세요. boolean -
onOpenChange 프리뷰 열림 상태 변경 시 콜백으로, 현재 프리뷰 인덱스를 포함해요. (open: boolean, info: { current: number }) => void -
onChange 프리뷰 이미지 변경 시 콜백 (current: number, prevCurrent: number) => void -
onTransform 프리뷰 변환 변경 시 콜백 { transform: TransformType, action: TransformAction } -
onVisibleChange '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

더 알아보기 (Learn more)