제가 잘 몰랐던 부분을 마음껏 물어볼 수 있어서 좋왔던 것 같습니다. 후후후 ^^;;
※ 본 포스트는 script.aculo.us의 effects.js와 관련된 세미나를 들으면서 들었던 생각과 내용을 보충하는 내용입니다. Effet를 생성하거나 ScopedQueue를 생성하거나 옵션을 주는 부분은 다루지 않습니다.

1. effects.js란?
- 인자로 받는 element들을 대상으로
- 시작시간(startOn)부터 종료시간(finishOn)까지
- 투명하게하거나, 접히거나, 서서히 사라지고 나타나는 등의 효과(데모 페이지)

2. effects의 코드 상의 구성
effects.js의 구성은 세 가지 단계로 나눌 수 있습니다. (effects.js 코드는 Effect 스코프를 가지고 있습니다.)
- Effect.Base를 상속받은 Effect 클래스
- Effect클래스를 시간 상(time line)에서 어디에 배치되고 동작할 지를 관리하는 Effect.ScopedQueue
- Effect.ScopedQueue를 전부 관리하는 Effect.Queues
※ 다이어그램은 의미전달 용도로 쓰였기 때문에 UML 규약과 틀릴 수 있습니다.
3. effects 개념상의 구성
effects의 개념을 보기 위해서는 가장 먼저 할일이 ScopedQueue를 들여보는 일입니다. ScopedQueue를 간단히 말하자면 영사기와 같다고 보시면 됩니다. 15ms이라는 단위를 갖는 시간 축을 갖고 15ms이 지날 때마자 자신의 effects(영화 필름의 프레임으로 생각하셔도 될 것 같습니다.)들에게 시간을 알려줍니다.
ScopedQueue에 더해진 Effect들은 넘겨 받은 시간을 확인하여 자신이 나타날 시간인지 아닌지를 확인한 후 자신들이 변화된 (스타일이 변경되거나 html이 변경된) 모습을 화면(페이지)상에 출력하게 됩니다.
ScopedQueue의 코드 상의 흐름을 잠시 살펴보겠습니다.
- 먼저 add()가 호출되면 현재 시간의 timestamp를 얻어옵니다.
add: function(effect) {
var timestamp = new Date().getTime(); - 더해진 effect가 다른 effect보다 앞에 나와야할 지 또는 뒤에 나와야할 지에 대한 position 값이 주어지면 해당하는 position만큼 effect들이 무대(페이지)에 나오는 시간과 들어갈 시간을 조절해 줍니다.
var position = Object.isString(effect.options.queue) ?
effect.options.queue : effect.options.queue.position;
switch(position) {
case 'front':
// move unstarted effects after this effect
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
e.startOn += effect.finishOn;
e.finishOn += effect.finishOn;
});
break;
case 'with-last':
timestamp = this.effects.pluck('startOn').max() || timestamp;
break;
case 'end':
// start effect after last queued effect has finished
timestamp = this.effects.pluck('finishOn').max() || timestamp;
break;
}
그림으로 본다면 다음과 같이 될 것 같습니다. 앞의 그림을 ScopedQueue의 현재 상태라고 가정하구요. 여기에 초록색 Effect를 추가하겠습니다. 시작시간(startOn)은 15ms, 종료 시간은 65ms으로 설정을하고 파란색 Effect의 앞(front)로 삽입을하면 다음의 결과가 나오게 됩니다.초록색 Effect가 앞에 나오고 다른 Effect들의 시작시간은 65ms만큼 앞에 더해지게 되는거죠. - 더해지는 Effect에 시작시간(startOn)과 종료시간(finishOn)에 현재 시간의 timestamp를 더해서 시작가능한 시간이 되도록 맞춰줍니다.
effect.startOn += timestamp;
effect.finishOn += timestamp; - Effect의 limit를 체크한 후, limit를 넘지 않는 경우 ScopedQueue에 추가를 합니다.
if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
this.effects.push(effect); - this.interval가 있는지 확인한 후 없는 경우 setInterval을 호출해서 interval을 생성합니다. (달리말하면, timeline을 생성한다고 봐도 되겠죠.)
if (!this.interval)
this.interval = setInterval(this.loop.bind(this), 15);
},
this.interval이 설정이 되면, 15ms마다 loop를 호출하게 됩니다. - loop에서는 호출 될 때마다, 현재 시간을 얻은 후 ScopedQueue에 쌓여 있는 effect들에게 시간을 알려줍니다. (Effect.Base에서 상속받는 loop()를 호출해주게 됩니다.)
loop: function() {
var timestamp = new Date().getTime();
for(var i=0, len=this.effects.length;i<len;i++)
this.effects[i] && this.effects[i].loop(timePos);
} - 호출된 Effect의 loop에서는 현재 시간이 자신이 나타나야되는 시간이 지났는지와 종료되어야 하는 시간인지를 체크한 후 (startOn과 finishOn 사이의 시간인지를 확인한 후에) 자기 자신을 화면에 그리게 됩니다.
// Effect.Base의 loop 메소드입니다.
loop: function(timePos) {
if (timePos >= this.startOn) {
if (timePos >= this.finishOn) {
this.render(1.0); // 종료 시점의 pos 값은 1입니다.
this.cancel();
this.event('beforeFinish');
if (this.finish) this.finish();
this.event('afterFinish');
return;
}
// startOn과 finishOn 사이의 시간인 경우
var pos = (timePos - this.startOn) / this.totalTime,
frame = (pos * this.totalFrames).round();
if (frame > this.currentFrame) {
// 현재의 frame과 timeline상의 시간의 frame이 맞지 않은 경우 새로 그려줍니다.
this.render(pos);
this.currentFrame = frame;
}
}
},
※※ 발표하느라 고생이 많으셨던 '멋진인생'님과 제가 잘 몰랐던 부분을 가르켜주신 신기루님 감사해요~
-_-;;; 대단하십니다~
답글삭제정리의 기술이라......
문서화가 부족한 저에게 뭔가 시사하는 바가 있는 글이네요 ㅋㅋ
@멋진인생 - 2008/02/25 12:02
답글삭제멋진인생님 블로그 주소 좀 알려주세요. 링크걸께요 ^^
비밀 댓글 입니다.
답글삭제@Anonymous - 2008/04/24 17:23
답글삭제메일로 답변을 드렸습니다. 좋은 하루 되세요 ^^