Transition의 현재 상태를 반환하는 객체다.
단독적으로 사용하지 않고 Surface나 Modifier에 붙여서 사용

var state = new Transitionable(0);
state.set(100, {duration : 500});//바로 실행
state.get(); //현재 상태 반환

[x,y,z]좌표값을 넣을 수 있다.
마지막 인자로 함수를 넣으면 콜백이 실행됨.

var state = new Transitionable(0);
state.set(100, {duration : 500}, function(){ alert('done!'); });

즉, Surface나 Modifier에서 set을 호출하고 get을 호출하여 값을 받아 움직인다고 생각하면 될 것 같다.


Transition은 Tween, Physics으로 두개가 있다. 

Tween Transitions

간단하게는 위에처럼 하고 30개의 curve을 제공한다.

var state = new Transitionable(0);
state.set(100, {duration : 500, curve : 'easeInOut'});

혹은 기본적으로 들어 있는 경우가 아닌 경우 직접 아래와 같이 등록해서 사용가능.
물론 만드는 것도 가능.

var TweenTransition = require('famous/transitions/TweenTransition');
TweenTransition.registerCurve('inSine', Easing.inSine);

//ex)
var customCurve = function(t){ return Math.pow(t,2); };
var TweenTransition = require('famous/transitions/TweenTransition');
TweenTransition.registerCurve('custom', customCurve);

Physics Transitions

가속도가 붙은 물리 transition이다.

SpringTransition

- 점을 넘어서 바운스

WallTransition

- 점을 안쪽에서 바운스

SnapTransition

- 스프링과 비슷한데 damping이 항상 들어감.


Tweens vs Physics

정확한 값이 아니고 정확한 타이밍에 이벤트를 발생시켜야 하는게 아니지만, 사용자 이벤트에 따라 변경하고 싶다면 사용한다.



Posted by 전용우
,


famo.us 코드 분석글은 정확한 글이 아니라 공부하면서 정리한 글이라 정확하지 않고 개인적인 의견들이 난무합니다. 정리하는 용도이니 추후 정리가 완료되면 좀 더 정확한 내용을 올릴 예정이니 나중에 정리된 글을 보세요.


앞에선[링크] famo.us에서 Render Tree를 알아봤고 이번에는 Render Tree의 레이아웃을 어떻게 하는지 알아본다.

앞에서 봤듯이 DOM Tree가 아니라 자체 Render Tree을 가지고 있고 레이아웃도 일반적으로 CSS로 하는게 아니라 famo.us에서 제공하는 레이아웃 방법을 사용한다.

레이아웃하는 방법은 Transforms와 Modifiers을 사용하면 된다.


Transforms

Transforms는 이름에서 나오는 느낌처럼 이미지처럼 걍 CSS3을 Transform을 자바스크립트로 옮긴 객체라고 생각하면 된다.

Transforms은 정적인 객체로 쉽게 translate, scale..을 관리할 수 있다. 내부적으로 css의 matrix3d을 사용하기 때문에 4X4 matrix 형식으로 다루는데 16개의 원소가 있는 배열로 관리한다.

Transforms은 아래와 같은 메서드들을 가지고 있고 아래와 같이 반환한다.


다른 메서드들이야 다를건 없는데 inFront, behind는 css의 z-index처럼 노드의 상하를 조절할 때 사용한다. 내부적으론 translateZ을 걍 살짝 추가한다.

Transform는 그냥 사용되지 않고 Scene Graph(보이는 노드들)에 붙여 이동할 때 사용한다.

var surface = new Surface({
        size: [50, 50],
        properties: { background: 'red' }
    });

    var modifier = new Modifier({
        transform : Transform.translate(100, 100, 0)
    });

    context.add(modifier).add(surface);

그리고 아래와 같이 다수개의 Transform들을 합치거나 기존의 Transform에 추가하여 사용하곤 한다.



Modifiers

Transform은 사실 현재 상태를 나타내는 객체로 혼자서 움직이거나 하지 못한다. 위에서 본 예제처럼 Modifiers에 붙여 Scene Graph에 붙여 사용한다. 그리고 Modifiers는 기본적으로  사이즈나 크기등을 조절할 수 있다.

크기의 경우, 아래와 같이 Surface에 size을 undefined로 하고 Modifirer에 지정하면 undefined의 경우 Surface에 사용한다. 만약 Surface의 값이 다 가지고 있다면 Modifiers의 값을 사용하고 있지 않다. 그래서 크기가 유동적이라면 Surface에서 등록하지 않고 Modifiers에서 등록하면 될 것 같다.


  var sizeModifier = new Modifier({size: [200, 200]});

  var surface = new Surface({
    size: [undefined, 100],
    properties: { background : 'red' }
  });

  context.add(sizeModifier).add(surface);


정렬의 경우, 문서에는 Origin,Align의 방법이 있는데 실제 코드에는 origin밖에 없다.

문서가 좀 더딘듯. origin은 몇가지가 지원되며 아래와 같다.



잼있게도 DOM을 직접 움직인다면 margin으로 처리했을 수 있는데 모두 계산하여 translate로 이동한다.


레이아웃을 사용할 때는 Modifiers을 사용한다고 했는데 이게 두가지 방법이 있다.

하나는 StateModifier, 다른 하나는 Modifier이다.

StateModifier는 push-based라고 하고 Modifier는 pull-based라고 한다.

말은 어렵지만, StateModifier는 내부적으로 모든 Transitionable(Translate...)들이 있어 Modifier에 설정하여 넣으면(push) 된다.

var surface = new Surface({
    size: [70, 70],
    properties: { background: 'red' }
});

var stateModifier = new StateModifier({
    opacity: 1
});

context.add(stateModifier).add(surface);

// animate the opacity from 1 to 0 over 500ms using a linear easing curve
stateModifier.setOpacity(
    0,
    {curve: 'linear', duration : '500'},
    function() { console.log('animation finished!') }
);


Modifier는 내부적으로 Transitionable가 없는 빈 상태로 아래와 같이 Transitionable을 연결하여 Modifier에서 Transitionable의 값을 가져와서(pull) 적용할 수 있다.


var surface = new Surface({
    size: [70, 70],
    properties: { background: 'red' }
});

var modifier = new Modifier();

context.add(modifier).add(surface);

// the opacityGetter
var opacityState = new Transitionable(1);

modifier.opacityFrom(opacityState);

// animate the opacity from 1 to 0 over 500ms using a linear easing curve
opacityState.set({
    Transform.translate(100, 0, 0),
    {curve : 'linear', duration : 500},
    function(){ console.log('animation finished!'); }
});

 

이번에는 레이아웃하는 방법등을 알아봤다, 좀더 자세한 내용은 아래 링크를 참고하면 된다.


https://famo.us/guides/layout

Posted by 전용우
,

famo.us 코드 분석글은 정확한 글이 아니라 공부하면서 정리한 글이라 정확하지 않고 개인적인 의견들이 난무합니다. 정리하는 용도이니 추후 정리가 완료되면 좀 더 정확한 내용을 올릴 예정이니 나중에 정리된 글을 보세요.



매주 화요일날 만나서 관심있는 자바스크립트 라이브러리를 분석하는 스터디를 진행 중인데 난 famo.us을 한다. 예전에 facebook에 paper을 웹으로 만들면서 화제가 됬고 최근데 2000만불을 펀딩 받은 것으로 유명하다. 

회사얘기는 대충하고 일단 이 친구들이 말하는게 다양한 폰에서 고퀄의 애니메이션을 보장해준다고 하길래 모바일웹을 개발해 본 입장에서 이게 가능할까라는 생각이 들어서 시작한다.

이번에는 프로젝트의 환경은 어떤지 찾아보고 몇 가지 문서를 보고 구조를 파악했다.


환경

빌드하는 도구가 있을 것 같지만, package.json에는 lint랑 jscs만 한다. 머징한 파일이 있는 것으로 보아 내부적으로 관리하는 것 같다. 컨트리뷰트 과정을 확인해보니 테스트 코드에 대한 내용이 없는데 내부적으로는 어떨지 모르겠다. 그리고 requirejs로 의존성 관리를 한다.


실행

기본적으로 실행하기 위해 몇 가지가 필요하다.

1. famous code : 내가 찾아볼 파일

2. polyfill code : 일부 polyfill들

3. css : 기본 스타일이나 크게 많지 않고 일부다.


구조

이번 내용은 주로 [링크]을 보고 정리한 내용. 

famous는 자체적으로 브라우저와 같은 Render Tree의 형식을 가지고 있다. 그래서 60fps를 가능하게 만들어준다.(왜 그런지는 끝에) famous는 JS로만 개발가능하게 만든 라이브러리로 sencha와 비슷한 컨셉

먼저, Context를 만듬. 브라우저로 따지면 root라고 생각하면 됨.

var context = Engine.createContext();
context.add(surface);

브라우저의 RenderObect와 같이 다수의 node을 가진다.

node의 종류는 Renderables, Modifiers가 있다.

Renderables은 실제 화면에 그려주는 역할을 하는 node로 ContainerSurface, ImageSurface, InputSurface, CanvasSurface, VideoSurface가 있다. 걍 엘리먼트라고 생각하면 된다. 여기서는 surface가 렌더링 되는 최소의 단위고 직접 컨트롤하기 보다는 surface를 통해하기를 말한다.

Modifiers는 움직이는 역할이다. Renderables은 정적인 컨텐츠고 실제로 움직일 때는 Modifiers을 사용한다.

var context = Engine.createContext(); var chain = context.add(modifier); chain.add(surface); var modifier = new Modifier({ transform : Transform.translate(100,200) });


그리고 각 노드들은 트리 형식의 구조를 만들 수 있고 add메서드을 통해 Tree을 만든다.

Chaining Nodes : 
  context                    var context = Engine.createContext();
     │
 modifier1                   context.add(modifier1)
     │                              .add(modifier2)
 modifier2                          .add(surface);
     │
  surface

Branching Nodes : 
      context                var context = Engine.createContext();
   ┌─────┴─────┐
modifier    surface2         context.add(modifier).add(surface1); // left branch
   │
surface1                     context.add(surface2);               // right branch

Views라는 개념이 있는데 이건 그냥 일반적으로 얘기하는 UI컴포넌트의 형식이라고 생각하면 된다. Render Tree에 추가하고 이벤트를 받거나 전달하고 파라메터, 상태를 지정가능. 여기서는 shadow dom과 같은 컨셉이라고 얘기한다. 아마도 직접 돔을 컨트롤하지 않고 View을 컨트롤하기 때문에 그런듯.

정리하면, Surfaces는 컨텐츠, Modifiers는 layout, 구조는 add메서도로 연결.



 Render Tree와 DOM을 비교한 표.

보면 알겠지만, famo.us는 html을 사용하는게 아니라 모두 famo.us객체를 사용한다. 

얻는 장점은 모든지 컨트롤 가능하다. 즉, 돔을 직접 수정한다면 생기는 문제점을 famo.us는 requestAnimationFrame에서 batch로 돌려 개선할 수 있다.

물론 단점은 생각보다 협업하기가 까다롭고, 모든걸 famo.us을 믿고 가야 하기 때문에 쉽지 않은 방법이다.


일단, 여기까지 본 결과 성능이 좋은 이유는 famo.us는 직접 DOM을 컨트롤하지 않고 Render Object을 만들어 컨트롤하기 때문에 requestAnimationFrame으로 batch를 하기 때문이 아닐까 생각하고 있다. 좀 더 살펴보는걸로.


https://famo.us/guides/render-tree


Posted by 전용우
,

#환경

일단 발견된 버그의 환경은 안드로이드 4.2 갤럭시 노트2에 기본 브라우저다.


#코드

var i = 0; var id = setInterval(function(){ if(i > -200){ document.getElementById('log').innerText = i; // document.getElementById("parent").style.webkitTransform = "translate3d("+i+"px,0,0)"; document.getElementById("parent").style.left = i+"px"; }else{ clearInterval(id); } i = i - 10; },16.8);

그냥 간단하게 엘리먼트를 주기적으로 옮기는 코드


#기대

16.8ms마다  -10px씩 이동하고 -200px이 되면 멈추야 한다.


#결과

매번 코드는 동작하지만 약 1000ms에 한번씩 이동함.


#원인

정확한 원인은 확인 안됨. 버전마다 차이는 있지만, 기기마다 차이가 있는지는 확인이 안됨. 참고로, 코드는 동작하지만 돔이 reflow가 발생되면 paint되지 않음. repaint의 경우는 정상적임. 타이머에 의해 돔이 주기적으로 reflow되면 매번 paint하지 않고 약 1000ms마다 paint됨. 아마도 내부적으로 타이머에서 돔이 reflow된 경우 적당하게 paint(약1000ms) 되는것 같음. 사용자 이벤트로 빠르게 변경하는건 상관없음. 재미있는건 translate는 잘됨. color같은 repaint작업은 상관 없음.


#회피방법

다각도로 테스트 해봤지만, left로 이동하면서 해결하는 방법은 없고 주석처럼 translate나 translate3d로 이동하면 해결가능.



Posted by 전용우
,

몇 년 전이더라...

사내에서 한참 교육을 할 때 테크니컬 라이팅 교육을 수강한 적이 있다.

그 때 하나의 주제로 글을 쓰는거였는데 마침 디버깅 관련한 문서가 없어서 디버깅을 주제로 글을 썼다. 그리고 나서 테스트을 추가로 써서 책으로 만들면 어떨까하고 알아봤는데 마침 인사이트 사장님이 좋게 봐주셔서 쓰기 시작했다.

이게 2011년 1월이다. -_-;

모두 그렇겠지만 처음에 한 6개월 생각했다. 근데 쓸 당시에 총각이였는데 지금은 2살배기 아이의 아빠가 됐다... 정말 틈틈히 쓰다보니 거의 3년이 지난 시점에 책이 나왔다.

쓰고 나서도 너무 아쉬움이 많이 남는다.

IE11이 나온 시점에 이 책에서는 IE9이 마지막이라 좀 아쉽고 크롬도 좀 더 다양한 기능들이 추가됐다.

이렇게 타이밍이나 지면상 다루지 못했던 다양한 얘기는 여기를 통해서 다룰 예정이다.

어쨌든 속 시원하네.



확대찜하기

[예약판매]자바스크립트 테스트와 디버깅

가격
22,500원
판매몰
gmarket
카테고리
-
구매하기


Posted by 전용우
,

앞의 글에서[링크] HTML import는 랜더링을 막기 때문에 지연되지 않기 위해 스크립트로 만들어서 사용했다.

근데 알고보니 HTML import는 기본적으로 랜더링을 막지 않는다.[링크] 앞에서 HTML import가 랜더링이 멈췄던 이유는 사실 스크립트 태그 때문이였다.


HTML import은 스크립트 태그를 만나기 전까지는 계속해서 랜더링을 하다가 스크립트 태그를 만나면 랜더링을 멈춘다.

예를들면 아래처럼 script태그가 나오기 전인 #ad_area까지 랜더링을 하다가 script태그를 만나면 멈추고 로딩이 끝나면 이어서 랜더링이 된다. 

<link rel="import" href="ad.html">

<div id="ad_area"> 앞 </div>

<script>
//script...
</script>

<div id="ad_area2"> 뒤 </div>

즉, 앞의 글에서 말한 HTML import는 랜더링을 막으면 안된다는 얘기는 상관이 없다.


그렇다고해서 무조건 랜더링을 막지 않는것이 좋은 것만은 아니다.

import는 Web Components을 사용할 때 사용하는 태그인데 사용하려고 하는 커스텀 엘리먼트가 import보다 먼저 랜더링되면 처음에는 모르는 태그라 넘어가다가 import가 완료가 되면 바로 적용이 된다.[링크]

이렇게 적용되면 FOCU[링크]와 같이 화면이 이상하게 보이는데 이런 문제를 해결하려면 적절하게 보이도록 만들거나[링크] 앞에서 본것처럼 아예 script을 커스텀 태그 앞에 넣어 멈추는 방법[링크]을 사용해야한다.

둘 다 매끄럽지 않기 때문에 현재 import을 적용할 때 element속성을 추가하여 element 속성에 설정한 엘리먼트들을 만나면 랜더링을 멈추는 스팩을 제안했고 여러 각도로 이슈를 해결하기 위해 토론되고 있다.[링크]

물론. 비슷한 이슈가 이미 논의가 됬고 :unresolved라는 가상 선택자[링크]를 만들어서 제공했지만, 크기를 가지는 엘리먼트의 경우 꿀렁거리는 이슈는 여전히 해결이 안된다. (참고로 angularjs의 ngCloak도 비슷한 용도이다.[링크])


대충 의미만 전달하기 위해 적었는데 좀 더 자세한 내용은 원문을 참고하길 바란다.[링크]


관련글

HTML import을 이용하여 document.write 개선하기.

Posted by 전용우
,

document.write을 사용하면 돔 랜더링이 멈추기 때문에 느리게 보인다. 그래서document.write을 대신하기 위한 다양한 방법이 나와있다. 하지만, 광고의 경우 다양한 곳에 삽입되기 때문에 대부분은 document.write을 사용한다.

이 문제를 해결하고자 최근에 웹성능의 본좌인 사우더스 아저씨가 HTML import을 활용한 방법을 제안했다.[링크] (HTML import는 본인과 크게 상관도 없고 아직 완성도가 있는 스팩인데 이런걸 활용하다니 좀 놀랐다.)

HTML import를 간단히 설명하면  Web Components을 사용하기 위한 방법으로 link태그를 이용하여 html을 로딩하는 방법이다.[링크]

자세한 내용은 원문[링크]을 참고하고 요약하면 사우더스 아저씨는 아래와 같이  import안에서 document.write를 하는 방법을 제안했다.

main.html

<link rel="import" href="ad.html">

<div id="ad_area" style="width: 480px; height: 60px; margin-left: 2em; margin-bottom: 2em;"></div>

<script>
var link = document.querySelector('link[rel=import]');
var content = link.import.querySelector('#ad');
document.getElementById('ad_area').appendChild(content.cloneNode(true));
</script>

ad.html
<div id="ad" style="background: #E99; border: 2px; font-size: 2em; text-align: center; padding: 8px;">
<script>
document.write("IMPORTED CONTENT<div style='font-size=0.8em;'>(using document.write)</div>");
</script>

근데 문제가 있다.

1. import는 동기로 로딩된다.[링크]

2. import한 html에서 document.write을 하면 main페이지에서 context로 실행되는 문제가 있다.[링크]

그래서 이를 해결하고자 아래와 같이 스크립트로 import을 삽입하고 ad.html에서 document.write을 재정의하여 현재 context로 실행하도록 했다.

main.html

<div id="ad_area" style="width: 480px; height: 60px; margin-left: 2em; margin-bottom: 2em;"></div>

<script>
  var link = document.createElement('link');
  link.rel = 'import';
  link.onload = function() {
    var link = document.querySelector('link[rel=import]');
    var content = link.import.querySelector('#ad');
    document.getElementById('ad_area').appendChild(content.cloneNode(true));
  };
  link.href = "ad.html";
  document.getElementsByTagName('head')[0].appendChild(link);
</script>

ad.html

<div id="ad" style="background: #E99; border: 2px; font-size: 2em; text-align: center; padding: 8px;">
<script>
document.write = function(msg) {
    document.currentScript.ownerDocument.write(msg);
};
document.write("IMPORTED CONTENT<div style='font-size=0.8em;'>(using document.write)</div>");
</script>


이렇게 document.write를 사용하면서 돔의 랜더링을 지연하지 않도록 개선할 수 있다.[링크]

물론 import의 안에서 실행되는 context의 이슈등 문제가 있어 당장 사용하기 힘들지만 아이디어는 좋은 것 같다.

그리고 사우더스는 HTML import가 기본으로 랜더링을 막지 말고 동작해야 한다고 제안했지만 이 문제는 쉽지 않다. 이 문제에 대해서는 다음에 알아보자. 또한 위의 방법은 오해가 있기 때문에 반드시 [링크]을 추가로 읽기 바란다.


관련글 

알고보면 랜더링을 막지 않는 import

Posted by 전용우
,

가끔 node.js로 코딩을 하곤 하는데 자주하는게 아니라 대충 실행하고 확인해서 크게 귀찮지 않았다. 근데 오늘 좀 많이 코딩할 일이 있어서 해보니 파일 수정하고 서버 재시작하는게 너무 불편하다.

당연히 변경된걸 자동으로 반영하는 도구가 있을것 같아 찾아보니 친절하게 많은 도구를 알려줘서 제일 처음[링크]에 나온 supervisor를 사용해봤다.

설치는 언제나 npm으로.

npm install supervisor -g

실행 방법도 언제나 간단.

supervisor web.js

확장자들로 설정할 수 있는데 난 web.js만 확인하고 있으면 되서 적용해봤는데....


완전 편하네. -_-;






Posted by 전용우
,

작년에는 Web Components관련해서 아무것도 없어서 가능성만 보고 발표했는데 올해는 그나마 라이브러리도 나오고 해서 좀 더 얘기할게 많았다.

Polymer랑 Brick에 대해서도 할 얘기가 많았지만 시간상 못하고 왔는데 언제 한번 자세히 정리해봐야지...


발표자료.


소스 코드는 아래서.

https://github.com/mixed/webcomponentscode/

Posted by 전용우
,

폴리머 프로젝트(Polymer Project)[링크]는 웹컴포넌트(WebComponents)을 기반으로 만든 라이브러리다.

근데 이 글은 폴리머 프로젝트가 어떤 프로젝트인지를 얘기하려고 쓴 글이 아니다. 그래서 웹컴포넌트가 뭔지는 알필요가 없기 때문에 자세히 설명하지 않는다. 그냥 컴포넌트 라이브러리구나 정도로 생각하면 된다.

본론으로 들어가기 전에 내가 왜 이글을 쓰게 됬는지 사전 배경이 필요해서 좀 주저리 주저리 적어본다. 나는 오랜시간 동안 자바스크립트 프레임워크를 개발하고 있는데 몇 년전 부터 고민이 내가 만들고 있는 프레임워크를 비롯하여 흔히 말하는 자바스크립트 프레임워크의 설계방법이 썩 좋은 방법이 아니라고 생각했다.

대부분의 자바스크립트 프레임워크는 크게 2가지 역활을 한다고 생각한다.

1. 브라우저마다 다르게 동작하는 기능들을 동일하게 동작하도록 하는 역활.

2. 좀더 쉽게 개발할 수 있도록 다양한 API을 제공하는 역활.

내가 알고 있는 현존하는 프레임워크들은 이 두가지가 코드로 섞여있다. 물론 아닐 수 있는데 찾진 못했다. 

1번을 비유를 하자면 성장과 같다. 시간이 지날수록 문제점을 발견하고 수정하면서 조금씩 성장하게 된다. 반면에 2번은 옷과 같다고 생각한다. 평상시에 캐주얼하게 잘 입는 사람이 옷을 잘입는다고 할 수 있지만 모든 상황에서 옷을 잘입는다고 하기 어렵다. 

근데 패션을 모를 때는 유행하는 옷을 입으면 옷 잘입는 것 같았는데 패션책을 보면서 눈을 뜨게 되면 때론 필요없는 악세사리는 빼버리기도 하고 상황에 맞게 내 뜻대로 입고 싶어 진다. 하지만 이미 옷은 몸과 붙어있어서 갈아입을 수 없게 된다.

이 문제는 내가 프레임워크를 개발하면 똑같이 겪었다. 역량이 낮을때는 알아서 해주는 라이브러리가 좋다가 어느 순간 역량이 높아지만 직접 하나씩 제어하고 싶어진다. 그리고 1번은 결코 버릴 수 없지만 2번은 버릴 수 있어 하는데 쉽지 않다. 

그래서 가끔 팀원들과 나중에는 저 2개를 반드시 분리하자고 했다. 물론 분리했을 때의 한계와 문제점들을 알기에 전체적으로 서둘러서 진행하진 못하고 나중에 모바일 정도만 적용해보자고 생각했다.


다시 돌아와서, 폴리머 프로젝트는 정확히 두가지가 분리되어 있다.

아래가 폴리머의 아키텍쳐이다. 보면 platform.js가 위에서 얘기한 1번이고 polymer.js가 2번에 속한다. 즉, 2번은 언제든지 버릴 수 있고 1번만 사용할 수 있다.

언제든지 polymer.js가 마음에 안든다면 platform.js는 사용하여 개발하면 된다.

근데 polymer프로젝트가 좀 아쉬운 점은 platform.js는 반드시 polyfill만 존재해야만 한다. 그렇지 않으면 언젠가는 1번과 2번이 섞이게 된다고 생각한다. 하지만 polyfill이 아닌 기능들이 있어 아쉽다.


    

Posted by 전용우
,