---
title: "iframe 안 모달의 딤을 부모 화면 전체로, 조상 z-index:0 이 막는다"
description: "iframe 안에서 모달이 뜰 때 부모 화면 전체를 딤 처리하려다 조상의 z-index:0 stacking context 에 막힌 원인과, 딤이 켜진 동안만 풀어 주는 해법을 정리했습니다."
date: 2026-10-13
updated: 2026-10-13T12:12:27.872Z
tags: [css, iframe, postmessage, stacking-context]
canonical: https://blog.wooncloud.com/posts/iframe-modal-dim-stacking-context
---

앱 한가운데 영역에 다른 서비스를 iframe 으로 임베드했는데, iframe 안에서 모달이 뜨면 딤(backdrop)이 iframe 영역만 덮었습니다. 화면 전체가 어두워져야 자연스러운데 말이죠.

iframe 안의 `position: fixed` 는 iframe 의 뷰포트 기준이라, 자식 문서는 자기 밖을 그릴 수 없습니다.

## 흔한 해법은 이렇습니다

부모가 대신 딤을 깔아 주면 됩니다.

1. 자식이 `postMessage({type: "modal", open: true})` 로 부모에게 알립니다.
2. 부모가 `position: fixed; inset: 0; z-index: 9000` 딤을 `body` 에 깝니다.
3. iframe 컨테이너만 `position: relative; z-index: 9001` 로 딤 위에 올립니다.
4. 두 딤의 색을 맞추면 한 장처럼 보입니다.

그런데 실제로 해 보니 iframe 까지 딤 아래로 깔려서 모달을 클릭할 수 없었습니다.

![조상 요소의 z-index:0이 stacking context를 만들면 iframe의 z-index 9001은 그 안에서만 유효해 바깥 딤 9000에 가려지지만, 조상의 z-index를 auto로 풀면 iframe이 딤 위로 올라온다.](/images/posts/iframe-modal-dim-stacking-context/04f29ff1-94f9-421f-b01c-0b7169a0b66a.webp)

## 왜 9001 이 안 먹을까요?

원인은 조상 레이아웃 컨테이너에 박혀 있던 `position: relative; z-index: 0` 한 줄이었습니다. 이 한 줄이 stacking context 를 만듭니다.

**stacking context 안의 z-index 는 그 컨텍스트 안에서만 비교됩니다.** 자식을 9001 로 올려도 바깥에서는 조상의 `0` 으로 취급되니, `body` 의 9000 을 넘지 못합니다.

`z-index: 0` 은 레이아웃 CSS 에 생각보다 흔하게 들어 있어서 놓치기 쉽습니다.

![iframe 컨테이너의 z-index 9001은 z-index 0인 조상 .layout 안에서만 유효해서 바깥에서는 딤 9000에 밀리고, 딤이 켜진 동안 조상의 z-index를 auto로 풀면 9001이 9000을 넘는다.](/images/posts/iframe-modal-dim-stacking-context/b95505b6-76a2-4453-a0a0-7ec5ec15544a.svg)

## 먼저 재고, 딤이 켜진 동안만 풀어 줍니다

추측하지 말고 먼저 재는 게 빠릅니다. iframe 부터 `<html>` 까지 조상을 올라가며 stacking context 를 만드는 속성을 덤프합니다.

찾아볼 것은 `position` 과 `z-index` 조합, flex/grid 자식의 `z-index`, `opacity < 1`, `transform`, `filter`, `mask`, `contain`, `isolation`, `will-change` 입니다.

범인을 찾았으면 딤이 켜진 동안만 그 조상의 인라인 `z-index` 를 `auto` 로 풀고, 끌 때 원래 인라인 값을 우선순위까지 복원합니다. CSS 파일은 건드리지 않습니다.

```js
let dim = null;
const save = (el, p) => ({ v: el.style.getPropertyValue(p), pr: el.style.getPropertyPriority(p) });
const restore = (el, p, s) => s.v ? el.style.setProperty(p, s.v, s.pr) : el.style.removeProperty(p);

function setDim(open) {
  if (!open) {
    if (!dim) return;
    dim.el.remove();
    restore(dim.box, "z-index", dim.boxZ);
    restore(dim.box, "position", dim.boxPos);
    if (dim.anc) restore(dim.anc, "z-index", dim.ancZ);
    dim = null;
    return;
  }
  if (dim) return; // 멱등
  const box = iframe.parentElement;
  const anc = box.closest(".layout"); // z-index:0 으로 stacking context 를 만드는 조상
  const el = Object.assign(document.createElement("div"), { style: "position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.5)" });
  dim = { el, box, anc, boxZ: save(box, "z-index"), boxPos: save(box, "position"), ancZ: anc && save(anc, "z-index") };
  document.body.append(el);
  box.style.setProperty("position", "relative");
  box.style.setProperty("z-index", "9001");
  if (anc) anc.style.setProperty("z-index", "auto");
}
```

저장은 off 에서 on 으로 넘어갈 때만 합니다. 모달이 겹치거나 톤이 바뀌어서 다시 불려도 원래 값이 덮이지 않도록 멱등하게 짠 겁니다.

## 해제 경로는 한 함수로 모읍니다

자식이 `open: false` 를 못 보내는 경우가 많습니다. 모달이 열린 채로 리로드되거나 화면이 바뀌면 딤이 영영 남죠.

그래서 해제 경로를 한 함수로 모으고 세 군데에서 부릅니다.

- iframe 이 새 문서로 `load` 될 때(리로드, src 교체)
- 부모 화면이 전환될 때
- 자식이 다시 `ready` 를 보낼 때

안전장치도 하나 둡니다. 딤을 켠 직후 `document.elementFromPoint` 로 iframe 의 보이는 영역 여러 지점을 찍어 봅니다. 어디서도 iframe 이 최상단이 아니면 딤을 걷습니다. 레이아웃이 나중에 바뀌어도 "모달을 클릭할 수 없다"는 최악만은 피하려는 용도입니다.

## 덤, CSP 가 막힌 same-origin iframe 에 메시지 흉내 내기

테스트하다가 막힌 부분입니다. 수신부가 `e.source === iframe.contentWindow` 를 검사하면, 부모에서 `postMessage` 를 불러서는 검증이 안 됩니다. `e.source` 가 부모 창이 되기 때문입니다.

자식 문서의 CSP(`script-src 'self'`)가 `eval` 을 막으면 자식 쪽에서 코드를 돌리기도 어렵습니다.

이럴 땐 같은 iframe 을 `about:blank` 로 이동시킵니다. 부모가 연 about:blank 는 부모의 출처를 물려받고, `contentWindow`(WindowProxy)도 그대로라 `e.source` 검사를 통과합니다. 그 상태에서 `iframe.contentWindow.eval('parent.postMessage(...)')` 로 메시지를 보내면 됩니다.

about:blank 는 배경이 투명해서 스크린샷에 부모 딤이 비쳐 보이는데, 정상입니다.

## 참고

- [MDN Stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context)
- [MDN Window.postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
- [MDN Document.elementFromPoint](https://developer.mozilla.org/en-US/docs/Web/API/Document/elementFromPoint)
- [MDN CSSStyleDeclaration.setProperty](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/setProperty)
