요즘 열심히 jQuery사용해보고 있는데 생각했던 것과 다르게 동작하는 것들을 정리하려고 한다.

오늘 발견한거 $.map[링크], map[링크]의 콜백의 인자 순서와 this, 반환 값이 다르다. 

// $.map
$.map(["a","b"],function(v,i){
 // v = value
 // i = index
 this;//window
 return v+"-";
});
// 반환 값 - Array

// map
$(".some").map(function(i,v){
 // i = index
 // v = value
 this;// v
 return v+"-";
});

// 반환 값 - jQuery // native map ["a","b"].map(function(v,i){ // v = value // i = index this;// window return v+"-"; }); // 반환 값 - Array

jQuery에서는  $.map은 native[링크]처럼 map은 좀 다르게 만들었다. 근데 $.map보다는 map이 native와 비슷한 구조라 헷갈린다.

아마도 map이 만들어지고 나서 $.map을 만들어진것 같다. 이후에 map이 native로 추가된 후 map의 사용법을 가져갈까? 표준 api의 사용법을 가져갈까? 고민하다 $.xx의 함수들은 표준 방법을 선택한 듯.

이런건 라이브러리를 만들면서 항상 고민되는 부분이다. 가장 깔끔한건 모두 표준과 같은 방법으로 맞추는건데 이게 쉬운 일인가? -_-;

map이면 굉장히 많이 쓰는 메서드인데 기존의 파라메터의 순서를 바꾸면 헬게이트 열림.

결국엔 기본 map은 수정할 수 없고, $.map을 map과 똑같이 하는 방법, native처럼 하는 방법 두 가지인데 사실 나였으면 지금의 jQuery방식보다는 $.map을 map처럼 바꿀 것 같다. 혹은 $.map을 지원하지 않거나.

지금의 방식은 모든 사람이 혼란스럽지만, $.map과 map의 사용법이 같다면, native에 map이 있는지 모르고 jQuery만 사용하는 사람한데는 혼란스럽지않다.

사실 그보다는 아예 $.map을 지원하지 않는 방법을 선호한다.





Posted by 전용우
,

express을 사용할 때 보통은 장난감을 만들어서 express generator[링크]을 사용하지 않고 대충 app.js에 몰아서 했는데 이번엔 좀 크기가 있는 어플리케이션을 만들 필요가 있어 폴더 구조도 정할 겸 express generator을 사용했다.


근데 난 nodemon을 사용하는데 기본 스크립트가 nodemon을 안쓰네. 아직 익숙하지 않아서 그런지 처음에 www 스크립트에 node -> nodemon으로 바꾸고 스크립트를 실행시켰는데 알고보니[링크] 당연하게도 package.json scripts의 start를 nodemon으로 바꾸면 된다.



Posted by 전용우
,

가끔 알려주면 사람들이 놀라는 팁.

JSON.stringfy는 JSON을 문자로 바꿔주는 메서드인지 다 안다. 보통 아래와 같이 사용

JSON.stringify({"a":1,"b":2});
//{"a":1,"b":2}

근데 위와 같이 작은 객체를 보기 쉬운데 크기가 커지면 커질수록 보기 어렵다.

그래서 stringify는 세 번째 인자로 숫자등을 넣어 공백이 삽입되 읽기 쉽게 할 수 있다.

숫자를 넣으면 들여쓰기, 공백, 줄 바꿈일 때 공백이 숫자만큼 들어가고 문자가 들어가면 문자가 들어간다.

예를 들면, 아래와 같이 세 번째 인자로 2을 넣으면 공백이 두개 들어간다.

JSON.stringify({"a":1,"b":2},null,2);
/*
{
  "a": 1,
  "b": 2
}
*/

두 번째 인자는 함수인데 반환 값으로 특정 값을 제외하고 싶을 때 사용한다. 반환 값이 undefined면 안 나옴.

자세한건 [URL참고]


[깨알팁 시리즈]

Posted by 전용우
,

그냥 검색하는게 짱인듯.


https://www.computersnyou.com/3376/setup-apache-php-mysql-macosx-10-10-yosemite/

http://stackoverflow.com/questions/25250566/apache-localhost-403-error-with-yosemite

Posted by 전용우
,

Views 와 Widgets사이에 커플링을 막고자 사용함.
뷰의 상하의 간에 메시지를 주고 받고 해야 한다.
일반적인 옵저버 패턴.


Broadcasting and Listening

eventHandlerA.on('A', function(data){ alert(data.msg); });  // alerts 'ALERT!' 

eventHandlerA.emit('A', message);//trigger

emit나 trigger나 같지만, emit는 밖에서 호출할 때, trigger는 안에서 호출할 때.


Piping
이벤트를 아래로 흐르게 해준다.(like 캡처링)

eventHandlerA.pipe(eventHandlerB).pipe(eventHandlerC);
eventHandlerC.on('A', function(data){alert(data.msg)});  // alerts 'ALERT!'
eventHandlerA.emit('A', message);


Subscribing

이벤트를 위로 올려준다.(like 버블링)

eventHandlerC.subscribe(eventHandlerB);
eventHandlerB.subscribe(eventHandlerA);
eventHandlerC.on('A', function(data){alert(data.msg)});  // alerts 'ALERT!'
eventHandlerA.emit('A', message);


Filtering

이벤트를 중간에 필터링 해줌.

var myFilter = new EventFilter(function(type, data) {
    return data && (data.msg === 'ALERT!'); //true or false
});

eventHandlerA.pipe(myFilter).pipe(eventHandlerB);
eventHandlerB.on('A', function(data){
    alert('piped message: ' + data.msg);
});



Mapping 

라우팅하듯이 특정 eventHandler에 이벤트를 전달한다.(개인적으론 불필요하다고 생각)

var myMapper = new EventMapper(function(type, data) { return (data && (data.direction === 'x')) ? eventHandlerB : eventHandlerC; });

eventHandlerA.pipe(myMapper); eventHandlerB.on('A', function(data){ alert('B direction : ' + data.direction); }); eventHandlerC.on('A', function(data){ alert('C direction : ' + data.direction); }); eventHandlerA.trigger('A', {direction : 'x'}); // pipes to eventHandlerB eventHandlerA.trigger('A', {direction : 'y'}); // pipes to eventHandlerC

pipe만 가능하다.(당연한다 subscribe가 되려면 mapping에서 이벤트를 받아야 함)


Arbitration

Mapping하고 비슷한 느낌인데 mode별로 이벤트를 등록할 수 있다.
한번에 하나의 모드만 가능하고 setMode로 하면 해당 모드만 동작.
이것도 왜 있어야 하는지 모르겠다.


var eventArbiter = new EventArbiter();

eventArbiter.forMode('routeA').on('A', function(data){
    alert('subscribed message: ' + data.msg);
});
eventArbiter.forMode('routeB').on('B', function(data){
    alert('subscribed message: ' + data.msg);
});

eventArbiter.setMode('routeA');

eventArbiter.forMode('routeA').emit('A', message); // alerts 'ALERT!'
eventArbiter.forMode('routeB').emit('B', message); // does nothing. Mode is not set.

eventArbiter.setMode('routeB');

eventArbiter.forMode('routeA').emit('A', message); // does nothing. Mode is not set.
eventArbiter.forMode('routeB').emit('B', message); // alerts 'ALERT!'


Event Handling Inside a Widget

자체적으로 몇가지 룰이 있다.

External to a Widget:

widget.trigger : the interface to talk to a widget

widget.on : the interface to listen to a widget

widget.pipe : the interface to pipe from a widget

widget.subscribe : the interface to subscribe from a widget


Internal to a Widget:

receive events via widget.eventInput

broadcast events via widget.eventOutput

widget.emit는 없음.


//아래와 같이 인풋 핸들러를 등록.

EventHandler.setInputHandler(widget, eventHandlerA);
eventHandlerA.on('B', function(data){alert(data.msg)});
widget.trigger('B', message);


Listening

setInputHandler으로 등록한 놈이 받음

// Child widget
function Child(){
    // setup input and output handlers
    this.eventOutput = new EventHandler();
    this.eventInput = new EventHandler();
    EventHandler.setInputHandler(this, this.eventInput);
    EventHandler.setOutputHandler(this, this.eventOutput);

    this.eventInput.on('hires tutor', function(){
        alert('Accepted to Harvard');
    }.bind(this));
}



// Parent widget
function Parent(){
    // setup input and output handlers
    this.eventOutput = new EventHandler();
    this.eventInput = new EventHandler();
    EventHandler.setInputHandler(this, this.eventInput);
    EventHandler.setOutputHandler(this, this.eventOutput);

    this.child = new Child();
    this.eventInput.on('bad report card', function(){
        this.child.trigger('hires tutor');
    }.bind(this));
}

var parent = new Parent();
parent.trigger('bad report card');


Broadcasting

setOutputHandler으로 등록한 얘가 전달.

EventHandler.setInputHandler(widget, eventHandlerA);
eventHandlerA.on('B', function(data){alert(data.msg)});
widget.trigger('B', message);



보면서 느낀건 전체적으로 과한 느낌이 있다.

너무 많다. 이런 경우 나중에 이벤트로만 넣으면 데이터가 흘러갈 때 문제가 있을 것 같다.
특히 mapping은 불필요. 데이터가 deep copy되지 않기 때문에 중간에 값을 바꾸면 디버깅의 거의 불가능하고 경험상 이벤트 종류는 너무 할 정도로 간단하게 가야 한다. 복잡하게 되면 나중에 엄청 고생함.

Posted by 전용우
,

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 전용우
,

앞의 글에서[링크] 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 전용우
,