본문 바로가기

프로그래밍 언어/JavaScript

iframe 확대/축소 구현 (with Panzoom)

주의! 크로스 도메인(CORS)에 걸리지 않는 경우만 가능합니다...!!

※ 아직 해결하지 못한 사항

  1. iframe 범위를 벗어났을 때 되돌아오게 하는 처리 - 확대/축소하면서 범위를 벗어나는 경우 원래대로 돌아오는 위치가 기대하는 것처럼 되지 않는다...
  2. 크로스 도메인 - iframe 내부 요소의 너비, 높이를 알아내서 내부 요소를 iframe 크기에 맞게 scale을 조절시키는 게 이 확대/축소 구현의 관건인데 크로스 도메인 정책 때문에 현재 도메인과 iframe 내부에 호출하는 도메인이 다를 경우 처리가 불가능하다…!

 

■ 기능 구현까지의 서사

더보기

1) iframe 확대/축소 기능의 필요성

 하이브리드 모바일 앱을 구축하면서 공지사항을 구현하는데 상세 내용으로 웹 페이지를 보여줘야 해서 iframe을 이용하게 되었다.

 상세 내용을 스크롤 없이 iframe 크기에 맞춰 모두 보여주기 위해서

 iframe 가로 사이즈 안에 상세 내용이 모두 보이게끔 iframe 내부 요소를 축소하고, 이 iframe 내부 요소의 높이에 맞게 iframe의 높이를 조절했는데 상세 내용이 웹을 기준으로 작성되어 있다보니 표나 사진이 있을 경우 모바일에서 보기엔 글자 크기가 너무 작아서 확대/축소 기능이 필수적이었다.

 

2) iframe 내부 확대/축소 기능 - 방법을 찾지 못함

iframe의 내부 요소 자체를 확대/축소하는 방법은 아직 찾지 못했다..

그래서 iframe 내부 요소를 iframe 크기에 딱 맞춰서 내부 html에 scale을 적용하고

iframe 자체를 확대/축소하면 되지 않을까 하고 방법을 찾아봤다.

 

3) iframe 자체를 확대/축소 - Panzoom과의 만남.. 그리고 문제점

iframe 확대/축소를 구글링하니 scale을 이용해서 정적으로 확대/축소하는 방법만 주구장창 나왔는데

그러다가 한줄기 빛과 같은 Panzoom을 만났다.

 

GitHub - timmywil/panzoom: A library for panning and zooming elements using CSS transforms

A library for panning and zooming elements using CSS transforms :mag: - GitHub - timmywil/panzoom: A library for panning and zooming elements using CSS transforms

github.com

 

 

Panzoom

 

timmywil.com

 

Panzoom 라이브러리는 scale등과 같은 css요소를 이용해서 동적으로 확대/축소를 할 수 있도록 도와준다고 한다.

 

Panzoom 깃허브 설명 중에 아래의 글이 있는데...

Panzoom uses CSS transforms to take advantage of hardware/GPU acceleration in the browser, which means the element can be anything: an image, a video, an iframe, a canvas, text, WHATEVER.

문제는... iframe일때 확대/축소가 안된다...!!!!!!!!!!!!!

 

4) 문제 해결 - part1

그래서 원인이 뭘까 생각하다가

iframe의 스크롤 이벤트랑 Panzoom 라이브러리의 핀치 이벤트가 서로 충돌해서 Panzoom의 핀치 줌 기능이 iframe에서는 제대로 동작하지 않는 거라는 생각이 들어서

아예 iframe의 스크롤 기능이 작동하지 않게 iframe 위를 덮어버리면 되지 않을까해서 아래와 같이 구현했다.

<div id="wrap">
  <div id="layer"></div>
  <iframe src="링크"/>
</div>

이렇게 하고 #wrap에 panzoom 기능을 적용하니까 성공적으로 iframe 확대 축소가 가능했다!!

예~~~ 드디어 성공!!! 하고 기뻐하려는 찰나....

 

5) 문제 해결 - part2 (최종)

상세 웹페이지에서 링크 이동을 하는 경우가 있다는 것을 알게 되었다...

그리고 중요한 건 iframe 내부의 링크를 클릭했을 때 iframe 내부에서 이동하지 않고 특정 함수를 호출해서 외부 브라우저(예: 삼성 인터넷)에서 해당 페이지가 열리게끔 처리해야 한다는 것이었다.

이 말은 즉, 아이프레임 내부의 href 요소를 지우고 해당 태그에 내가 원하는 함수를 click 이벤트에 걸어야 한다는 것!

 

오랫동안 이 기능을 어떻게 구현하나 걱정하다가 오늘 딱!!! 구글링하다가 해결방안을 찾았다.

구세주님 좌표: https://stackoverflow.com/questions/15248970/iframe-prevents-iscroll-scrolling-on-mobile-safari

 

구현 방법은

iframe 위에 레이어 div를 배치하고

② 해당 iframe 요소를 탭/클릭하려는 경우 레이어 div에서 감지한 x, y 좌표를 저장하고

iframe 콘텐츠 내의 해당 좌표에 대해 클릭 이벤트를 발생시키는 방식!

 

결과물은 아래에~~

아직 해결해야 하는 것들이 많이 있지만... 이렇게라도 구현한 것에... 나름 만족....!

■ 문제 해결 소스

● HTML

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script src="https://unpkg.com/@panzoom/panzoom@4.4.4/dist/panzoom.min.js"></script>
        <style>
            #layer {
                position: absolute;
                opacity: 0;
                z-index: 2;
            }
        </style>
    </head>
    <body>
        <div id="panzoom_parent">
          <div id="panzoom">
		<div id="layer" class="full-size"></div>
		<iframe src="해당링크" onload="iframeCssChange();" style="visibility: hidden; overflow: hidden;"></iframe>
          </div>
        </div>
    </body>
</html>

Javascript

// 확대/축소 설정
Panzoom(document.querySelector('#panzoom'), {
  minScale: 1,
  enableTextSelection : true,
  canvas: true, // panzoom 기능 적용한 범위 이외의 다른 영역을 드래그해서도 이동시킬 수 있다.
});

// 팬/줌 시에 화면에서 iframe이 벗어나면 원상복귀
document.querySelector('#panzoom').addEventListener('panzoomend', (event) => {
    var parentTop = $('#panzoom_parent').offset().top;
    var parentBottom = offsetBottom('#panzoom_parent');
    var parentLeft = $('#panzoom_parent').offset().left;
    var parentRight = offsetRight('#panzoom_parent');

    var childTop = $('#panzoom').offset().top;
    var childBottom = offsetBottom('#panzoom');
    var childLeft = $('#panzoom').offset().left;
    var childRight = offsetRight('#panzoom');

    var x = 0;
    var y = 0;
    var scale = panzoom.getScale();

    if (parentLeft < childLeft && parentRight < childRight) {
        x = (parentLeft - childLeft)/scale;
    } else if (parentLeft > childLeft && parentRight > childRight) {
        x = (parentRight - childRight)/scale;
    }
    if (parentTop < childTop && parentBottom < childBottom) {
        y = (parentTop - childTop)/scale;
    } else if (parentTop > childTop && parentBottom > childBottom) {
        y = (parentBottom - childBottom)/scale;
    }

    if (x || y) {
        panzoom.pan(x, y, {animate: true, relative: true});
    }
});

var offsetBottom = function(el, i) {
    i = i || 0;
    return $(el)[i].getBoundingClientRect().bottom;
};

var offsetRight = function(el, i) {
    i = i || 0;
    return $(el)[i].getBoundingClientRect().right;
};

// iframe css 변경
var iframeCssChange = function() {
  if ($('iframe').contents().find('html')) {
        var iframeInitHeight = $('iframe').height();
        var innerHtml = $('iframe').contents().find('html')[0];
        var innerWidth = innerHtml.scrollWidth;
        var innerHeight = innerHtml.scrollHeight;
        var scale = +(Math.floor(($('iframe').width() / innerWidth) + "e+2")  + "e-2"); // 소수점 둘째 자리까지

        $('iframe').contents().find('html').css({'transform': 'scale(' + scale + ')', 'transform-origin' : '0% 0%', 'width' : innerWidth, 'height' : '0'});

        // iframe 내부 영역의 크기와 iframe 크기를 비교해서 내부의 크기를 iframe 크기에 딱 맞게 정적으로 확대/
        if (innerHeight * scale > iframeInitHeight) {
            $('iframe').height(innerHeight * scale);
            $('#layer').height(innerHeight * scale);
        }

        // iframe 내부 링크 처리
        $('iframe').contents().find('[href]').each(function(){
            var href = $(this).attr('href');
            $(this).attr({
                'href': 'javascript:void(0);',
                'data-href': href
            });
        }).on("click",function(){ // 클릭 시 실행
            var href = $(this).data("href");
            // ~~~ href를 외부 브라우저에서 여는 함수 ~~~
        });

        // iframe 내부로 클릭 이벤트 전달
        $(document).on("click", "#layer", function(event){
            var iframe = $('iframe').get(0);
            var iframeDoc = (iframe.contentDocument) ? iframe.contentDocument : iframe.contentWindow.document;

            // Find click position (coordinates)
            var x = event.offsetX;
            var y = event.offsetY;

            // Trigger click inside iframe
            var link = iframeDoc.elementFromPoint(x, y);
            var newEvent = iframeDoc.createEvent('HTMLEvents');
            newEvent.initEvent('click', true, true);
            link.dispatchEvent(newEvent);
        });
    }
    
	$('iframe').css('visibility', '');
};