color-thief 라이브러리 도입
프로젝트에서 이미지의 메인 색을 추출해서 배경색을 지정해 주는 디자인 구현이 필요해서 관련 라이브러리로 color-thief를 도입하기로 결정하였다.
https://lokeshdhakar.com/projects/color-thief/
Color Thief
Examples Try it yourself Drag an image here Drop it! Getting started The Color Thief package includes multiple distribution files to support different environments and build processes. Here is the list of all the files in the /dist folder and what formats
lokeshdhakar.com
❗문제상황
현재 프로젝트에서 nextjs와 typescript를 사용하고 있었는데 문제는 Color Thief에서 typescript를 위한 @types/colorthief를 지원하지 않는다는 점이었다. 따라서 typescript 오류가 발생하는 상황에서 Color Thief를 사용할 수 없었다.
이때 선택한 차선책은 color-thief-react를 사용하는 것이었다. color-thief-react는 color thief를 기반으로 리액트 컴포넌트에서 훅처럼 사용할 수 있도록 만든 라이브러리였다. 해당 라이브러리를 선택한 이유는 react에서 간편하게 사용할 수 있다는 점과 typescript를 위한 type을 지원한다는 점이었다.
마지막 업데이트도 2년 전이고 아무래도 color thief가 원래 라이브러리이다 보니까 해당 라이브러리를 사용하는 것에 아쉬움이 있었지만 일단 차선책으로 도입해 보게 되었다.
✅첫 번째 해결: color-thief-react 사용하기
import { useColor } from 'color-thief-react';
const imgSrc =
'https://live.staticflickr.com/65535/50237066832_72c7290c5c_c.jpg';
...
const { data, error } = useColor(imgSrc, 'hex');
...
이런 식으로 훅처럼 사용할 수 있어서 사용 방법은 매우 간단하였다.
cors 에러
하지만 문제는 외부 이미지를 사용할 경우 cors 에러가 난다는 점이었다. canvas로 이미지 색을 추출 하면서 image를 canvas로 사용할 때 원본을 변경할 여지가 있다면 cors에러가 난다고 한다.
DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
이렇게 securityError가 발생하였고 찾아보니 관련해서 crossOrigin이라는 옵션이 존재하였다. 해당 옵션을 붙이면서 에러를 해결하는 듯했지만 문제는 공식 데모버전에서 제공하는 해당 이미지 주소에서만 적용되었고 임의의 이미지 주소를 넣으면 다시 cors 에러가 발생하다는 것이었다.
로컬 이미지를 넣는 경우에는 cors에러가 발생하지 않지만 프로젝트에서 필요한 기능은 동적 데이터의 이미지에 대한 배경색을 추출하는 것이었다. 서버를 연결하면 결과가 다를까 싶어 서버 api를 연결하여 동적으로 이미지를 받아 실행해 보았지만 cors에러가 발생하는 것은 마찬가지였다.
cors에러를 해결하기 위해 많은 구글링을 해보았는데 문제점은 대부분의 해결방법이 color-thief-react가 아니라 color thief에 관련된 해결책이라는 점이다. 그래서 다시 한번 color thief를 typescript에서 사용할 방법이 없을까 하고 검색하던 중 구세주를 발견하였다!
✅최종 해결 방법: color thief에서 type 지정하기
https://github.com/lokesh/color-thief/issues/188
typescript declaration file · Issue #188 · lokesh/color-thief
Try npm install @types/colorthief if it exists or add a new declaration (.d.ts) file containing declare module 'colorthief'; unfortunately it doesn't exist
github.com
위의 이슈에서 나와 동일한 문제를 겪고 있는 사람들이 있었고 그 사람들이 호환가능한 타입을 만들었던 것이다!
declare module 'colorthief' {
export type RGBColor = [number, number, number];
export default class ColorThief {
getColor: (img: HTMLImageElement | null, quality: number = 10) => RGBColor;
getPalette: (
img: HTMLImageElement | null,
colorCount: number = 10,
quality: number = 10,
) => RGBColor[];
}
}
이렇게 module로 만들어서 type을 위의 코드에서 찾을 수 있도록 지정해 주는 것이었다. 이 type 설정을 통해서 typescript에서도 color thief를 사용할 수 있게 되었다!
👩💻실제로 color thief 사용해 보기
const [bookCoverBgColor, setBookCoverBgColor] = useState('');
const handleGetImgColor = (image: HTMLImageElement) => {
const colorThief = new ColorThief();
const colorHex = colorThief
.getColor(image)
.map((x) => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
})
.join('');
setBookCoverBgColor(`#${colorHex}`);
};
동적으로 배경색을 설정하기 위해 hex값을 사용하려고 하였다. 그래서 getColors를 통해서 유사한 색에 대한 3개의 정수값을 받는다.

이렇게 색에 대한 세 가지의 정수 값을 받을 수 있다. 만약 여러 개의 색이 필요하다면 getPalette 를 사용하면 된다!
(기존에는 getPalette를 이용해서 색을 찾아오고 있었는데 포스팅 이후에 getColor를 사용하는 방법으로 리팩토링 할 예정이다!)
이렇게 정수값을 받은 후에 map을 통해서 각 값을 16진수 값으로 변경한다. hex의 경우 두 자리로 맞추는 것이 일반적이기 때문에 만약 한 자리 수일 경우 0을 붙여준다. 그리고 join을 통해서 하나의 문자열로 만들면 해당 이미지에 대한 메인 hex 값을 얻을 수 있게 되는 것!
이렇게 만든 hex 값을 state에 업데이트해 준 후 배경 색으로 설정해 주어 사용하였다.
<div
onClick={handleMoveRecrdFeed}
className='w-full h-[108px] flex justify-between border border-solid border-[#DFDFDF] rounded-md mb-4 px-6 overflow-hidden '
style={{
backgroundColor: `${bookCoverBgColor}20`,
}}
>
...
<div className='mt-4'>
<Image
src={thumbnail}
width={80}
height={110}
alt='책 표지'
className='w-[80px] h-[110px]'
crossOrigin='anonymous'
onLoadingComplete={handleGetImgColor}
/>
</div>
</div>
프로젝트에서 tailwind를 사용하고 있어서 동적으로 색을 지정하고 싶은 경우 bg-[#000000]과 같이 완전한 형태로 넘겨야 한다. 하지만 이 값은 완전한 형태로 넘겨줄 수 없어서 일단 인라인 스타일로 적용해 두었는데 추후에 다른 방법이 있는지 찾아볼 예정이다.
Image태그에 onLoadingComplete라는 속성의 콜백 함수로 handleGetImgColor를 설정해 두었다. 말 그대로 로딩이 끝난 후에 해당 함수를 실행하여 색을 추출함으로써 이미지가 아직 로딩되지 않았을 때 함수가 실행되는 것을 방지할 수 있다.

그러면 이렇게 책 메인 색깔에 따라 배경 색이 달라지는 것을 확인할 수 있다!
이전 프로젝트에서도 해당 라이브러리를 도입하고자 했으나 cors에러가 나서 그때는 방법을 찾지 못한 상황에서 시간을 많이 쏟을 수 없었어서 결국 도입하지 못했었다. 이번에도 해당 라이브러리를 다시 사용하기로 마음먹으면서 마찬가지로 cors에러와 type 지정으로 인해 꽤 많은 시간을 허비했었다. 계속에서 막히게 되면서 해당 디자인 구현이 어렵다고 하고 랜덤 색으로 변경해야 하나? 수없이 고민했지만 결국 방법을 찾고 구현할 수 있어서 기쁘다.
'개발 공부 > React' 카테고리의 다른 글
[next + tanstack query] 낙관적 업데이트 적용하기 (0) | 2023.07.22 |
---|---|
무한스크롤 스크롤 위치 기억하기 (0) | 2023.07.10 |
[프로젝트 고민]input 불필요한 렌더링 줄이기 (0) | 2023.04.15 |
[프로젝트 고민]useMutation에서 async/await 적용하기 (6) | 2023.03.30 |
input type='file' 동일 파일 재 선택 시 오류 (0) | 2023.03.22 |
color-thief 라이브러리 도입
프로젝트에서 이미지의 메인 색을 추출해서 배경색을 지정해 주는 디자인 구현이 필요해서 관련 라이브러리로 color-thief를 도입하기로 결정하였다.
https://lokeshdhakar.com/projects/color-thief/
Color Thief
Examples Try it yourself Drag an image here Drop it! Getting started The Color Thief package includes multiple distribution files to support different environments and build processes. Here is the list of all the files in the /dist folder and what formats
lokeshdhakar.com
❗문제상황
현재 프로젝트에서 nextjs와 typescript를 사용하고 있었는데 문제는 Color Thief에서 typescript를 위한 @types/colorthief를 지원하지 않는다는 점이었다. 따라서 typescript 오류가 발생하는 상황에서 Color Thief를 사용할 수 없었다.
이때 선택한 차선책은 color-thief-react를 사용하는 것이었다. color-thief-react는 color thief를 기반으로 리액트 컴포넌트에서 훅처럼 사용할 수 있도록 만든 라이브러리였다. 해당 라이브러리를 선택한 이유는 react에서 간편하게 사용할 수 있다는 점과 typescript를 위한 type을 지원한다는 점이었다.
마지막 업데이트도 2년 전이고 아무래도 color thief가 원래 라이브러리이다 보니까 해당 라이브러리를 사용하는 것에 아쉬움이 있었지만 일단 차선책으로 도입해 보게 되었다.
✅첫 번째 해결: color-thief-react 사용하기
import { useColor } from 'color-thief-react';
const imgSrc =
'https://live.staticflickr.com/65535/50237066832_72c7290c5c_c.jpg';
...
const { data, error } = useColor(imgSrc, 'hex');
...
이런 식으로 훅처럼 사용할 수 있어서 사용 방법은 매우 간단하였다.
cors 에러
하지만 문제는 외부 이미지를 사용할 경우 cors 에러가 난다는 점이었다. canvas로 이미지 색을 추출 하면서 image를 canvas로 사용할 때 원본을 변경할 여지가 있다면 cors에러가 난다고 한다.
DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
이렇게 securityError가 발생하였고 찾아보니 관련해서 crossOrigin이라는 옵션이 존재하였다. 해당 옵션을 붙이면서 에러를 해결하는 듯했지만 문제는 공식 데모버전에서 제공하는 해당 이미지 주소에서만 적용되었고 임의의 이미지 주소를 넣으면 다시 cors 에러가 발생하다는 것이었다.
로컬 이미지를 넣는 경우에는 cors에러가 발생하지 않지만 프로젝트에서 필요한 기능은 동적 데이터의 이미지에 대한 배경색을 추출하는 것이었다. 서버를 연결하면 결과가 다를까 싶어 서버 api를 연결하여 동적으로 이미지를 받아 실행해 보았지만 cors에러가 발생하는 것은 마찬가지였다.
cors에러를 해결하기 위해 많은 구글링을 해보았는데 문제점은 대부분의 해결방법이 color-thief-react가 아니라 color thief에 관련된 해결책이라는 점이다. 그래서 다시 한번 color thief를 typescript에서 사용할 방법이 없을까 하고 검색하던 중 구세주를 발견하였다!
✅최종 해결 방법: color thief에서 type 지정하기
https://github.com/lokesh/color-thief/issues/188
typescript declaration file · Issue #188 · lokesh/color-thief
Try npm install @types/colorthief if it exists or add a new declaration (.d.ts) file containing declare module 'colorthief'; unfortunately it doesn't exist
github.com
위의 이슈에서 나와 동일한 문제를 겪고 있는 사람들이 있었고 그 사람들이 호환가능한 타입을 만들었던 것이다!
declare module 'colorthief' {
export type RGBColor = [number, number, number];
export default class ColorThief {
getColor: (img: HTMLImageElement | null, quality: number = 10) => RGBColor;
getPalette: (
img: HTMLImageElement | null,
colorCount: number = 10,
quality: number = 10,
) => RGBColor[];
}
}
이렇게 module로 만들어서 type을 위의 코드에서 찾을 수 있도록 지정해 주는 것이었다. 이 type 설정을 통해서 typescript에서도 color thief를 사용할 수 있게 되었다!
👩💻실제로 color thief 사용해 보기
const [bookCoverBgColor, setBookCoverBgColor] = useState('');
const handleGetImgColor = (image: HTMLImageElement) => {
const colorThief = new ColorThief();
const colorHex = colorThief
.getColor(image)
.map((x) => {
const hex = x.toString(16);
return hex.length === 1 ? '0' + hex : hex;
})
.join('');
setBookCoverBgColor(`#${colorHex}`);
};
동적으로 배경색을 설정하기 위해 hex값을 사용하려고 하였다. 그래서 getColors를 통해서 유사한 색에 대한 3개의 정수값을 받는다.

이렇게 색에 대한 세 가지의 정수 값을 받을 수 있다. 만약 여러 개의 색이 필요하다면 getPalette 를 사용하면 된다!
(기존에는 getPalette를 이용해서 색을 찾아오고 있었는데 포스팅 이후에 getColor를 사용하는 방법으로 리팩토링 할 예정이다!)
이렇게 정수값을 받은 후에 map을 통해서 각 값을 16진수 값으로 변경한다. hex의 경우 두 자리로 맞추는 것이 일반적이기 때문에 만약 한 자리 수일 경우 0을 붙여준다. 그리고 join을 통해서 하나의 문자열로 만들면 해당 이미지에 대한 메인 hex 값을 얻을 수 있게 되는 것!
이렇게 만든 hex 값을 state에 업데이트해 준 후 배경 색으로 설정해 주어 사용하였다.
<div
onClick={handleMoveRecrdFeed}
className='w-full h-[108px] flex justify-between border border-solid border-[#DFDFDF] rounded-md mb-4 px-6 overflow-hidden '
style={{
backgroundColor: `${bookCoverBgColor}20`,
}}
>
...
<div className='mt-4'>
<Image
src={thumbnail}
width={80}
height={110}
alt='책 표지'
className='w-[80px] h-[110px]'
crossOrigin='anonymous'
onLoadingComplete={handleGetImgColor}
/>
</div>
</div>
프로젝트에서 tailwind를 사용하고 있어서 동적으로 색을 지정하고 싶은 경우 bg-[#000000]과 같이 완전한 형태로 넘겨야 한다. 하지만 이 값은 완전한 형태로 넘겨줄 수 없어서 일단 인라인 스타일로 적용해 두었는데 추후에 다른 방법이 있는지 찾아볼 예정이다.
Image태그에 onLoadingComplete라는 속성의 콜백 함수로 handleGetImgColor를 설정해 두었다. 말 그대로 로딩이 끝난 후에 해당 함수를 실행하여 색을 추출함으로써 이미지가 아직 로딩되지 않았을 때 함수가 실행되는 것을 방지할 수 있다.

그러면 이렇게 책 메인 색깔에 따라 배경 색이 달라지는 것을 확인할 수 있다!
이전 프로젝트에서도 해당 라이브러리를 도입하고자 했으나 cors에러가 나서 그때는 방법을 찾지 못한 상황에서 시간을 많이 쏟을 수 없었어서 결국 도입하지 못했었다. 이번에도 해당 라이브러리를 다시 사용하기로 마음먹으면서 마찬가지로 cors에러와 type 지정으로 인해 꽤 많은 시간을 허비했었다. 계속에서 막히게 되면서 해당 디자인 구현이 어렵다고 하고 랜덤 색으로 변경해야 하나? 수없이 고민했지만 결국 방법을 찾고 구현할 수 있어서 기쁘다.
'개발 공부 > React' 카테고리의 다른 글
[next + tanstack query] 낙관적 업데이트 적용하기 (0) | 2023.07.22 |
---|---|
무한스크롤 스크롤 위치 기억하기 (0) | 2023.07.10 |
[프로젝트 고민]input 불필요한 렌더링 줄이기 (0) | 2023.04.15 |
[프로젝트 고민]useMutation에서 async/await 적용하기 (6) | 2023.03.30 |
input type='file' 동일 파일 재 선택 시 오류 (0) | 2023.03.22 |