[React] ColorThief - 색 추출 기능

hyejinJo·2025년 8월 12일
1

React

목록 보기
18/19
post-thumbnail

슬라이드 배너를 만들 때, 각 요소 썸네일 이미지의 색을 추출해 배경색을 깔아달라는 디자인의 요청이 있었다. 그래서 발견한 것이 ColorThief 라는 라이브러리..!
ColorThief 는 Javascript만 사용하여 이미지에서 색상 팔레트를 가져오는 라이브러리라고 한다.

ColorThief 를 설치 후 import 해주자

npm i --save colorthief
import ColorThief from 'colorthief';
const imageRef = useRef<HTMLImageElement>(null);
const colorThief = useRef<ColorThief | null>(null);
const [colorList, setColorList] = useState<string[]>([]);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
  colorThief.current = new ColorThief();
}, []);

ColorThief 인스턴스를 생성한 후 ref에 저장해주자

imageRef 역시 img 요소를 반복문으로 저장해주고 있다.

const { slides, options, isAutoPlay = true, onClick } = props;
// slides: 슬라이드 요소(ReactNode)가 담긴 배열

<S.EmblaContainer className='embla__container'>
    {slides?.map((slide, index) => (
      <S.EmblaSlide
        className='embla__slide'
        $isOnClick={!!onClick}
        key={index}
        onClick={() => onClick?.(slide)}
      >
        <S.EmblaSlideContent>
          <S.EmblaSlideImage>
            <S.EmblaSlideImageBadge>
              {slide.isNew && (
                <CommonBadge
                  label='NEW'
                  color='primary.blue'
                  bgColor='secondary.skyblue'
                  borderNone
                />
              )}
            </S.EmblaSlideImageBadge>
            {error && <div>오류 발생: {error}</div>}
            <img
              ref={imageRef}
              src={slide?.attach?.realPath}
              alt={slide?.title}
              onLoad={handleImageLoad(index)}
              crossOrigin='anonymous'
              style={{ display: 'block' }}
              className={`embla-slide-image-${index}`}
            />
            
            ...

img 가 로드될 때를 기점으로 이미지를 참조하여 colorThief 내장함수인 getColor() 의 인수로 넣어준다.

이때, getColor() 함수가 내부적으로 img 의 데이터를 가져오려 하면서 아래와 같은 예외 상황이 발생할 수 있다.

  • 이미지가 cross-origin 상태일 때 (CORS 문제),
  • img가 아직 완전히 로드되지 않았을 때

이런 상황을 대비해 try/catch 문을 사용해주자.

그렇게 하면 rgb 세가지 숫자를 반환해주는데(ex. 23, 31, 54), 이때 이 세가지 값들이 들어간 dominantColor 를 colorList 에 rgb 문자 형식으로 넣어주면 된다.!

const handleImageLoad = (index: number) => (event: React.SyntheticEvent<HTMLImageElement>) => {
  const img = event.currentTarget;

  if (colorThief.current) {
    try {
      const dominantColor = colorThief.current.getColor(img);
      const rgbString = `rgb(${dominantColor[0]}, ${dominantColor[1]}, ${dominantColor[2]})`;
      setColorList((prev) => {
        const newList = [...prev];
        newList[index] = rgbString;
        return newList;
      });
      setError(null);
    } catch (err) {
      setError('색상 추출 중 오류가 발생했습니다.');
      console.error(err);
    }
  }
};

하지만 새로고침을 했을 경우 색상이 추출되지 않는 상황이 발생했다.

이는 useEffect 로 따로 설정을 해주었다.

위에서 언급했듯이, 이미지가 완전히 로드되었을 때를 기점으로 불러주는것이 바람직하기 떄문에 img.complete 등의 조건부를 넣어주자.

// 새로고침 or 슬라이드가 변경되거나 컴포넌트가 마운트될 때 색상 추출 시도
  useEffect(() => {
    if (slides && colorThief.current && Array.isArray(slides)) {
      // 약간의 지연 후 색상 추출 시도 (이미지가 로드되었을 가능성)
      const timer = setTimeout(() => {
        slides.forEach((_, index) => {
          const img = document.querySelector(`.embla-slide-image-${index}`) as HTMLImageElement;
          if (img && img.complete && img.naturalWidth > 0) {
            try {
              const dominantColor = colorThief.current!.getColor(img);
              const rgbString = `rgb(${dominantColor[0]}, ${dominantColor[1]}, ${dominantColor[2]})`;
              setColorList((prev) => {
                const newList = [...prev];
                newList[index] = rgbString;
                return newList;
              });
            } catch (err) {
              console.error(`색상 추출 실패 (인덱스 ${index}):`, err);
            }
          }
        });
      }, 100);

      return () => clearTimeout(timer);
    }
  }, [slides]);

마지막 코드를 추가해주니 새로고침을 했을 때도 정상적으로 색상이 잘 추출되는 것을 확인할 수 있었다..! ☺️

profile
Frontend Developer 💡

0개의 댓글