- iframe 내에서 클릭 이벤트가 발생했을 때, 부모 엘리먼트에 적용된 클릭 이벤트가 동작하지 않았다. ( 버블링 미발생 )
- 이유가 무엇인지 알아보고 해결 방법을 찾아보자.
1. iframe에서 버블링이 발생하지 않는 이유
- 일반적으로 이벤트 버블링은 단일 문서 트리에서만 발생한다. iframe으로 생성된 엘리먼트는 별도의 문서 트리를 가지기 때문에 이벤트 전파가 동작하지 않습니다. ( 이러한 결과는 생각해보면 당연한 것 같기도..? )
- 관련 내용 참고하기
function App() {
const srcDoc = `
<!DOCTYPE html>
<html>
<button>클릭</button>
</html>
`;
const handleClick = () => {
console.log("부모 클릭 이벤트");
};
return (
<div className="App" onClick={handleClick}>
{}
<iframe srcDoc={srcDoc} />
</div>
);
}
export default App;
2. iframe에서 이벤트 버블링 발생시켜보기
1단계: iframe 내부 widow에 클릭 이벤트 적용하기
iframeElement.contentWindow
를 통해 iframe 하위에 있는 전역 widnow에 접근할 수 있습니다. ( 관련 내용 )
contentWindow
에 클릭 이벤트를 적용하고, 이벤트 핸들러에서 커스텀 이벤트를 생성합니다.
- 필요에 따라 커스텀 이벤트를 만들 때 좌표값을 계산하여 포함시킬 수 있습니다. ( 나중에 좌표를 사용할 경우 추가 )
2단계 클릭 이벤트 핸들러에서 자기 자신에게 이벤트를 dispatch하기
- 위에서 생성한 커스텀 이벤트를 자기 자신에게 dispatch 합니다.
- 이 방법을 통해 iframe 엘리먼트에서 클릭 이벤트가 발생하고, 부모 엘리먼트까지 이벤트가 전파됩니다. ( 버블링 발생 )
3. 코드로 살펴보기
- 유틸 훅으로 분리하여
useIFrameClickEventBubbling
를 구현한 결과 입니다.
import { useEffect } from "react";
function useIFrameClickEventBubbling(iframeElement) {
useEffect(() => {
if (!iframeElement) {
return;
}
const iframeWindow = iframeElement.contentWindow;
if (!iframeWindow) {
return;
}
const handleClick = (event) => {
const { top, left } = iframeElement.getBoundingClientRect();
const customEvent = new MouseEvent("click", {
bubbles: true,
clientX: event.clientX + left,
clientY: event.clientY + top,
});
iframeElement.dispatchEvent(customEvent);
};
iframeWindow.addEventListener("click", handleClick);
() => {
if (iframeWindow) {
iframeWindow.removeEventListener("click", handleClick);
}
};
}, [iframeElement]);
}
export default useIFrameClickEventBubbling;
import { useRef, useState, useEffect } from "react";
import useIFrameClickEventBubbling from "./useIFrameClickEventBubbling";
function App() {
const ref = useRef(null);
const [element, setElement] = useState();
const srcDoc = `
<!DOCTYPE html>
<html>
<button>클릭</button>
</html>
`;
const handleClick = () => {
console.log("부모 클릭");
};
useEffect(() => {
if (ref.current) {
setElement(ref.current);
}
}, [setElement]);
useIFrameClickEventBubbling(element);
return (
<div className="App" onClick={handleClick}>
<iframe srcDoc={srcDoc} ref={ref} />
</div>
);
}
export default App;
마치며
- 코드 자체는 간단하지만, 이 접근 방식은 다양한 형식으로 변형하여 여러 문제를 해결할 수 있지 않을까? 생각이 들었다.
👍👍