웹에서 동작하는 3D 엔진들 (예: three.js 또는 babylon.js)로 제작된 3D 콘텐츠는 일반적으로 캔버스를 사용하여 GPU 제어와 그래픽 컨텍스트의 반복 렌더링을 처리합니다. 이 때문에 HTML 페이지의 전체 클라이언트 영역을 사용하는 경우가 많고 따라서 웹 브라우저에서 별도의 단위로 처리되어야 합니다.
iframe은 웹 브라우저에서 별도의 컨텍스트 관리를 제공하기 때문에 이 용도에 맞는 적합한 tag입니다.
한편 iframe 요소의 "src" 속성은 iframe 내부에 표시될 콘텐츠의 URL을 지정하는 데 사용되는 데, 이를 위해 별도의 HTML 파일이 필요하기 때문에 "src" URL이 없이는 iframe 콘텐츠를 사용하는 것이 쉽지 않습니다.
그러나 웹에서 하나의 기사 또는 블로그 포스트와 같은 페이지에서 간단한 시연이나 튜토리얼 같은 목적으로 3D 콘텐츠를 제공할 경우, 각각의 HTML 및 JavaScript 파일을 생성하는 것은 목적에 비해 지나치게 번거롭습니다.
1. 하나의 HTML 파일에 iframe과 문서 내용을 모두 담기
만약 하나의 HTML 파일에 iframe과 그 내용 문서를 동시에 삽입해야 할 경우, HTML과 자바스크립트를 함께 사용하는 몇 가지 트릭을 사용해야 할 수 있습니다.
다음의 예에서 볼 수 있는 것처럼, src URL을 사용하지 않고 iframe 내용을 직접 삽입하려면 iframe 요소를 만들고, 자바스크립트를 사용하여 iframe의 document 객체를 조작하여 동적으로 내용을 설정할 수 있습니다.
<iframe id="my-iframe"></iframe>
<script type="javascript">
const iframe = document.getElementById('my-iframe');
const iframeDocument = iframe.contentWindow.document;
iframeDocument.open();
iframeDocument.write('<html><body><h1>Hello, world!</h1></body></html>');
iframeDocument.close();
</script>
이 예시에서는 먼저 id가 my-iframe인 iframe 요소에 대한 참조를 가져옵니다. 그런 다음 iframe의 contentWindow 속성을 사용하여 해당 문서에 대한 참조를 가져와서 다른 HTML 문서와 마찬가지로 iframe의 내용을 조작할 수 있습니다.
그 다음 문서 객체의 open() 메서드를 사용하여 문서를 쓰기 위해 엽니다. write() 메서드를 사용하여 표시하려는 HTML 콘텐츠를 삽입합니다. 마지막으로 close() 메서드를 사용하여 문서를 닫고 동적 콘텐츠 할당을 완료합니다.
이 방법을 사용하면 src 속성에서 정적 URL을 지정하지 않고도 하나의 HTML 문서 내에서 iframe 요소에 동적 콘텐츠를 생성하고 할당할 수 있습니다.
이 접근 방식이 모든 웹 브라우저에서 지원되는 것은 아니므로 대상 브라우저에서 예상대로 작동하는지 충분히 테스트해야 합니다.
2. iframe.content에 HTML 문자열 작성하기
JavaScript를 사용하여 iframe에 동적으로 콘텐츠를 삽입할 때, </script>, </head>, </body>, </html> 등의 종료 tag가 포함된 코드에서 문제가 발생할 수 있습니다. 이 tag들은 문자열 내에 있더라도 브라우저에서 해당 tag 블록의 끝으로 해석될 수 있으므로 HTML 구문 분석기가 오동작하고 스크립트가 제대로 실행되지 않을 수 있습니다.
이 문제를 해결하기 위해, 다음과 같이 종료 tag를 여러 문자열 리터럴로 분리할 수 있습니다:
const scriptContent =
"<script>" +
"console.log('Hello from inside the iframe!')" +
"</scr" + "ipt>";
또는 "</script>" 태그가 스크립트 블록의 끝으로 해석되는 것을 방지하기 위해 escape 기호를 사용할 수 있습니다. 이를 위해 해당 태그의 슬래시 / 문자를 유니코드 \u002f로 대체할 수 있습니다.
const scriptContent =
"<script>" +
"console.log('Hello from inside the iframe!')" +
"<\u002fscript>";
이 예시에서는 </script> 태그의 슬래시 / 문자를 해당 문자의 유니코드 코드 포인트인 \u002f로 대체했습니다. 이렇게 하면 브라우저가 해당 태그를 스크립트 블록의 끝으로 해석하는 대신 문자열 리터럴의 일부로 올바르게 구문 분석할 수 있습니다.
< 문자를 HTML 구문 분석기가 새로운 tag의 시작으로 해석하는 것을 방지하는 다른 방법은 해당 문자를 HTML 엔티티로 대체하는 것입니다. 이 기술은 <, >, &와 같이 특별한 의미가 있는 HTML 문자를 이스케이핑하는 데 일반적으로 사용됩니다.
다음은 이 기법을 사용하여 iframe 문서에 스크립트 콘텐츠를 삽입하는 방법의 예시입니다:
const scriptContent =
"<script>" +
"console.log('Hello from inside the iframe!')" +
"</script>".replace(/</g, "<");
이 예시에서는 replace() 메서드를 사용하여 스크립트 콘텐츠에서 모든 < 문자를 다시 <로 대체하여 HTML 구문 분석기가 새로운 tag의 시작으로 해석하는 것을 방지합니다.
이러한 기법들 중 하나를 사용하여 스크립트 콘텐츠에서 문자를 이스케이핑하면 HTML 구문 분석 오류를 일으키지 않고 안전하게 iframe 문서에 삽입할 수 있습니다.
3. 자바스크립트에서 여러 줄 문자열 작성하기
<script> 요소 내에서 여러 줄로 스크립트 문자열을 작성할 때, 각각의 문자열들이 구문 오류 없이 올바르게 처리 되게 하려면 다시 몇가지 기법을 사용해야 합니다. 다음은 여러 줄의 스크립트 콘텐츠를 HTML에서 감싸는 여러 가지 방법입니다:
3.1. backslash를 사용하여 줄 바꿈 문자 이스케이핑하기:
<script>
var message = "This is a \
multi-line \
message.";
</script>
이 예시에서는 문자열 내에서 줄 바꿈 문자를 이스케이핑하기 위해 \를 사용하고 있습니다. 이렇게 하면 HTML 문서에서 여러 줄에 걸쳐 문자열을 작성할 수 있으며, JavaScript 인터프리터에서 전체 문자열을 하나의 문자열로 해석할 수 있습니다.
3.2. 문자열 연결하기:
<script>
var message = "This is a " +
"multi-line " +
"message.";
</script>
이 방법은 여러 줄에 걸쳐 문자열을 작성하면서도, 문자열을 이어붙여서 하나의 문자열로 만듭니다. 이 방법은 이스케이핑 백슬래시를 사용하는 방법보다 코드가 더 깔끔하지만, 각 줄의 따옴표(")를 맞추어 주어야 하므로 번거로울 수 있습니다.
3.3. 템플릿 리터럴 사용하기:
<script>
var message = `This is a
multi-line
message.`;
</script>
이 예시에서는 backtick(`)을 사용하여 템플릿 리터럴을 만들어 여러 줄의 문자열을 만들었습니다. 이 방법은 이스케이핑이나 연결이 필요하지 않으므로 더 자연스러운 방식으로 여러 줄의 문자열을 작성할 수 있습니다.
여러 가지 방법으로 <script> 요소에서 여러 줄의 문자열 콘텐츠를 만드는 데 사용할 수 있지만, 가장 적합한 방법은 프로젝트의 특정 요구 사항과 대상 브라우저의 특성에 따라 다를 수 있습니다.
4. 모두 합쳐보기
다음은 파일을 분리하지 않고 iframe에서 three.js를 사용하여 3D 콘텐츠를 표시하는 예시입니다.
<iframe id="my-iframe" src="about:blank" width="100%" height="500px" loading="lazy"></iframe>
<script type="text/javascript">
const sc = 'script', hd = 'head', st = 'style', bd = 'body', ht = 'html';
var iframe = document.getElementById('my-iframe');
var iframeDocument = iframe.contentWindow.document;
var htmlText =
`<html>
<head>
<meta charset = 'utf-8'>
<title>Three.js Single Page Example</title>
<style>
body { margin: 0; }
</${st}>
</${hd}>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></${sc}>
<script>
window.addEventListener("load", () => {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x00aaff);
renderer.setSize(window.innerWidth-1, window.innerHeight-1);
document.body.appendChild(renderer.domElement);
const light = new THREE.DirectionalLight( 0xffffff, 1.0 );
light.position.set(0, 0.5, 1.0);
scene.add( light );
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshLambertMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 2;
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
});
</${sc}>
</${bd}>
</${ht}>`;
iframeDocument.open();
iframeDocument.write(htmlText);
iframeDocument.close();
</script>
위의 예시에서는 템플릿 리터럴을 사용하면서 동시에 ${} 구문의 치환을 사용해 좀더 간결하게 구문 오류 없이 문자열을 처리하도록 iframe과 script를 하나의 HTML 문서에 포함되도록 처리했습니다.
HTML 호환성
앞서서 트릭이라고 했지만, 사실 <iframe> 요소는 HTML 사양의 표준 요소이며 HTML 문서에 직접 포함될 수 있습니다. 또한 사용자 상호작용 또는 기타 요인에 따라 iframe에 동적으로 콘텐츠를 로드해야하는 경우 JavaScript를 사용하여 프로그래밍 방식으로 iframe을 삽입해야하는 경우가 종종 있습니다.
실제로 프로그래밍 방식으로 iframe을 삽입하는 것은 최신 웹 개발에서 흔한 기술입니다. 특히 Single Page Applications (SPAs) 및 기타 동적 웹 애플리케이션에서 사용됩니다. 이를 통해 개발자는 새로운 페이지의 로딩이나 새 HTTP 요청 없이 iframe에 콘텐츠를 동적으로 로드할 수 있으므로 성능 및 사용자 경험을 향상시킬 수 있습니다.
결론
이상에 소개한 여러 기법을 활용하여 iframe과 3D 콘텐츠를 모두 포함하는 하나의 간결한 HTML 파일을 만들 수 있습니다. 이를 사용하면 3D 콘텐츠를 독립 실행형 파일로 배포하거나 외부 파일을 참조하지 않고 하나의 웹 페이지 내에서 3D 콘텐츠를 임베드 할 수 있습니다.
그러나 HTML 파일에 너무 큰 3D 모델을 임베드하면 파일 크기와 로딩 시간이 크게 증가할 수 있다는 점을 염두에 두어야 합니다. 이는 특히 느린 인터넷 연결이 있는 모바일 장치에서 사용자 경험에 부정적인 영향을 미칠 수 있습니다. 이를 완화하기 위해 3D 콘텐츠의 크기를 최적화하거나 lazy loading 또는 progressive loading과 같은 다른 접근 방식을 고려해야 할 수도 있습니다.
'Three.js' 카테고리의 다른 글
Three.js를 하나의 HTML 파일로 실행하는 간단한 Boilerplate 예제 (0) | 2023.04.08 |
---|---|
구의 표면에 점들을 균등하게 분포시키는 방법 (Three.js) (0) | 2023.04.05 |