Rev. 2.73

Ajax가 성행하면서 자바스크립트로 DOM을 직접 다뤄야 하는 일이 잦아졌다. 만약 자바스크립트를 이용해 노드를 대량으로 생산해야하는 임무가 주어졌다면 어떻게 처리하는 것이 효과적일까? 실제로 이와 비슷한 프로세스를 수행하는 이미지 리플랙터의 소스코드를 분석하고 이를 개선해 보자. 참고로, 이미지 리플랙터는 이미지의 반사된 잔상효과를 만들어주는 이펙트 라이브러리이다.

var Reflector = {
    reflect: function(element){
        element = $(element);
        var options = $H({
            amount: 1/3,
            opacity: 1/3
        }).merge(arguments[1] || {});
        var p = element.parentNode, n = element.nextSibling;
        var d = 1.0/(element.height*options.amount);
        (element.height*options.amount).times( function(line){
            var h = Builder.node('div',{style:'height:1px;overflow:hidden;'},
            [Builder.node('img',{src:element.src,
                style:'margin-top:-'+(element.height-line-1)+'px'
            })]);
            p.insertBefore(h,n);
            $(h).setOpacity((1-d*line)*options.opacity);
        });
    }
};

// Run
Reflector.reflect('img_id',{ amount:1/5, opacity:1/4 });

위 코드는 이미지 리플랙터의 원본 코드로서 실행되면 브라우저가 먹통이 되는 증상을 보인이다. Scriptaculous 프레임웍의 builder.js 라이브러리를 사용하는 Builder 메서드를 사용해서 높이 1px짜리 DIV와 IMG태그를 대량으로 생산하고 있다. Builder메서드는 엘리먼트를 생성하기 위한 document.createElement()와 innerHTML, Prototype의 setStyle 등을 간단하게 사용할 수 있게한다. 즉 createElement와 setStyle 메서드가 루프를 돌면서 브라우저에게 마비증상을 선사(?)하는 것이다.

var Reflector = {
    reflect: function(element){
        element = $(element);
        var options = $H({
            amount: 1/3,
            opacity: 1/3
        }).merge(arguments[1] || {});
        var p = element.parentNode, h = '';
        var d = 1.0/(element.height*options.amount);
        var m = document.createElement('DIV');
        for(var i=0; i < element.height*options.amount; i++){
            h += '<div style="height:1px; overflow:hidden;'+this.setOpacity((1-d*i)*options.opacity)+'">';
            h += '<img src="'+element.src+'" style="margin-top:-'+(element.height-i-1)+'px" alt="" /></div>';
        }
        m.innerHTML = h;
        p.insertBefore(m, element.nextSibling);
    },
    setOpacity: function(value) {
        var style = 'opacity:'+value;  
        if(Prototype.Browser.IE) style = 'filter:alpha(opacity='+value*100+')';
        return style;
    }
};

이것은 innerHTML에 직접 코드를 삽입하는 방식으로 변경한 것이다. 이미지 하단에 들어갈 'DIV'엘리먼트를 하나만 생성하고 루프되는 코드를 직접 작성하여 생성한 DIV에 innerHTML로 넣어준다. 결과는 완전히 동일하지만 소요되는 시간차이는 상당하다. createElement메서드로 640x360 이미지의 효과를 만들어 내는데 걸리는 시간은 파이어폭스가 2.25~4.563초, 익스플로러(IE7)가 12.19~15.18(HTML이 들고있는 주변 태그에 영향을 크게 받는다. 상단의 문법 하이라이트 코드를 수정했더니 결과가 확 바뀌었다.)초 정도가 소요된다. innerHTML 방식은 파이어폭스가 0.07~0.11초, 익스플로러가 0.04~0.11초 정도로 환경에 따라 다르겠지만 약 30 ~ 50배 가까이 소요시간을 단축하는 효과를 볼 수 있다. 생성하는 노드의 수가 많으면 많을 수록 더욱 큰 차이를 보인다. 아래의 이미지를 직접 테스트해 보자.

Faux_Fur_wide_screen.jpg

최근에는 노드를 대량으로 생성하는 방식으로 엘리먼트의 코너를 둥글게하거나, 대각선을 그리는 등 굳이 이미지를 사용하지 않아도 자바스크립트를 사용하여 DOM을 동적으로 표현하는 꼼수들이 속속 선보이고 있다. 가뜩이나 새로선보인 IE7 RC1의 노드생성속도는 완전 쉣이다. 랙현상으로 인해 사용이 꺼려진다면 innerHTML로 DOM을 직접 그려보는 것도 좋은 대안이 될 수 있겠다.

Comments

Got something to add? You can just leave a comment.

  • createElement 같은 경우는 브라우저가 그때마다 화면을 다시 그리기 때문에 느린 것 같네요. innerHTML의 경우는 웹페이지를 로드할 때처럼 메모리에서 쫙 파싱하고 한 번에 보여주는 방식일 테니 아무래도 빠르겠죠. 어쨌든 좋은 팁 알아갑니다. :)

    reply edit

  • 네, 말씀하신데로 입니다. 차이가 너무나도 심해서createElement 메서드가 무색하게까지 느껴지는군요.

    reply edit

  • innerHTML이 웹 표준인가요? 제가 요새 표준으로 만드는 것에 민감합니다.

    reply edit

  • W3C는 document.createElement() 를 권장하고 있습니다만, 비표준이라고 단정하기에도 모호하군요; 거의 모든 브라우저에서 즐겨 사용되고 있으니까요;;

    reply edit

  • 시간이 좀 지난 포스트네요...

    createElement 와 innerHTML 은 성능을 따져 구분해 써야 될것이 아니라 처음부터 용도가 다른것이 아닐까요 ?
    element 의 HTML 내용을 스트링으로 밖에 만들어 낼 수 없을때나 복잡할때 innerHTML 을 쓰면 될테고,
    그렇지 않을때 혹은 세세한 컨트롤이 필요할때 createElement 를 쓰겠죠.

    위의 처음 예제가 느리게 실행되는 이유는

    var p = element.parentNode;
    ().times(function(){
    ...
    p.insertBefore(h,n);
    });

    처럼 반복문 안에서 실제 DOM 모델을 업데이트 하기 때문이 아닌가 하는데요.
    아래 처럼 바꾸면 어떻게 되는지요 ?

    var p = element.parentNode;
    var t = new Element();
    ().times(function(){
    ...
    t.insertBefore(h...);
    });
    p.insertBefore(t..);

    말씀 드리려는 의도를 아셨으면 좋겠습니다.

    reply edit

  • 아 그렇군요! 위 코드는 처리방식에서 부터 차이가 있었군요.
    말씀하신데로, 실제 DOM모델을 업데이트 하면서 발생하는 랙현상이였습니다. 나중에 포스트를 업데이트 하도록 하겠습니다.

    감사합니다.

    reply edit

  • pianio11 pianio11

    파이아폭스 2.0 테스트 결과
    createElement, 2.213
    createElement (no update dom) 0.591
    innerHTML 0.13

    reply edit

  • mayou mayou

    파이어폭스 3.0 pre 20080402
    createElement, 0.001
    createElement (no update dom) 0.001
    innerHTML 0.032

    reply edit

  • 시간이 너무 지나서 의미가 있을지 모르겠지만 그래도 테스트 결과를 올려봅니다. 각 브라우저 별로 동일한 컴퓨터에서 테스트 했습니다.

    # 사파리4 베타
    createElement, 0.003
    createElement (no update dom) 0.001
    innerHTML 0.006

    # 크롬 1.0.154.48
    createElement, 0.001
    createElement (no update dom) 0.001
    innerHTML 0.016

    # 파이어폭스 3.0.7
    createElement, 0
    createElement (no update dom) 0.001
    innerHTML 0.029

    # IE 6.0
    createElement, 0
    createElement (no update dom) 0
    innerHTML 1.125

    0이나 0.001은 거의 동일하다고 보시면 됩니다. 여러번 반복하면 번갈아 나옵니다.

    IE 같은 경우 거의 사용을 안해서 업데이트를 안해서 6.0밖에 없었습니다.-_-;;

    그나저나 세월이 많이 지나서 그런지 포스트의 내용과 테스트의 결과가 정반대로 나와 버리는 군요.
    innerHTML이 가장 시간이 많이 걸리는 군요.

    그리고 IE6.0의 createElement의 결과도 의외입니다. -_-a

    reply edit

  • 테스트 코드가 조금 이상했습니다. 올바로 수정해 놓았습니다.

    reply edit

  • 실시간 답글이시군요. ^^

    그래서 다시 시도해 봤습니다.

    # 사파리4 베타
    createElement, 0.235
    createElement (no update dom) 0.01
    innerHTML 0.005

    # 크롬 1.0.154.48
    createElement, 0.14
    createElement (no update dom) 0.025
    innerHTML 0.012

    # 파이어폭스 3.0.7
    createElement, 0.364
    createElement (no update dom) 0.148
    innerHTML 0.029

    # IE 6.0
    createElement, 2.812
    createElement (no update dom) 2.724
    innerHTML 0.82

    브라우저의 버전이 최신으로 갈 수록 createElement와 innerHTML의 시간 격차가 점점 줄어드네요.

    reply edit

Your Reaction Time!

captcha

avatar