From d3e43654e92d98e16fa431a93109a7c0730709e0 Mon Sep 17 00:00:00 2001 From: htlee Date: Fri, 20 Feb 2026 20:49:22 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20html2canvas=20oklch/oklab=20=EC=83=89?= =?UTF-8?q?=EC=83=81=20=ED=8C=8C=EC=8B=B1=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tailwind CSS 4의 oklch()/color-mix(in oklab) 함수를 html2canvas가 파싱하지 못하는 문제 해결 — 캡처 전 모든 요소의 색상을 브라우저 computed RGB 값으로 강제 인라인 적용 후 캡처, 완료 후 원복 Co-Authored-By: Claude Opus 4.6 --- .../area-search/utils/captureUtils.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/frontend/src/features/area-search/utils/captureUtils.ts b/frontend/src/features/area-search/utils/captureUtils.ts index c773c34..5ba54e8 100644 --- a/frontend/src/features/area-search/utils/captureUtils.ts +++ b/frontend/src/features/area-search/utils/captureUtils.ts @@ -1,5 +1,45 @@ import html2canvas from 'html2canvas' +/** + * html2canvas가 oklch()/oklab()/color-mix() 등 최신 CSS 색상 함수를 지원하지 않으므로 + * 캡처 전 모든 요소의 색상을 브라우저 computed RGB 값으로 강제 인라인 적용. + * 캡처 후 원래 스타일로 복원하는 클린업 함수를 반환한다. + */ +const COLOR_PROPS = [ + 'color', 'background-color', + 'border-color', 'border-top-color', 'border-right-color', + 'border-bottom-color', 'border-left-color', + 'outline-color', 'text-decoration-color', +] + +function forceResolvedColors(root: HTMLElement): () => void { + const saved: { el: HTMLElement; cssText: string }[] = [] + const allElements = [root, ...Array.from(root.querySelectorAll('*'))] as HTMLElement[] + + for (const el of allElements) { + saved.push({ el, cssText: el.style.cssText }) + const cs = getComputedStyle(el) + + for (const prop of COLOR_PROPS) { + const val = cs.getPropertyValue(prop) + if (val && val !== 'transparent' && val !== 'rgba(0, 0, 0, 0)') { + el.style.setProperty(prop, val) + } + } + + const shadow = cs.getPropertyValue('box-shadow') + if (shadow && shadow !== 'none') { + el.style.setProperty('box-shadow', shadow) + } + } + + return () => { + for (const { el, cssText } of saved) { + el.style.cssText = cssText + } + } +} + /** * DOM 요소를 PNG로 캡처 후 다운로드 * @param contentEl 캡처할 콘텐츠 영역 (헤더 제외) @@ -23,6 +63,9 @@ export async function captureAndDownload( modal.style.maxHeight = 'none' modal.style.overflow = 'visible' + // oklch/oklab → RGB 강제 변환 (html2canvas 호환) + const restoreColors = forceResolvedColors(contentEl) + try { const canvas = await html2canvas(contentEl, { backgroundColor: isDark ? '#141820' : '#ffffff', @@ -41,6 +84,7 @@ export async function captureAndDownload( } catch (err) { console.error('[captureAndDownload] 이미지 저장 실패:', err) } finally { + restoreColors() contentEl.style.overflow = saved.elOverflow modal.style.maxHeight = saved.modalMaxHeight modal.style.overflow = saved.modalOverflow