# experimental-canvas — HTML-in-Canvas 실전 지침 Chrome의 HTML-in-Canvas API로 **실제 HTML 요소를 캔버스 픽셀/GPU 텍스처로 그린다.** 요소는 DOM에 남아 클릭·포커스·접근성이 유지된다. 구현 직전에 읽는 문서다. ## 1. 발동 조건 게이트 **기본값은 "쓰지 않는다".** 세 관문을 전부 통과할 때만 진행한다. ### 관문 1 — 셰이더가 HTML의 렌더된 픽셀을 읽어야 하는가 | 하려는 것 | 판정 | |---|---| | 글로우, 파티클, 커서 트레일, 배경 그라디언트 | **배경 캔버스 오버레이로 충분.** 쓰지 마라 | | 유리 굴절, 프로스티드 패널 | **SVG `feDisplacementMap` + `backdrop-filter`** | | 카드/패널을 3D로 기울이기 | **`CSS3DRenderer`.** DOM 그대로라 완벽하다 | | 상태 A → B 페이지 전환 | **View Transitions + `mask-image`** | | 텍스트를 픽셀 단위로 왜곡, 요소의 일부만 압축 | 후보 | | 라이트/다크 두 렌더를 노이즈로 픽셀 합성 | 후보 | | 렌더된 픽셀의 휘도·엣지에 반응하는 효과 | 후보 | | 3D 메시 위의 상호작용 UI (천, 책, 화면) | 후보 | 배경 캔버스는 **그릴 수는 있어도 읽을 수는 없다.** 읽어야만 하는 경우가 아니면 탈락이다. ### 관문 2 — 폴백이 이미 완성되어 있는가 **폴백 없는 구현은 금지다.** 순서를 뒤집지 마라. 1. HTML/CSS만으로 페이지를 **완성**한다. 레이아웃·포커스 순서·읽기 순서는 여기서 끝난다. 2. 폴백 이펙트(SVG 필터 / CSS 트랜지션 / 배경 캔버스)를 붙여 **그 상태로 출시 가능하게** 만든다. 3. 그 위에 HTML-in-Canvas를 **얹는다**. 캔버스는 장식이다. 구조가 아니다. ### 관문 3 — 맥락이 허용하는가 사용자가 **명시적으로 실험을 원했거나**, **데모·포트폴리오·사내 도구**처럼 브라우저를 통제할 수 있을 때만. 일반 사용자 대상 프로덕션이면 여기서 멈춘다. ## 2. 가용성 — 읽는 시점에 반드시 확인 | 항목 | 값 | |---|---| | 플래그 | `chrome://flags/#canvas-draw-element` → Enabled | | 권장 브라우저 | Chrome Canary 149+ | | Origin Trial | Chrome 148 ~ **154**. **2026년 10월 초 만료 예정** | | Stable 기본 활성화 | **없음.** chromestatus 상태는 `In development` | | Firefox / Safari | 구현 없음, 입장 미표명 | **OT 만료 후에는 플래그 전용으로 되돌아간다.** 일반 사용자에게는 아무것도 보이지 않고 **폴백이 곧 실제 결과물이 된다.** 2026-10 이후에 읽고 있다면 `chromestatus.com/feature/5172548013916160`에서 연장/출시 여부를 먼저 확인하라. ## 3. 권장 경로 — 폴리필 우선 3D를 쓴다면 **`three-html-render` 폴리필로 시작한다.** three.js 공식 예제가 쓰는 방식이다. 네이티브가 있으면 `texElementImage2D` fast path, 없으면 `foreignObject` 래스터화 + `matrix3d` DOM 오버레이로 **자동 전환**된다. 같은 코드가 전 브라우저에서 돌고 상호작용도 유지된다. 폴리필 한계: `textarea` 내부 스크롤 미반영, `contenteditable` 캐럿/선택 미렌더, 동적 스타일시트 수동 무효화 필요, `:visited` 불가. **매 프레임 재캡처는 비싸다 — 무효화 시점에만 갱신하도록 짜라.** 2D 전용 이펙트라면 폴리필 없이 능력 감지 분기(§7c)로 간다. ## 4. 현재 API 표면 `` 를 선언하고, 그릴 요소를 **직계 자식**으로 둔다. 손자는 그릴 수 없다. | 용도 | 호출 | |---|---| | 2D 그리기 | `ctx.drawElementImage(el, dx, dy[, dw, dh])` → `DOMMatrix` | | 2D 크롭 | `ctx.drawElementImage(el, sx, sy, sw, sh, dx, dy[, dw, dh])` | | WebGL 업로드 | `gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA8, el)` | | WebGPU 업로드 | `device.queue.copyElementImageToTexture({source: el}, {destination: {texture}, width, height})` | | 갱신 훅 | `canvas.onpaint = (e) => {}` — `e.changedElements`로 바뀐 요소만 온다 | | 킥스타트 / 프레임 루프 | `canvas.requestPaint()` | | 3D 위치 동기화 | `canvas.getElementTransform(el, screenSpaceMatrix)` → `DOMMatrix` | | 워커 전송 | `canvas.captureElementImage(el)` → `ElementImage` (Transferable) | | three.js | `material.map = new THREE.HTMLTexture(element)` (r184+) | ### 이 이름을 본다면 낡은 자료다 — 따라 쓰지 마라 | 낡은 이름 | 현재 | |---|---| | `canvas place element`, `placeElement()` | 제안명 `html-in-canvas` | | `drawElement()`, `drawHTMLElement()`, `drawHTML()` | `drawElementImage()` | | `texElement2D()` | `texElementImage2D()` | | `copyElementImage()` | `copyElementImageToTexture()` | | `setHitTestRegions()` | **폐기.** 반환 `DOMMatrix`를 `style.transform`에 반영 | ## 5. 필수 계약 3가지 ### ① 반환 `DOMMatrix`를 매 프레임 `style.transform`에 반영한다 ```js const t = ctx.drawElementImage(el, x, y); el.style.transform = t.toString(); // 이 줄이 없으면 클릭이 전부 어긋난다 ``` 히트테스트·포커스·탭 이동·find-in-page는 전부 **DOM 위치**를 본다. 그린 위치와 DOM 위치를 맞추는 건 개발자 책임이다. 소스 요소의 CSS transform은 **그리기에서 무시**되므로 이 대입이 캔버스 그림을 바꾸지 않는다 — 무한 루프는 생기지 않는다. 3D에서는 `canvas.getElementTransform(el, screenSpaceMatrix)`가 같은 역할을 하고, three.js는 `InteractionManager.update()`가 대신 해준다. ### ② WebGL/WebGPU는 try/catch로 신·구 시그니처를 모두 지원한다 2026년 상반기에 두 시그니처가 모두 바뀌었다. **Chrome 공식 블로그 예제가 이미 구버전이다.** WICG 공식 데모조차 양쪽을 지원한다. ```js try { gl.texElementImage2D(gl.TEXTURE_2D, gl.RGBA8, el); } // 현재 catch (e) { gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, // 구버전 gl.UNSIGNED_BYTE, el); } try { device.queue.copyElementImageToTexture( // 현재 { source: el }, { destination: { texture }, width, height }); } catch (e) { device.queue.copyElementImageToTexture(el, width, height, { texture }); } // 구버전 ``` ### ③ 캔버스 안에 스크롤 영역을 넣지 마라 캔버스 안 콘텐츠는 JS로 그려진다. **컴포지터 스레드 스크롤·애니메이션을 잃고** 스크롤이 메인 스레드에 묶여 끊긴다. 캔버스 안을 스크롤시키지 말고 **캔버스 전체를 페이지와 함께 스크롤**시켜라. ## 6. 그리지 않는 것 픽셀을 읽을 수 있으므로, 저자가 원래 못 보던 정보는 **아예 안 그려진다.** 검게 보인다고 버그가 아니다. | 안 그려짐 | 비고 | |---|---| | cross-origin `