안녕하세요.

'생각의 웹'입니다.


이글을 읽는 분들 중 지난 한국 웹 20주년 기념 컨퍼런스에 참석하신 분이 계신지 모르겠습니다.

이날 오후 첫번째 세션 중 트랙 C에서 엔씨 소프트 김민태 차장님이 '웹을 지탱하는 차세대 기술' 이라는 주제로 발표하셨는데 그림과 같이 가장 상단 부에 배치하신 기술이 지금 제가 여러분들께 소개하고자 하는 웹 컴포넌트(Web Component)입니다.


김민태 차장님이 의리로 공유하신 슬라이드를 보시죠.



슬라이드가 그림 위주로 작성된 관계로 강의를 듣지 않은 분들을 위해 제가 기록한 강의록을 아래와 같이 공유합니다.

- 웹의 성공 요인: simplicity -> 2000년대 application 형태로 진화

- Web Application

○ 고생스러움

* 올드한 느낌 

* 통합되지 않는 낙후된 개발환경

* 규모가 커지면 아키텍쳐링하기 힘듦

* 라이브러리 의존성 (너무 많은 라이브러리와 버전간 충돌 문제)

○ 웹 기술의 한계

* 자바스크립트의 한계

□ Common JS spec로 해결 시도 중

e.g. Cloud9

□ 그러나 기술 표준의 부재로 과도한 엔지니어링 노력이 필요

* div의 한계

□ Web Components 스펙으로 돌파구 마련

□ 그런데 관심이 별로 없음

□ 신기술들

. Template

◊ 템플릿 라이브러리를 사용하지 않아도 사용 가능

. Shadow DOM

◊ 랜더링 되지 않는 DOM (createShadowRoot() API)

. Custom Elements

◊ Element를 user defined 할 수 있음.

. HTML Imports

◊ 뷰를 가지고 있는 컴포넌트를 가져올 수 있음 -> 기존 웹 개발 패러다임의 변화를 가져올 수 있음

* 현재 사용가능한가?

□ polyfill로 모든 브라우저에서 대응 가능!! (구글 polymer, 모질라 brick)

e.g. slideshare - Polymer: Web Components & Web 참조

○ 퍼포먼스 이슈

* 브라우저 구조상 이슈로 성능 향상에 한계가 있음

□ DOM, Rendering 이슈

□ 중첩된 DOM 객체들 처리 이슈

□ 고해상도 지원

* 해결 방안 : Web GL

□ CPU 대신 GPU로 처리

□ Unity 2D 처리 사례처럼 3D 가속으로 2D를 처리 가능

. 단, 구현이 쉽지 않음. 

○ 기능 이슈의 해결 방안

* Service Worker

* Web Worker

* Transpiler & ASM.js


  김민태 차장님이 완소 자료로 언급하셨던 구글 폴리머 관련 슬라이드입니다.



공교롭게도 (아니면 필연적으로) 당일 오후 두번째 세션 트랙 A에서 W3C 다니엘 데이비스(Daniel Davis)가 발표한 'HTML5 이후의 변화들'이라는 강연에서도 HTML5가 우리에게 준 것들 (What did HTML5 gives us?)로 이 웹 컴포넌트를 예로 듭니다.


웹 컴포넌트는 위의 그림처럼 <meter>라는 사용자 정의 (custom) 태그 (tag)로 손쉽게 원하는 컴포넌트를 만들 수 있게 하는 기술입니다.

이로써, <div> 태그에 각종 class 속성을 입혀 원하는 컴포넌트를 만들던 이전 방식에서 벗어나 보다 직관적이고 확장성 있는 컴포넌트 개발이 가능합니다.


사실 이 기술은 년 초 WWW 2014 컨퍼런스 HTML5 기술 튜토리얼 시간에 공유된 바 있는 데 지금까지 저도 많은 관심을 갖지 못했었습니다.

(제대로 잘 만드는 것보다 어떻게든 만드는 것에만 급급했기 때문이죠. 아마 대부분의 개발자 분들이 처한 입장이 아닐까 싶군요.)

아래 링크를 가시면 다양한 예제를 코드와 더불어 경험해 볼 수 있습니다.


http://mainline.essi.fr/HTML5slides/chapter7Seoul.html


이번 포스팅을 정리합니다.


웹 컴포넌트 기술은 이미 현실에 적용된 차세대 웹 기술 중에서 가장 효용성이 있는 기술입니다. 널리 퍼뜨려 사용하게 만드는 것이 의리입니다. ^^

혹시 여전히 한국 사회의 악의 축으로 자리 잡고 있는 IE6, 7 과 같은 구형 브라우저들 때문에 사용을 주저하시는 분들이 있다면 이를 매꿔주는 다양한 폴리필(polyfill)이 있으니 걱정 놓으셔도 됩니다.


다음 포스팅으로 저만의 웹 컴포넌트를 만들어 이에 대한 후기를 공유해볼까 하는데 언제가 될지는 선뜻 약속드리기 힘드네요. ^^;

블로거 분들이 댓글로 관심 보여주시면 포스팅이 좀 빨라지지 않을까요? ;-)


긴 글 읽어 주셔서 감사합니다.

행복한 하루 되시길! 

 


'Web of Humans > HTML5' 카테고리의 다른 글

차세대 웹 기술 - 웹 컴포넌트 (Web Components)  (0) 2014.10.23
W3C HTML5 official tutorial  (0) 2014.04.30



안녕하세요, 생각의 웹입니다.


이번 포스팅은 요즘 잘 나가는 backbone.js를 이용해 객체 지향 Web App을 만든 사례를 소개하려 합니다.

backbone.js는 MVC (Model-View-Control) 모델을 기반으로 JavaScript 코드의 유지 보수성과 재사용성을 향상시킨 오픈소스 라이브러리입니다.

1. Prerequisites

backbone.js를 사용하기 위해서는 해당 라이브러리 코드를 사이트에서 다운로드 받아야 합니다. 또한, backbone.js은 underscore.js에 강한 종속성(dependency)가 있으니 함께 다운로드 받아야 합니다.

저는 코드를 다운로드 받아 js/libs 폴더에 두고 다음과 같이 index.html에 삽입했습니다. (이후 사용을 위해 가장 보편적인 DOM 관련 라이브러러인 jQuery 또한 참조하는 점도 주의깊게 봐 주시기 바랍니다. )

<script src="js/libs/underscore.js"></script> <script src="js/libs/backbone.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>


2. 모델 생성하기

모델을 생성하기 위해 Backbone.Model.extend() 함수를 사용합니다.

이번 웹앱으로 HTML5 기능을 이용한 간단한 생일 카드를 만들기 위해 다음과 같은 코드를 작성했습니다.

    var Card = Backbone.Model.extend({
    defaults: {
        img: 'Happy-Birthday-Greetings.png',
        title: 'Happy birthday to you!',
        music: 'Kevin_Lax_-_Happy_Birthday.mp3'
    },
    initialize: function () {        
        console.log("Card object initialized.");
    },
    getImageUrl: function () {
        return 'img/' + this.get('img');
    },
    getMusicUrl: function () {
        return 'res/' + this.get('music');
    }
});

카드에 포함될 기본 데이터로 이미지, 음악, 제목 속성을 지정하고 이미지는 img 폴더에 음악은 res 폴더에 두도록 설계했습니다.

initialize 함수 (정확히는 함수형 속성)는 해당 모델이 생성되는 시점에 호출되며 여기에서 생성 시 필요한 초기화를 할 수 있습니다.


이제 이 모델을 통해 인스턴스를 하나 생성해 보도록 합시다.

    var birthdayCard = new Card({    
    to: 'My beloved Joyan',
    from: 'Your father',    
    message:'May happiness be with you all!'
});

이 카드 모델에 추가로 필요한 속성들(수신인, 발신인, 내용)을 추가해서 인스턴스를 만들었습니다.

이상 없이 코딩했다면 console 창에 "Card object initialized."가 찍히겠죠.

3. 뷰 생성하기

앞서 소개드린 바로는 아직 별다른 이득(benefits)이 하나 없습니다. 이제 이걸로 뷰(view) 객체를 만들어 어떤 이득을 얻을 수 있는지 확인해 보겠습니다.

<div id="myCard"></div>


    var CardView = Backbone.View.extend({
    el: '#myCard',
    audio: null,
    canvas: null,

    initialize: function () {

        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];

        console.log('Card view intialized.');
    },
    render: function () {
        // append canvas, audio tag
        this.$el.append('<canvas id="myCanvas" width="730" height="414"></canvas>');
        this.$el.append('<audio id="myAudio"></audio>');
        return this;
    } 
});

모델을 생성할때와 유사하게 Backbone.View.extend() 함수를 사용해서 뷰를 생성합니다.

멤버 속성 중 하나인 el 에 index.html에 추가한 div 태그를 참조하기 위한 DOM selector를 저장합니다. 그리고, 앞서 설명드린 바와 같이 초기화 과정 중 호출되는 initialize() 함수를 살펴보면 index.html에 선언한 myCard라는 아이디의 div 태그를 참조하도록 되어 있으며 생성 시, HTML5 canvas, audio를 myCard div 내에 삽입하고 이를 멤버 속성인 audio와 canvas에 저장하도록 되어 있습니다.

이제 이 뷰의 인스턴스를 생성해 봅시다.

    var birthdayCardView = new CardView({
    model: birthdayCard
 });

CardView의 생성자에 model이라는 속성으로 앞서 생성한 birthdayCard를 지정합니다.

정상적으로 코드가 수행하면 console 창에 'Card view intialized.'가 출력됩니다.


3. 뷰에서 모델 정보 참조하기

이제 뷰와 모델이 연결되었습니다. 이제는 모델에 저장된 정보를 가지고 뷰를 꾸며보도록 하겠습니다.


    var CardView = Backbone.View.extend({
    el: '#myCard',
    canvas: null,
    audio: null,

    initialize: function () {
        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];

        this.showImage(); /* add image to canvas */

        console.log('Card view intialized.');
    },
    render: function () {
        /* omitted to short */
    },
 
    showImage: function () {
        var view = this;
        var context = view.canvas.getContext('2d');
        var imageObj = new Image();
        // clear canvas
        context.clearRect(0, 0, view.canvas.width, view.canvas.height);
        imageObj.onload = function () {
            context.drawImage(imageObj, 5, 5, 730, 414);
        };
        imageObj.src = view.model.getImageUrl();
        console.log('An image is shown.');
        this.state = 'image';
    }  
});

이로써 초기화 과정 중 birthdayCard 인스턴스에 저장된 image 파일을 가져다 canvas에다 뿌렸습니다.

showImage() 함수의 imageObj.src = view.model.getImageUrl(); 코드와 같이 view.model로 모델의 객체에 접근할 수 있습니다.

4. 뷰에서 이벤트 처리하기

이번에는 마우스를 이미지에 올릴 경우, 모델 객체에 저장된 음악을 출력하고 이미지에서 마우스를 내릴 경우 멈추도록 이벤트 핸들링을 해 봅시다.

    var CardView = Backbone.View.extend({
    el: '#myCard',
    canvas: null,
    audio: null,

    events: {
        'mouseover': 'playMusic',
        'mouseout': 'pauseMusic'
    },
    initialize: function () {
        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];
        this.setMusic(); /* 음악 파일 경로를 설정 */
        this.showImage();
        console.log('Card view intialized.');
    },
    render: function () {
        // append canvas, audio tag
        this.$el.append('<canvas id="myCanvas" width="730" height="414"></canvas>');
        this.$el.append('<audio id="myAudio"></audio>');
        return this;
    },
    setMusic: function () {
        // Set a birthday song from model
        this.audio.src = this.model.getMusicUrl();
    },

    showImage: function () {
       /* omitted to short*/
    },
 
    playMusic: function () {
        this.audio.play();
        console.log('The birthday song is playing.');
    },
    pauseMusic: function () {
        this.audio.pause();
        console.log('The birthday song paused.');
    }
});

뷰 객체의 events 속성으로 'mouseover', 'mouseout' 이벤트에 대한 핸들러를 playMusic, pauseMusic 과 같이 지정함으로써, 사진에 마우스를 올렸을때 음악이 흘러나오도록 하는 이벤트를 처리할 수 있습니다.

추가로 마우스 클릭 시, 이미지 대신 하트를 그리고 다시 클릭 시 메세지를 출력해 봅시다.

    var CardView = Backbone.View.extend({
    el: '#myCard',
    canvas: null,
    audio: null,
    state: null, /* 클릭 시 상태 변이를 저장하는 속성 */
    events: {
        'click': 'onClick',
        'dblclick': 'showImage',
        'mouseover': 'playMusic',
        'mouseout': 'pauseMusic'
    },
    initialize: function () {
        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];
        this.setMusic();
        this.showImage();
        console.log('Card view intialized.');
    },
    render: function () {
        // append canvas, audio tag
        this.$el.append('<canvas id="myCanvas" width="730" height="414"></canvas>');
        this.$el.append('<audio id="myAudio"></audio>');
        return this;
    },
    setMusic: function () {
        // Set a birthday song from model
        this.audio.src = this.model.getMusicUrl();
    },
    onClick: function () {
        switch (this.state) {
            case 'image':
                this.showHeart();
                break;
            case 'heart':
                this.showMessage();
                break;
            case 'message':
                // ignore state change
                break;
        }
    },
    showImage: function () {
        var view = this;
        var context = view.canvas.getContext('2d');
        var imageObj = new Image();
        // clear canvas
        context.clearRect(0, 0, view.canvas.width, view.canvas.height);
        imageObj.onload = function () {
            context.drawImage(imageObj, 5, 5, 730, 414);
        };
        imageObj.src = view.model.getImageUrl();
        console.log('An image is shown.');
        this.state = 'image';
    },
    showHeart: function () {
        var view = this;
        var context = view.canvas.getContext('2d');
        // clear canvas
        context.clearRect(0, 0, view.canvas.width, view.canvas.height);
        context.strokeStyle = "#000000";
        context.strokeWeight = 3;
        context.shadowOffsetX = 4.0;
        context.shadowOffsetY = 4.0;
        context.lineWidth = 10.0;
        context.fillStyle = "#FF0000";
        var d = 150;
        var k = 100;
        context.moveTo(k, k + d / 4);
        context.quadraticCurveTo(k, k, k + d / 4, k);
        context.quadraticCurveTo(k + d / 2, k, k + d / 2, k + d / 4);
        context.quadraticCurveTo(k + d / 2, k, k + d * 3 / 4, k);
        context.quadraticCurveTo(k + d, k, k + d, k + d / 4);
        context.quadraticCurveTo(k + d, k + d / 2, k + d * 3 / 4, k + d * 3 / 4);
        context.lineTo(k + d / 2, k + d);
        context.lineTo(k + d / 4, k + d * 3 / 4);
        context.quadraticCurveTo(k, k + d / 2, k, k + d / 4);
        context.stroke();
        context.fill();
        console.log('A heart is shown.');
        this.state = 'heart';
    },
    showMessage: function () {
        var view = this;
        var context = this.canvas.getContext('2d');
        // Print 'To' message
        context.font = 'bold 20pt Segoe UI';
        context.fillText('To. ' + view.model.get('to'), 10, 50);
        // Print 'title' message
        context.font = 'italic 40pt Calibri';
        context.fillStyle = 'gray';
        context.fillText(view.model.get('title'), 10, 310);
        // Print 'message' message
        context.font = 'italic 20pt Calibri';
        context.fillText(this.model.get('message'), 10, 350);
        // Print 'from' message
        context.font = '10pt Segoe UI';
        context.fillStyle = 'black';
        context.fillText('Sincerely, \n' + view.model.get('from'), 10, 400);
        console.log('The birthday message is shown.');
        this.state = 'message';
    },
    playMusic: function () {
        this.audio.play();
        console.log('The birthday song is playing.');
    },
    pauseMusic: function () {
        this.audio.pause();
        console.log('The birthday song paused.');
    }
});

이로써 이번 샘플 앱을 모두 완성했습니다.

onClick 함수에서 click 이벤트가 발생할 때마다 state 속성을 image -> heart -> message로 변경시켜 이에 따라 알맞는 함수를 호출하고 있습니다. showHeart()의 경우, canvas에 벡터 방식으로 heart를 그리고 있고 showMessage() 함수에서는 모델에서 가져온 속성들 (to, title, message, from)을 출력하고 있습니다.


이상으로 간략하게 나마 backbone.js를 이용해 코딩하는 법을 소개했습니다.

서두에 말씀드린 것처럼 backbone.js를 사용하면 기존 코드보다 훨씬 가독성이 좋은 코드로 구현되고 역할 별로 책임(responsibility)이 분리되어 유지 보수성이 향상됩니다.

추가적으로 컬렉션, REST API 연동 등의 우수한 기능을 제공하기 때문에 이번 기회에 익혀 두시면 좋을 듯 합니다.


긴 글 끝까지 읽어 주셔서 감사합니다. 행복한 하루 되시길!

  1. 생각의 웹 WebofThink 2014.09.25 17:03 신고

    소스 코드를 아래 공유합니다: https://github.com/hyunghunny/HTML5BirthdayCard


HTML5의 환상적인 JavaScript API를 소개한 슬라이드를 소개합니다.


http://mainline.essi.fr/HTML5slides/indexSeoul.html


WWW conference 2014에서 공유된 자료로 슬라이드 역시 HTML5로 작성되어 소개와 실습을 동시에 할 수 있는 멋진 자료입니다. ;-)


방대한 내용이라 일부 강의되었던 내용에 대해 기술한 포스팅을 추후 게시할 예정입니다.

많은 관심 부탁드립니다. 

'Web of Humans > HTML5' 카테고리의 다른 글

차세대 웹 기술 - 웹 컴포넌트 (Web Components)  (0) 2014.10.23
W3C HTML5 official tutorial  (0) 2014.04.30

+ Recent posts