들어가며
얼마 전 유튜브 노마드 코더 채널에 재미있는 영상이 하나 게시되었습니다. 제목부터가 "이게 진짜 HTML이라고?"인데, 썸네일과 제목만 보면 뭔가 새로운 CSS트릭이 생겼나 싶지만 실은 크롬에서 신규 출시한 새로운 API,
<canvas> 안에 진짜 HTML을 그릴 수 있게 하는 API에 대한 소개입니다.
HTML-in-Canvas
지금까지의 캔버스와 HTML
<canvas>
이 브라우저는 캔버스를 지원하지 않습니다.
</canvas>
위 코드에서 텍스트 부분은 화면에 나오지 않는다는 것을 아실 겁니다. 지금까지 캔버스 내부의 마크업은 fallback content, 즉 "캔버스를 못 그리는 브라우저를 위한 대체 콘텐츠"일 뿐이었습니다. 캔버스는 비트맵 드로잉 표면이고, 그 안의 DOM은 애초에 렌더링 대상이 아니었죠.
그래서 캔버스나 WebGL로 뭔가 화려한 걸 만들려는 순간 우리는 늘 같은 벽에 부딪혔습니다. 텍스트입니다. 줄바꿈을 직접 계산하고, 폰트 메트릭을 직접 재고, RTL은 포기하고, 스크린리더는 논외였죠. html2canvas 같은 라이브러리로 우회해봐도 결국 "스크린샷을 찍어 붙이는" 방식이라 인터랙션이 죽습니다.
HTML-in-Canvas는 바로 이 벽을 부수려는 시도입니다.
HTML-in-Canvas가 하는 일
핵심 아이디어는 단순합니다. 캔버스 안의 DOM을 진짜로 레이아웃시키고, 그 렌더링 결과를 캔버스에 그린다. 그런데 원본 DOM은 여전히 살아있어서 포커스도 되고 스크린리더도 읽습니다.
API는 세 개의 프리미티브로 구성됩니다.
layoutsubtree 속성
캔버스의 자식 요소를 레이아웃과 히트 테스트 대상으로 편입시킵니다. 일반 DOM처럼 동작하지만, 그려지기 전까지는 보이지 않습니다.
drawElementImage() (2D) / texElementImage2D() (WebGL·WebGPU)
자식 요소를 현재 트랜스폼을 적용해 캔버스에 그립니다. WebGL 쪽은 아예 텍스처로 업로드됩니다. 즉, 프래그먼트 셰이더가 내가 만든 <div>의 픽셀을 만질 수 있다는 뜻입니다.
paint 이벤트
자식 요소가 변할 때마다 발생합니다. 여기서 캔버스를 다시 그려주면 됩니다. 이때 drawElementImage()가 돌려주는 트랜스폼을 원본 요소에 다시 적용해, 그려진 좌표와 실제 DOM 좌표를 맞춰주는 것이 관건입니다. 이 동기화가 어긋나면 클릭과 포커스가 눈에 보이는 위치를 벗어납니다.
프론트엔드 관점에서는?
Three.js는 이미 HTMLTexture PR이 병합되어 정식 릴리스에 포함됐습니다.
PlayCanvas도 공식 문서에 항목을 두고 device.supportsHtmlTextures로 지원 여부를 감지하도록 안내하고 있는데, 이쪽은 아직 WebGL 백엔드만 지원합니다.
프론트엔드 관점에서 매력적인 지점은 명확합니다.
- 텍스트 레이아웃을 브라우저에 위임할 수 있습니다. 줄바꿈, RTL, 폰트 폴백 — 전부 이미 레이아웃 엔진이 하는 일입니다. 차트 라이브러리들이 축·범례·툴팁 때문에 폰트 측정을 직접 구현해온 걸 생각하면 꽤 큰 절약입니다.
- 캔버스 안 UI의 접근성이 따라옵니다. 이미 DOM에 있으니까요.
- WebGL 씬의 HUD를 진짜
<input>으로 만들 수 있습니다. z-index 해킹도, 오버레이 정렬 지옥도 없이. - CSS로 만든 UI에 셰이더를 먹일 수 있습니다. 이제 안된다고 선을 긋던것도 도전을 해볼 수 있게 됩니다. 이를테면, 리퀴드 글래스 같은것 말이죠.
리퀴드 글래스
애플이 리퀴드 글래스를 발표했을 때 웹 진영에서 나온 고민은 "이거 웹에서 어떻게 만들지?"였습니다. 예전의 글래스모피즘은 backdrop-filter: blur() 한 줄이면 끝났지만, 리퀴드 글래스의 핵심은 블러가 아니라 굴절입니다. 유리 가장자리에서 뒤쪽 픽셀이 실제로 휘어야 하는데, CSS에는 그런 게 없습니다.
그래서 나온 우회로가 SVG feDisplacementMap입니다. 변위 맵을 만들어 픽셀을 밀어내는 방식으로 굴절을 흉내내는 건데, 문제는 여기서부터입니다.
backdrop-filter에 SVG 필터를 물리는 건 크로미움에서만 동작합니다. 사파리와 파이어폭스는 속성을 받아들이고는 SVG 부분만 조용히 무시하고 밋밋한 블러만 남깁니다. 웹의 리퀴드 글래스가 크롬에서는 유리인데 다른 데서는 성에인 이유죠.- 형태 제약이 있습니다. 대개 고정 크기의 둥근 사각형과 원 정도만 됩니다. 애플 것은 텍스트와 아이콘 같은 임의의 모양에도 적용되고요.
- 라이브 배경 대신 배경을 복제해서 필터를 거는 우회법도 있는데, 스크롤할 때 원본과 복제본이 따로 놀면서 떨리고, DOM을 통째로 복제하다 보니 ID 중복·ARIA 참조 깨짐·포커스 트랩 같은 접근성 문제가 따라옵니다.
엄청 골치아프죠.
HTML-in-Canvas는 여기에 다른 각도의 답을 냅니다. DOM을 텍스처로 올려버리면 굴절은 셰이더에서 직접 계산하면 되는 일이 됩니다. 형태 제약도 없습니다. 물론 정직하게 말하면 크로미움 전용이라는 문제는 그대로입니다. 우회로가 바뀌었을 뿐 브라우저 벽은 여전하다는 뜻이죠.
그래서 평가는?
노마드 코더는 마지막에 아직 프로덕션에 적용하지는 못한다고 분명히 선을 긋습니다. 아직 정식 버전이 아니라 초안 단계이기에 지금 보이는 모든 것이 바뀔 수 있다고요. 또한, 직접 테스트해보려면 사용자가 플래그를 켜서 기능을 손수 활성화해야 하는 상태라는 점도 언급합니다.
그럼에도 이 기능이 정식으로 채택되길 바란다는 소망으로 영상을 끝냅니다. 이유가 재미있습니다. 이게 웹을 다시 재미있게 만들어줄 것 같다는 겁니다. 마치 플래시처럼요.
그런데 커뮤니티의 반응은 갈립니다. “우와 신기하다"와 "이거 괜찮은 거 맞아?”같은 반응이 많아 보여요. 해외 커뮤니티의 반응들도 클로드와 함께 정리해봤습니다.
1. "플래시가 돌아왔다"
가장 많이 나온 농담이자, 농담만은 아닌 지적입니다. 웹 페이지를 통째로 캔버스에 밀어 넣는 흐름 자체에 대한 거부감이죠. 어떤 댓글은 여기에 "훨씬 축소되고 멍청해지고 제대로 동작도 안 하는 형태로"라고 덧붙였습니다.
영상에서 노마드 코더는 플래시는 웹이 재미있던 시절의 상징이었는데, 여기서 플래시는 웹이 접근성도 개방성도 잃었던 시절의 상징입니다. 같은 비유를 서로 정반대로 쓰고 있다는 게, 이 기술을 둘러싼 온도차를 가장 잘 보여줍니다.
2. 표준화 절차에 대한 불신
가장 뜨거운 쟁점입니다. 지지 측 논리는 "표준화는 원래 구현이 선행되어야 하고, WHATWG 이슈에 Mozilla와 Apple 사람들도 참여 중"이라는 것입니다.
반대 측은 이렇게 받아칩니다 — 이건 표준이 아니라 워킹그룹 저장소에 적힌 낙서고, 작성자와 편집자가 전부 구글 사람이라는 것. 실제로 Mozilla와 WebKit 모두 공개적으로 부정적인 포지션을 냈습니다. Mozilla는 "이 제안은 여러 문제를 하나의 해법으로 풀려 하는데, 각 문제에 대해 이게 옳은 해법이 아니거나 어떤 경우엔 잘못된 방향으로 가는 한 걸음"이라는 취지로 반대했습니다.
3. "이거 구글 독스 때문 아니야?"
가장 냉소적이면서 가장 그럴듯한 해석입니다. WebKit 논의 기록에 구글 독스가 이 API의 유력한 채택 후보로 언급되는데, 구글 독스는 2021년에 이미 캔버스 기반 렌더링으로 갈아탄 서비스입니다. 그러니 "캔버스로 갔다가 너무 힘들어서 브라우저 쪽에 HTML 지원을 밀어 넣는 것 아니냐, 나머지는 사후 정당화 아니냐"는 의심이 나옵니다.
4. 보안 우려
캔버스로 브라우저 크롬(주소창, TLS 자물쇠까지)을 통째로 그려서 가짜 URL을 보여주면 어떻게 되냐는 질문입니다. 다만 이건 이미 존재하는 "browser-in-browser" 공격이고 DOM만으로도 가능하다는 반박이 곧바로 달렸습니다.
5. "기대만큼은 아니다"라는 온건한 실망
개인적으로 가장 새겨들을 만한 반응입니다. 입력 매핑이 여전히 수동에 가깝다는 지적인데, raw API를 직접 쓰면 이런 모양이 됩니다.
<canvas id="canvas" layoutsubtree>
<form id="myForm">
<input type="text" placeholder="Type here...">
</form>
</canvas>
<script>
const ctx = canvas.getContext('2d');
canvas.onpaint = () => {
ctx.reset();
const transform = ctx.drawElementImage(myForm, 100, 50);
// 실제 DOM 위치를 그려진 위치와 동기화
myForm.style.transform = transform.toString();
};
</script
마지막 두 줄이 문제입니다. 그려진 좌표와 실제 DOM 좌표를 개발자가 직접 맞춰줘야 하고, 이게 어긋나는 순간 클릭과 포커스가 눈에 보이는 위치를 벗어납니다. 저 정도 예제야 한 줄로 끝나지만, 회전하고 스케일되고 곡면에 투영되는 상황이라면 이야기가 달라지죠. 사실상 단순한 용도에서만 제대로 동작한다는 말이 나오는 이유입니다. HTML을 곡면에 투영하는 것 같은, 정말로 하고 싶었던 일에는 아직 못 미친다는 거죠.
마치며
정리하면 이렇습니다. 기술적으로는 매력적이고, 표준화 측면에서는 의심스럽고, 실무적으로는 시기상조입니다.
그런데 이 세 문장이 서로 모순되지 않는다는 게 이 API의 묘한 지점입니다. 캔버스 안 텍스트 레이아웃과 접근성 문제는 실재하는 문제이고, 이 API는 그걸 꽤 우아하게 풉니다. 동시에 이게 정말 그 문제를 풀기 위해 만들어진 건지, 아니면 특정 제품의 곤란을 브라우저 레벨에서 해결하려는 건지 누군가는 의심하고 있죠.
결국 갈리는 지점은 "이 기능이 좋냐"가 아니라 "웹이 어느 방향으로 가는거냐"인 것 같습니다. 누군가는 웹이 다시 재미있어지길 바라고, 반대하는 누군가는 웹이 열려 있길 바랍니다. 어느 한쪽이 옳다 그르다 판단하기에는 어려운 문제죠.

