들어가며

패럴랙스 스크롤링 웹 사이트의 세계로 들어갈 준비는 되었나요? 패럴랙스 스크롤을 사용한 사이트는 많습니다. 안타깝게도 일부 사이트에 있는 패럴랙스 스크롤링은 사용자들의 주의를 분산시키거나 보여주고자 했던 콘텐츠가 아닌 다른 곳으로 사용자들의 눈을 돌리지만, 제대로만 사용한다면 훨씬 더 빼어난 웹을 만들 수 있습니다. 패럴랙스는 잘만 사용된다면 사용자들이 더 실감 나는 매력적인 방식으로 콘텐츠를 탐색할 수 있도록 도와줍니다.

최근 우리팀은 콘텐츠 일부에 패럴랙스 스크롤 애니메이션을 사용하기 시작했습니다. 우리는 ADT의 역사라는 페이지에서 약간의 아이디어를 얻고, 유명 드라마인 워킹 데드에 대한 인터랙티브 좀비 인포그래픽을 만들어보기로 했습니다. 오늘은 이 프로젝트의 무대 뒤로 여러분을 데려가 굉장히 유용했던 Skrollr.js라는 자바스크립트 플러그인의 사용법을 보여주려고 합니다.

메인 좀비 이미지

간단히 말해서 Skrollr는 HTML과 CSS를 사용해 정교한 스크롤 기반 애니메이션(패럴랙스 혹은 그 밖의 것)을 만드는 플러그인입니다. Skrollr의 수많은 장점은 공식 문서에서 찾아볼 수 있습니다. 이 글에서 우리는 자바스크립트와 스프라이트 몇 개를 사용해 사용자가 스크롤을 앞뒤로 움직임에 따라 반응하는 보행 애니메이션을 간단하게 만들어보겠습니다. 이 글을 끝까지 따라가면, 패럴랙스 환경에서 걷는 캐릭터가 인간에서 좀비로 변하는 것도 볼 수 있습니다.

먼저 몇 가지 소스 파일을 다운로드 받으세요. 워킹 데드 프로젝트에서 사용했던 이미지 몇 장과 자바스크립트 파일, HTML 뼈대가 있을 것입니다. 이제 이 파일들을 가지고 삽질을 시작해보겠습니다.

1단계 - 패럴랙스 백그라운드

HTML5 data- 속성을 추가한 <div> 몇 개를 추가해서 패럴랙스 백그라운드 레이어 두 장을 만들어 보겠습니다. 또한, 스타일시트에 CSS도 약간 추가하고 우리가 작성한 main.js 파일에서 Skrollr의 .init() 함수도 호출할 것입니다. 다음 코드에서 보듯 패럴랙스 레이어는 id="skrollr-body"가 설정된 엘리먼트의 자식 노드여야 합니다. 지금 보기에는 클래스와 ID가 너무 많다 싶을 수 있지만, 나중에 엘리먼트를 조금 더 추가하게 되면 이유가 있었음을 알게 될 것입니다.

HTML:

<div id="skrollr-body">
	<div id="Scene">
		<div id="bg1" class="parallax-layer"
			data-0="left:0px;"
			data-5000="left:-1000px;">
		</div>
		<div id="bg2" class="parallax-layer"
			data-0="left:0px;"
			data-5000="left:-2500px;">
		</div>
	</div>
</div>

우리가 보게될 마술같은 일은 모두 data- 속성 덕분입니다. Skrollr를 호출하면, Skrollr는 #skrollr-body 안에 있는 모든 엘리먼트를 탐색하며 엘리먼트에 설정된 data- 속성을 사용해 적절한 애니메이션을 구현합니다. data- 뒤에 붙은 숫자는 사용자가 스크롤 한 픽셀의 수이며, 속성의 값은 해당 스크롤 위치에 왔을 때 현재의 요소에 설정할 스타일입니다. #bg1를 예로 들면, 사용자가 페이지 상단(data-0)으로부터 페이지 아래로 5,000 픽셀(data-5000)만큼 스크롤하면 #bg1이 왼쪽으로 1000 픽셀만큼 움직이는 애니메이션이 나타날 것입니다. 설정한 값을 보면 Skollr는 대략 사용자 스크롤 5픽셀마다 왼쪽으로 1픽셀씩 #bg1을 옮길 것입니다. 이 애니메이션은 앞으로도 뒤로도 일어나므로 사용자는 어느쪽으로든 스크롤 할 수 있으며 Skrollr이 적절한 애니메이션을 보여줄 것입니다.

#bg2data-* 속성에 설정된 값을 살펴보면 사용자 스크롤 2픽셀마다 #bg2가 1픽셀씩, 다시 말해 #bg1보다 훨씬 빨리 움직이는 것을 알 수 있습니다. 이 덕분에 3D 공간 같은 느낌이 날 것입니다. 그러나 이 코드를 실행하기 전에 이미지를 추가한 후 액자처럼 감싸는 CSS를 추가해야 합니다. 그 후에는 Skrollr의 초기화 함수도 호출해야 하고요.

CSS:

화면은 700px 높이의 프레임에 수직 중간 정렬되게 합니다.

#Scene{
	position: fixed;
	width: 100%;
	height: 700px;
	margin-top: -350px;
	top: 50%;
	}
.parallax-layer{
	position: absolute;
	height: 700px;
	background-repeat: no-repeat;
	}
#bg1{
	background: url(../images/bg-1.jpg);
	width: 3528px;
	}
#bg2{
	background: url(../images/bg-2.png) left bottom;
	/* 이미지가 꽤 크지만... 사용할만한 가치가 있습니다 */
	width: 4368px;
	}


JavaScript:

skrollr.init();

잠시 후에는 여기에 다른 작업을 더 추가할 것이므로 여기까지 작업한 내용을 별도 파일로 저장했습니다. 하지만, 여러분은 굳이 그렇게 하지 않아도 됩니다.

이것 보세요! 패럴랙스가 만들어졌네요!

2단계 - 전경 엘리먼트

다음으로는 전경에 몇 가지 요소를 추가해보겠습니다. 구름, 해, 학교를 추가한 후 캐릭터도 추가할 것입니다. 이 중 학교는 캐릭터가 인간에서 좀비로 변신할 때 잠시 캐릭터를 숨기는 용도로 사용합니다.

먼저 두 번째 구름 레이어를 추가해봅시다. 이 구름 레이어는 #bg2와 같은 속도로 화면을 가로질러 이동할 것이므로, #bg2 안에 구름 레이어를 추가하여 (절대 위치에 기반한) 동일한 애니메이션이 적용되도록 합니다. 이처럼 내부에 있는 div는 부모의 CSS 위치 속성을 그대로 사용하는 경우가 많습니다.

HTML:

<div id="bg2" class="parallax-layer"
	data-0="left:0px;"
	data-5000="left:-2500px;">
	<div id="cloud"></div>
</div>

CSS:

#cloud{
	position:absolute;
	background:url(../images/clouds.png);
	width:2059px;
	height:347px;
	}

이제 학교와 아직 좀비가 되기 전인 캐릭터를 추가해보겠습니다. 학교는 #bg2보다 살짝 더 빠르게 움직이게 할 것입니다. 또한, 학교를 그릴 때 뒤쪽과 앞쪽 레이어를 나누고 그 사이에 좀비 캐릭터를 두어서 마치 캐릭터가 문을 통과해서 지나가는 것처럼 보이게 할 것입니다.

HTML:

<div class="parallax-layer">
	<div id="schoolunder" class="school"
		data-0="left:2000px;"
		data-5000="left:-1500px;">
	</div>
	<div id="zombie"></div>
	<div id="schoolover" class="school"
		data-0="left:2000px;"
		data-5000="left:-1500px;">
	</div>
</div>

CSS:

캐릭터는 화면 왼쪽에서 300px만큼 떨어진 곳에 그대로 있되 배경이 화면 좌우로 움직이도록 하여 마치 예전에 보던 횡 스크롤 게임 느낌이 나도록 했습니다.

#zombie{
	position:absolute;
	background-image:url(../images/zombify.png);
	width:250px;
	height:190px;
	top:340px;
	left:300px;
	}
.school{
	position:absolute;
	width:600px;
	height:523px;
	top:100px;
	}
#schoolunder{
	background:url(../images/school_under.png);
	}
#schoolover{
	background:url(../images/school_over.png);
	}

이제 이 캐릭터는 마치 스케이트를 타듯 미끄러지며 화면을 이동하는 것처럼 보일 텐데, 여기에 보행 애니메이션과 짤막한 좀비 변신 애니메이션을 추가해보겠습니다. 그전에 해 이미지부터 추가하고 넘어가죠. 우리는 5개의 data 속성을 사용하여 해가 뜨고 지는 애니메이션을 표현했습니다. 이 엘리먼트는 #bg1#bg2 사이에 둘 것이므로 하늘 배경보다는 앞에 보이지만 전경 엘리먼트 뒤쪽을 이동하게 될 것입니다.

HTML:

<div id="bg1" class="parallax-layer"
	data-0="left:0px;"
	data-5000="left:-1000px;">
</div>
<div id="sun"
	data-0="top:200px;"
	data-2000="top:25px;"
	data-3000="top:0px;"
	data-4000="top:25px;"
	data-5000="top:50px;">
</div>
<div id="bg2" class="parallax-layer"
	data-0="left:0px;"
	data-5000="left:-2500px;">
</div>

CSS:

#sun {
	position:absolute;
	background:url(../images/sun.png);
	width:194px;
	height:194px;
	left:180px;
	}

우리가 지금까지 한 내용을 확인해보세요. 꽤 괜찮지만, 아직 다듬어야 할 부분이 남아 있습니다.

3단계 - 계속 걷게 하기 (& 좀비로 변신하기)

이제 캐릭터에 생명력을 불어넣을 때입니다. 캐릭터의 보행 및 변신 애니메이션에 필요한 이미지 프레임은 다운로드 받은 .png 스프라이트 파일 안에 있습니다. 처음에는 인간이었다가 나중에 좀비로 변할 것입니다. 이 프레임들을 배경 이미지로 보여주고 사용자의 스크롤에 따라 주기적으로 반복시키는 자바스크립트를 작성해보겠습니다. 스크롤을 어느 쪽으로 하든 동작하게 만들어서 정말 게임 같은 느낌을 내보겠습니다.


좀비화를 표현하는 스프라이트

data-* 속성을 사용하여 Skrollr가 배경 이미지를 애니메이션으로 만들게 할 수도 있지만, 이 방법은 속성을 꽤 많이 작성해야 하므로 직접 함수를 작성하기로 합니다. 직접 작성한 덕분에 약간의 유연함도 얻을 수 있으며 코드의 유지보수도 확실히 더 편해졌습니다. 우리는 skrollr.init()를 호출할 때 우리가 작성한 함수를 인수로 전달할 것입니다. 조금 더 자세히 말하자면 beforerender 리스너 함수로 할당하여 Skrollr가 애니메이션의 각 프레임을 그리기 직전에 자동으로 호출되게 할 것입니다(beforerender를 비롯한 Skrollr의 다른 옵션은 문서를 참고하세요). beforerender에는 curTop이라는 프로퍼티를 가진 객체가 인수로 전달됩니다. curTop은 현재 스크롤이 상단으로부터 얼마나 떨어졌는가를 알려주는, 다시 말해 페이지 상단으로부터 사용자가 스크롤 한 픽셀의 양을 알려주는 숫자입니다. 우리는 이 값을 사용하여 배경 이미지로 사용한 스프라이트를 앞으로 움직일 것인지 뒤로 움직일 것인지 또는 인간을 좀비로 바꿔야 할 때인지 정하겠습니다.

보행 애니메이션부터 작업한 후 인간에서 좀비로 변신하는 과정을 추가하겠습니다.

JavaScript:

var zombie = $('#zombie'),
	pLocs = [0, -250, -500, -750, -1000, -1250, -1500]
	curFrm = 0,
	lastStep=0;

skrollr.init({
	beforerender: function(o){
		// 마지막에 백그라운드 이미지를 움직인 후에
		// 사용자가 50픽셀 아래로 스크롤하면
		// 앞으로 이동해야 하므로 스프라이트에 있는
		// 다음 프레임으로 옮긴다.
		if(o.curTop > lastStep + 50) {
			if (curFrm>=6){ curFrm=-1; }
			zombie.css('background-position', pLocs[curFrm++] + 'px 0px');
			lastStep = o.curTop;
		// 사용자가 50픽셀 위로 스크롤하면
		// 뒤로 이동해야 하므로 이전 프레임으로 옮긴다.
		} else if(o.curTop < lastStep - 50) {
			if (curFrm<=0){ curFrm=7; }
			zombie.css('background-position', pLocs[curFrm--] + 'px 0px');
			lastStep = o.curTop;
		}
	}
});
  • zombie — 캐릭터를 표현하는 div에 대한 참조
  • pLocs — 스프라이트의 각 프레임이 왼쪽 끝으로부터 떨어진 픽셀 위치를 담은 배열
  • curFrm — pLoc에 있는 보행 애니메이션 프레임 중 현재 사용 중인 프레임의 인덱스
  • lastStep — 가장 최근에 배경 이미지를 움직였던 스크롤 위치. 프레임을 앞/뒤 어느 쪽으로 이동할 것인지 계산할 때 사용

멋지네요! 어려운 부분이 끝났습니다. 우리의 좀비는 걷기 시작할 테고요. 이제 남은 단계는 좀비로 변신!밖에 없습니다.

어쩌면 워킹 데드 드라마와 약간 다를지도 모르는데, 이 프로젝트에서는 캐릭터의 좀비 변신이 학교(zombie school)로 표현되었습니다. 덕분에 이 과정은 상대적으로 편했습니다. 인간이 걷는 장면의 프레임과 좀비가 걷는 장면의 프레임은 같은 이미지에 있었고 인간이 걷는 장면 바로 뒤에 좀비가 걷는 장면이 있었으므로 사용자가 캐릭터가 학교를 지나가는 장면이 나오는 곳을 스크롤 할 때 #zombie 레이어 배경 이미지의 위치만 적당히 바꿔주면 됐습니다. 인간이 걷는 장면은 7프레임인데 반해 좀비가 걷는 장면은 4프레임 밖에 안 들어서 애니메이션이 조금 더 유연하게 동작하도록 코드를 살짝 손봐야 했습니다. 수정된 부분은 아래에 굵은 글씨로 표시해두었습니다.

JavaScript:

var zombie = $('#zombie'),
	pLocs = [0, -250, -500, -750, -1000, -1250, -1500],
	curFrm = 0,
	lastStep=0,
	// We need just a couple extra variables
	animationCycle, backPosY;

skrollr.init({
	beforerender: function(o){
		// 사용자가 2800픽셀 이하로 스크롤하면
		// 캐릭터를 그대로 인간인 상태로 두고
		// 그 이상 스크롤하면 좀비로 바꾼다.
		if(o.curTop < 2800) {
			animationCycle=7;
			backPosY= '0px';
		} else {
			animationCycle=4;
			backPosY= '-190px';
		}
		if(o.curTop > lastStep + 50) {
			if (curFrm>=animationCycle-1){ curFrm=-1; }
			zombie.css('background-position', pLocs[++curFrm] + 'px ' + backPosY);
			lastStep=o.curTop;
		} else if(o.curTop < lastStep - 50) {
			if (curFrm<=0){ curFrm=animationCycle; }
			zombie.css('background-position', pLocs[--curFrm] + 'px ' + backPosY);
			lastStep = o.curTop;
		}
	}
});

그럼 이제 끝났습니다. 지금 막 우리는 게임 스타일의 패럴럭스 스크롤링을 만들었습니다. 축하합니다! 만약 더 다듬어진 버전을 보고 싶다면 우리팀이 작성한 워킹 데드 데모를 확인해보세요.

마무리

여기서 한 것은 Skrollr로 할 수 있는 기능의 겉핥기에 지나지 않습니다. 따라서 가능하다면 다른 많은 기능에 대해서도 꼭 읽어볼 것을 추천합니다. 애니메이션에 이징(easing)을 적용하거나 data 속성과 상수를 조합해서 사용하거나(유지보수에 좋습니다) Skrollr 스타일 시트에 프레임을 추가하는 대신 마크업에 전체 키프레임을 넣는 등 흥미로운 기능이 많습니다.

끝으로 잠깐 몇 마디 덧붙이려고 합니다. 가끔 웹 디자이너인 우리들은 매체의 노예처럼 느껴질 때가 있습니다. 웹에는 제약이 많고 크로스 브라우저와 크로스 디바이스 이슈에서 오는 어려움도 많습니다. 최소한 저는 여러 매체에서 디자인과 개발을 하는 한 사람으로서 간혹 매체의 노예처럼 느껴질 때가 있습니다. 하지만 이러한 브라우저 기반 매체에는 매우 독특한 면이 있는 것도 사실입니다. 예를 들어, 스크롤링 덕분에 사용자와 이런 식으로 인터랙션 할 수 있는 기회가 생겼습니다. 개인적으로 디자이너들은 이런 고유한 특성을 알아두어야 하며, 항상 이러한 특성을 훌륭한 경험으로 창조해 낼 방법을 찾아야 한다고 생각합니다.


이 글은 Elli Bishop이 오페라 개발자 사이트에 게시한 Creating Game-Style Parallax Scrolling: Zombie Edition을 번역한 글입니다.

Posted by 행복한고니 트랙백 0 : 댓글 1

웹앱에는 대용량 데이터나 바이너리 파일을 오프라인으로 저장할 수 있는 기능이 있습니다. 심지어 MP3 파일을 캐시하는 것도 가능합니다. 이미 웹 브라우저 기술은 오프라인으로 데이터를 저장할 수 있으며, 대용량도 저장할 수 있습니다. 문제는 사용할 방법이 파편화되어 있다는 것입니다.

localStorage는 매우 단순한 데이터 저장소이지만, 속도도 느리고 대용량 바이너리 데이터를 다룰 수 없습니다. IndexedDB와 WebSQL은 비동기 식이며 빠르고 대용량도 다룰 수 있지만 API가 직관적이지 못합니다. 심지어 아직도 주요 브라우저 중에는 IndexedDBWebSQL를 둘 다 지원하지 않는 것도 있으며 가까운 시일 내에는 이런 상황이 나아질 것 같지도 않습니다.

만약 오프라인을 지원하는 웹앱을 작성해야 하고, 어디서부터 시작해야 할지 갈피를 못 잡겠다면 이 글이 유용할 것입니다. 오프라인 지원 기능은 한 번도 다뤄본 적이 없지만, 이 기능 때문에 골치 아픈 분이 읽어도 좋습니다. 모질라에서 만든 localForage는 어떤 브라우저에서도 손쉽게 데이터를 오프라인으로 저장할 수 있게 해 주는 라이브러리입니다.

저는 around라는 HTML5 포스퀘어 클라이언트를 개발하며 오프라인 저장소의 문제점들을 경험했습니다. localForage 사용법은 이 글에서 설명하겠지만, 코드를 꼼꼼히 보고 배우는 것을 좋아하는 분들에게는 소스도 공개되어 있습니다.

localForage는 매우 단순한 localStorage API를 사용하는 자바스크립트 라이브러리로서, get, set, remove, clear, length와 같은 기본적인 기능은 물론 다음과 같은 기능도 지원합니다.

  • 콜백을 사용한 비동기 API
  • IndexedDB, WebSQL, localStorage 드라이버 (가장 적절한 드라이버를 자동으로 선택)
  • Blob와 임의의 데이터 타입을 지원하여 이미지, 파일 등 저장 가능
  • ES6 Promises 지원

IndexedDBWebSQL을 포함한 덕분에 localStorage만 사용할 때보다 더 많은 데이터를 저장할 수 있으며, 이들의 비동기적 특성 덕분에 get/set 함수를 호출해도 메인 쓰레드가 느려지지 않으므로 앱이 더 빨라질 수 있습니다. promises를 지원하므로 콜백 수프(callback soup, 콜백이 너무 많아서 프로그램의 흐름을 알기 어렵게 된 상태) 없이 자바스크립트를 작성할 수 있습니다. 물론 콜백을 좋아한다면 localForage는 그 방법도 지원합니다.

얘기는 그만. 어떻게 동작하는지 보여줘!

localStorage API는 여러 경우에 있어 사실 꽤 훌륭합니다. 사용하기 편하고 복잡한 데이터 구조를 만들지 않아도 되고, 단순하여 별도의 코드 조각이나 라이브러리가 따로 필요하지 않습니다. 예를 들어, localStorage를 사용해 앱 설정을 저장하고 싶다면 다음과 같이 작성할 수 있습니다.

// 오프라인으로 저장할 설정값
var config = {
    fullName: document.getElementById('name').getAttribute('value'),
    userId: document.getElementById('id').getAttribute('value')
};
 
// 다음에 앱을 실행할 때 사용하기 위해 설정값을 저장한다.
localStorage.setItem('config', JSON.stringify(config));
 
// 다음에 앱을 실행할 때 다음과 같이 사용할 수 있다.
var config = JSON.parse(localStorage.getItem('config'));

단, localStorage에는 문자열 형식으로 값을 저장해야 하므로 값을 JSON 형태로 혹은 JSON 형태에서 변환해야 합니다.

아주 기분 좋을 정도로 직관적이지만 금세 localStorage에 있는 문제점을 깨닫게 될 것입니다.

  1. 동기식이다. 데이터가 얼마나 크던 데이터를 디스크에서 읽어 들여 해석할 때까지 기다려야 합니다. 이 때문에 앱의 반응성이 느려질 것입니다. 이는 특히 모바일 디바이스에 안 좋은데 데이터를 완전히 읽어 들일 때까지 메인 쓰레드가 대기해야 하므로 여러분의 앱은 느리고 반응성까지 떨어지는 것처럼 보일 것입니다.
  2. 문자열만 지원한다. 반드시 JSON.parseJSON.stringify를 사용해야 합니다. 이는 localStorage가 값으로 자바스크립트 문자열 형식만 지원하기 때문입니다. 숫자도, 불리언도, 대용량 바이너리 데이터 등도 저장할 수 없습니다. 이 때문에 숫자나 배열을 저장하는 작업은 귀찮아지고, 대용량 바이너리 데이터를 저장하는 것은 사실상 불가능합니다(최소한 엄.청. 짜증나고 느릴 것입니다).

localForage를 사용한 더 좋은 방법

localForage는 localStorage의 API를 사용하면서도 비동기식 API를 사용해 이 같은 문제를 극복했습니다. 다음은 같은 데이터를 저장할 때 IndexedDB와 localStorage의 사용법을 비교한 것입니다.

IndexedDB 코드

// IndexedDB.
var db;
var dbName = "dataspace";
 
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];

var request = indexedDB.open(dbName, 2);
 
request.onerror = function(event) {
    // 에러 처리
};
request.onupgradeneeded = function(event) {
    db = event.target.result;
 
    var objectStore = db.createObjectStore("users", { keyPath: "id" });
 
    objectStore.createIndex("fullName", "fullName", { unique: false });
 
    objectStore.transaction.oncomplete = function(event) {
        var userObjectStore = db.transaction("users", "readwrite").objectStore("users");
    }
};
 
// 데이터베이스가  생성되고 나면 사용자를 추가한다
 
var transaction = db.transaction(["users"], "readwrite");
 
// 데이터가 데이터베이스에 모두 저장되었을 때 할 일
transaction.oncomplete = function(event) {
    console.log("All done!");
};
 
transaction.onerror = function(event) {
    // 에러도 빠뜨리지 말고 다루어야 한다
};
 
var objectStore = transaction.objectStore("users");
 
for (var i in users) {
    var request = objectStore.add(users[i]);
    request.onsuccess = function(event) {
        // Contains our user info.
        console.log(event.target.result);
    };
}

WebSQL은 번잡하다 싶을 정도는 아니지만 그래도 기본 코드가 약간 필요하긴 합니다. localForage에서는 다음과 같이 사용하면 됩니다.

localForage Code

// 사용자를 저장한다
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];
localForage.setItem('users', users, function(result) {
    console.log(result);
});

얼마 차이 안나는군요?

문자열 외의 데이터 저장하기

사용자의 프로필 이미지를 다운받은 뒤 오프라인 상태에서 볼 수 있도록 캐시한다고 생각해 봅시다. localForage에서는 바이너리 데이터를 간단하게 저장할 수 있습니다.

// AJAX를 사용해서 사용자의 사진을 다운로드한다
var request = new XMLHttpRequest();
 
// 첫 번째 사용자의 사진 가져오기
request.open('GET', "/users/1/profile_picture.jpg", true);
request.responseType = 'arraybuffer';
 
// AJAX 상태가 변경되면 사진을 로컬에 저장한다.
request.addEventListener('readystatechange', function() {
    if (request.readyState === 4) { // readyState DONE
        //localStorage로는 바이너리 데이터를 있는 그대로 저장할 수 없었을 것이다.
        localForage.setItem('user_1_photo', request.response, function() {
            // 사진이 저장되었다. 다음 단계를 진행하자.
        });
    }
});
 
request.send();

저장한 사진을 가져올 때는 코드 3줄만 있으면 된다.

localForage.getItem('user_1_photo', function(photo) {
    // data URI 등을  만들어 img 태그 등에 사진을 표현한다
    console.log(photo);
});

Callback과 Promise

코드에서 콜백을 사용하고 싶지 않다면, localForage에서 콜백 함수를 인수로 사용하는 대신 ES6 Promise를 사용할 수 있습니다.

localForage.getItem('user_1_photo').then(function(photo) {
    // data URI 등을  만들어 img 태그 등에 사진을 표현한다
    console.log(photo);
});

사실 이 예제는 조금 억지로 만든 감이 있습니다. 그러나 around를 살펴보면 이 라이브러리가 실제로 사용되는 것을 볼 수 있습니다.

크로스 브라우저 지원

localForage는 최근 브라우저를 모두 지원합니다. IndexedDB사파리 외의 모든 최근 브라우저에서 지원하고 (IE 10+, IE Mobile 10+, Firefox 10+, Firefox for Android 25+, Chrome 23+, Chrome for Android 32+, and Opera 15+), 내장 안드로이드 브라우저(2.1+)와 사파리는 WebSQL을 사용합니다.

가장 안 좋은 경우라 해도 localForage는 localStorage를 대비책으로 사용하므로 최소한 기본적인 데이터는 오프라인으로 저장할 수 있게 됩니다(하지만 이 경우 Blob은 저장할 수 없고 많이 느릴 수도 있다). 그러나 적어도 자동으로 데이터를 JSON 문자열로 변환 혹은 JSON 문자열을 데이터로 변환해주는 정도의 이점은 얻을 수 있습니다.

localForage에 대한 자세한 정보는 Github에서 볼 수 있으며 localForage가 해줬으면 하는 기능이 있다면 이슈에 추가해주세요.

이 글은 localForage: Offline Stroage, Improved를 번역한 글입니다. 원문의 라이센스에 따라 CC BY-SA로 공개합니다.


Posted by 행복한고니 트랙백 0 : 댓글 0

http://www.flickr.com/photos/jasonprini/4104210048/

스티브 사우더스씨가 브라우저의 이상한 동작에 대한 글을 작성했습니다. 성능에 관련된 내용인데, 간략한 내용은 다음과 같습니다.

스키마가 없으면 두 번 다운로드
인터넷 익스플로러 7과 8 버전은 http 프로토콜이 빠져있으면 스타일 시트를 두 번 다운로드 합니다. 가끔 "//stevesouders.com/images/book-84x110.jpg" 과 같이 사용하는 경우는 봤는데, 이 경우는 계속 주의해야 할 것 같습니다.

document.write 스크립트가 다운로드 정지의 원인(파이어폭스)
파이어폭스에서는 docuemnt.write를 사용한 스크립트를 읽어들이면 다른 다운로드를 정지시킵니다. 안타까운 사실이지만, document.write는 이미 만들어졌습니다. 이 문제는 페이지 컨텐트에 document.write를 이용해서 광고를 삽입하려고 할 때 수천억배쯤 더 나빠집니다. 대충 이런 식으로 작성하죠.
[code:js]
document.write('<script src="http://www.adnetwork.com/main.js"><\/script>');
다행히 대부분의 최신 브라우저들은 document.write로 추가된 스크립트까지 병렬로 읽어들입니다. 그러나, 몇 주전 파이어폭스 3.6에서 광고를 삽입할 때 이상한 중단 현상을 발견하고, 추적해보았더니 document.write로 추가된 스크립트가 문제였습니다.
document.write 스크립트 테스트 페이지에서 이 문제점을 보여주고 있습니다. 이 페이지에는 4개의 스크립트가 있는데, 첫번째와 두번째 스크립트는 document.write를 사용해 추가되었습니다. 세번째와 네번째 스크립트는 일반적인 방법(HTML과 SCRIPT SRC)을 사용해서 추가했습니다. 그리고 모든 스크립트는 4초가 걸려야 다운로드 할 수 있도록 했습니다. IE8, 크롬4, 사파리4, 오페라 10.10은 페이지를 전부 다운로드 하는데 ~4초가 걸렸습니다. 모든 스크립트는 document.write까지 포함해서 병렬로 처리되었습니다. 반면 파이어폭스에서는 페이지를 전부 다운로드 하는데 12초가 걸렸습니다(2.0, 3.0, 3.6에서 테스트). 첫번째 document.write 스크립트는 1초부터 4초까지 걸렸고, 두번째 document.write 스크립트에 5초부터 8초까지 걸렸습니다. 그리고 나머지 두 개의 일반적인 스크립트를 9초부터 12초까지 다운로드 했습니다.

media=print 스타일시트
인터넷 익스플로러에서는 media="print"를 사용한 스타일시트가 렌더링을 정지시킵니다.
저는 웹브라우저가 현재 사용중이지 않은 미디어 타입의 스타일시트를 건너뛰지 않고 다운로드한다는 사실에 놀랐습니다. 몇몇 웹 개발자들에게 물어보았지만 아무도 이런 동작에 대한 적당한 이유를 대지 못하더군요. 또한 여러분은 media="print" 타입의 스타일시트라 하더라도 Page Speed나 YSlow의 추천에 따라 문서 HEAD에 스타일시트를 두려고 할 것입니다.

동적인 스타일시트
IE에서는 DHTML과 setTimeout을 사용해서 스타일시트를 읽어들이면 렌더링이 멈추는 것을 방지할 수 있습니다. 몇 주전에 유명한 위젯을 만든 회사와 회의를 했습니다. 위젯이 메인 페이지에 주는 영향을 줄이기 위해 그들이 사용한 기술이 바로 다음과 같이 스타일시트를 동적으로 불러들이는 것이었습니다.
[code:js] var link = document.createElement('link'); link.rel = 'stylesheet'; link.type = 'text/css'; link.href = '/main.css'; document.getElementsByTagName('head')[0].appendChild(link);
과거에는 다운로드 중단을 피하기 위해 스크립트를 동적으로 로딩하는 것에만 주의를 기울였습니다. 스타일시트를 동적으로 읽어들일 생각은 미처 하지 못했었죠. 스타일시트로 오면 다운로드 중단은 문제가 되지 않습니다. 스타일시트는 다운로드를 멈추게 하지 않습니다(파이어폭스 2.0는 예외). 스타일시트 다운로드에서 걱정스러운 부분은 스타일시트를 모두 다운로드 하기 전까지 렌더링을 멈추는 IE의 특성입니다. 다른 브라우저에서는 스타일이 적용되지 않은 컨텐트가 잠깐 나왔다가 사라지는 현상(Flash Of Unstyled Content, FOUC)이 생길 것입니다.

배경이미지 예측
크롬과 사파리는 스타일시트가 전부 준비되기 전이라도 배경 이미지의 다운로드를 시작합니다. 배경 이미지 스타일이 재정의 되는 경우라면 쓸데없는 다운로드가 늘어나는 셈입니다.
직장 동료인 스티브 램이 이런 동작을 저에게 알려주었는데, 이 얘기를 들었을 때 제가 처음 했던 말은 "완전 낭비잖아!" 였습니다. 개인적으로 프리페칭(prefetching)을 좋아하기는 하지만, 대부분의 프리페칭 기능은 너무 공격적이어서 크게 좋아하지 않습니다. 사용하지 않아도 될 다운로드 리소스를 소모하는 일이 잦기 때문입니다. 이 얘기를 처음 들은 이후에 조금 더 생각해봤습니다. 예측된 배경 이미지의 낭비는 얼마나 자주 일어날까? 검색해봤더니 유명 웹 사이트에서는 재정의된 배경 이미지 스타일이 없더군요. 하나도요. 그런 페이지가 하나도 없다고 말할 수는 없겠지만, 매우 이례적인 경우인 것만은 확실합니다.
다르게 생각하면 이러한 배경 이미지 예측 다운로드는 사용자가 인지하는 페이지 속도와 성능을 개선시킬 것입니다.

from Souders blast off 5 in a row
Posted by 행복한고니 트랙백 1 : 댓글 3
Ajaxian에 모처럼 마음에 드는 프로그램이 나타났습니다. TinyMCE로 유명한 Moxiecode에서 Plupload라는 범용 업로드 컴포넌트를 릴리스 했습니다. 이 컴포넌트는 각종 플러그인을 활용해서 가능한 풍부한 사용자 경험을 만들어냅니다. HTML5, Google Gears, Silverlight, Flash, Yahoo! Browser Plus 등을 사용해서 드래그 & 드롭(탐색기에서 웹 브라우저로 바로 드래그하여 파일 업로드 가능), 업로드 프로그레스 바, 클라이언트 측 이미지 리사이즈가 가능합니다.
사용하는 백엔드에 따라서 기능이 조금 다를 수 있습니다. 모든 기능을 지원하는 백엔드는 Google Gears입니다.

CoreAPI는 다음과 같이 사용할 수 있습니다.
[code:js]
var uploader = new plupload.Uploader({         runtimes : 'gears,html5,flash,silverlight,browserplus',         browse_button : 'pickfiles',         max_file_size : '10mb',         resize : {width : 320, height : 240, quality : 90},         url : 'upload.php',         flash_swf_url : '/plupload/js/plupload.flash.swf',         silverlight_xap_url : '/plupload/js/plupload.silverlight.xap',         filters : [                 {title : "Image files", extensions : "jpg,gif,png"},                 {title : "Zip files", extensions : "zip"}         ] });
jQuery의 Queue Widget을 사용했다고 합니다.

from Plupload: A rich upload exprerience on pluggable backends
Posted by 행복한고니 트랙백 0 : 댓글 0
플래시 플러그인없이 플래시 파일(swf)을 실행할 수 있을까요? 어찌보면 황당하다고 할 수도 있을 이런 시도를 한 사람이 있습니다(그리고 성공했습니다). 토바이스 슈나이더(Tobias Schneider)는 브라우저에서 바로 실행할 수 있는 Gordon이라는 이름의 플래시 런타임을 작성했습니다. 이 런타임은 순수하게 자바스크립트와 HTML5 만으로 작성되었으며, 오픈소스이고, MIT 라이센스로 배포됩니다. 배포는 GitHub에서 하고 있습니다.

Gordon의 데모는 여기서 볼 수 있습니다.(데모는 폴 아이리쉬님이 제공해주셨습니다)

Gordon은 최신 버전의 파이어폭스, 크롬, 사파리에서 동작합니다. 그리고, 아이폰 사파리에서도 잘 동작합니다(다만 blue 데모의 경우 제 아이폰 3GS에서는 일부 효과가 다소 느려보입니다).  이 런타임은 "git clone git://github.com/tobeytailor/gordon.git" 명령을 통해 직접 설치해볼 수 있습니다. 데모가 file:// 로 시작하는 URI에서는 동작하지 않는다고 하므로, 웹 서버를 설치하거나 웹 호스팅을 사용해야 할 듯 합니다.

아직까지 문서가 충분히 많지는 않으므로, Gordon의 지원 기능이나 개발 방향에 대해서는 아직 명확하게 알 수 없습니다. 하지만, 개방형 웹 기술에 큰 방점을 찍은 것만은 사실로 보입니다.
[code:js]
<body onload="new Gordon.Movie('trip.swf', {id: 'stage', width: 500, height: 400})">
    <div id="stage"></div>
</body>

from Gordon: Flash Runtime Implemented in Javascript
Posted by 행복한고니 트랙백 1 : 댓글 4
구글 크롬에는 V8 디버거라는 자바스크립트 디버거도 있습니다만, 이번에 새롭게 크롬 개발툴 프로토콜(Chrome Dev Tools Protocol)이라는 것을 선보였습니다.
V8 디버거는 자바스크립트 디버깅만 할 수 있었고, 하나의 V8 가상 머신에서만 동작했었습니다. 사실 하나의 구글 크롬 인스턴스에는 렌더링 프로세스에 따라 한 개 이상의 V8 가상 머신이 존재할 수 있습니다. 게다가 브라우저 탭에서 URL을 가져오거나 DOM 트리를 탐색하고 수정하는 작업은 자바스크립트 만으로는 다룰 수 없습니다.
이러한 제약때문에 원격 디버거와 디버깅되는 프로세스간에 추가 정보를 주고받을 수 있는 크롬 개발툴 프로토콜을 만들었습니다. 크롬 개발툴 프로토콜은 현재의 V8 디버거 프로토콜을 비롯한 다른 디버깅 관련 프로토콜의 전송 수단으로도 사용할 수 있습니다.
이 프로토콜을 사용해서 만든 도구 중 하나가 바로 이클립스 기반의 디버거입니다.
원격 디버거에 대한 노력은 그 밖에도 많이 있습니다. 한가지 예로, 오페라 드래곤플라이의 디버깅 및 검사 아키텍쳐인 스코프 인터페이스를 들 수 있습니다. 파이어버그 역시 웹 디버그 프로토콜을 가지고 있으며, ActiveState 에서 사용하던 전통있는 DBGP도 있습니다.

from Google Chrome Eclipse Debugger

Posted by 행복한고니 트랙백 0 : 댓글 0
Michael Ventnor씨가 HTML5에 정의된 체크박스와 라디오버튼의 미정 상태[각주:1]를 구현하고 이에 대한 글을 작성하셨습니다:
웹 개발을 해보셨다면, 체크박스와 라디오 버튼은 선택했거나 혹은 선택하지 않은 두 가지의 상태가 있다는 것을 아실 것입니다. 하지만, 체크박스의 경우 두 가지 상태 중간의 미정 상태를 사용하고 싶을 수도 있습니다. 예를 들면, 모든 체크박스가 하나의 상태(선택됐거나 선택되지 않은 것 중 하나)를 가질 때, 그를 표현할 수 있는 마스터 체크박스의 경우가 있습니다(아래 그림 참고). HTML5의 "미정 상태" DOM 속성을 구현한 덕분에 이 기능은 Trunk (Firefox 3.2) 에서 사용할 수 있습니다. 다음 코드면 충분하죠.
[code:js]
document.getElementById(”check1").indeterminate = true;
아래 그림을 보시면 애매하게 선택된 체크박스를 확인할 수 있습니다(첫번째 그룹의 "CheckAll" 체크박스). CSS3의 :indeterminate 수도 클래스(pseudoclass)는 아직 지원하지 않습니다.

from document.getElementById(”check1″).indeterminate = true; Now shipping in Firefox Trunk
  1. 영어로는 indeterminate로, 체크박스를 여러번 클릭하면 나타나는 선택한 것도 아니고 선택하지 않은 것도 아닌 상태를 뜻합니다. 우리말로는 정확한 번역이 없어 "정해지지 않았다"는 뜻으로 "미정(未定) 상태"라 번역했습니다. [본문으로]
Posted by 행복한고니 트랙백 0 : 댓글 0
Brad Neuberg씨가 이번에 iPhone에 포함된 두 가지 근사한 HTML5 기능에 대해 알려주셨습니다.

Safari 자바스크립트 데이터베이스 프로그래밍
HTML5 스펙에는 클라이언트 데이터 저장을 위한 새로운 메커니즘이 있습니다. 바로 자바스크립트 데이터베이스 지원입니다. HTML 5는 현재 Web Hypertext Application Technology Working Group(WHATWG)가 개발하고 있습니다.

자바스크립트 데이터베이스 지원은 Safari 3.1 이후부터 가능하며, iPhone OS는 2.0 이후부터 가능합니다.

HTTP 쿠키가 허용하는 용량보다 많은 데이터를 사용자의 로컬 컴퓨터에 저장하려는 웹 개발자라면 이 문서를 꼭 읽어보셔야 합니다.

HTML5 응용프로그램 캐시
구글의 Michael Nordman씨가 "이건 아직 Safari나 iPhone에서 지원하지 않습니까?"라고 물었습니다.

David Kilzer씨가 이에 대한 답변을 남겼습니다. "이 기능은 iPhone OS 2.1에 포함되었습니다. iPhone에 설치된 Safari에서 '+' 버튼을 눌러 '홈 스크린에 추가하기'를 사용하면, (HTML5 스펙별로) manifest가 정의된 웹 응용프로그램이 다른 캐시된 리소스와 함께 저장될 것입니다. 이 기능이 동작하려면 manifest 파일이 '반드시' 정확한 MIME 타입으로 제공되어야 합니다.

Mac OS X에는 아직 이 기능을 지원하는 Safari를 탑재하지 않았습니다."

from HTML5 Features in latest iPhone; Application Cache and Database
Posted by 행복한고니 트랙백 0 : 댓글 0

IE='\v'=='v'

2009.01.30 19:29 from [IT] Web Tech
[code:js]
IE='\v'=='v'
IE인지 확인하는 가장 짧은 방법입니다(8버전 포함). 다른 방법으로는 아래의 코드도 있습니다.
[code:js]
IE=top.execScript?1:0
흠 :)
Gareth Heyes씨는(v 트릭을 만든 사람) 이 트릭에 대해 포스팅하면서 여러 브라우저를 판별할 수 있는 한 줄 코드를 만들었습니다.
[code:js]
B=(function x(){})[-5]=='x'?'FF3':(function x(){})[-6]=='x'?'FF2':/a/[-1]=='a'?'FF':'\v'=='v'?'IE':/a/.__proto__=='//'?'Saf':/s/.test(/a/.toString)?'Chr':/^function \(/.test([].sort)?'Op':'Unknown'

국내 PHP 개발자 사이트인 PHP 스쿨에도 오늘자로 관련 팁이 게시되었습니다.

from IE='\v'=='v'
Posted by 행복한고니 트랙백 0 : 댓글 0
웹 어플리케이션을 보호하기 위해 많은 사람들이 즐겨쓰는 유리 방패가 CAPTCHAS 입니다. 일그러지고 찌그러진 글자를 표시해서 사용자로 하여금 그 글자를 입력하도록 한 다음에 폼을 전송하거나 데이터에 접근할 수 있게 합니다. 이 방법은 보통의 사용자들을 귀찮게 하고, 시각장애인이나 실독증 환자는 아예 접근이 불가능합니다. 이 방법은 또한 우리가 생각하는 것만큼 안전하지도 않습니다. PWNtcha는 OCR과 백엔드 시스템을 사용해서 여러  CAPTCHAS들을 계속해서 크랙하고 있습니다.

정말로 놀라운 것은 자바스크립트와 Canvas만 가지고도 크랙이 가능하다는 것입니다. ShaunF씨는 파일 호스팅 사이트인 Megaupload의 CAPTCHAS 암호를 자동으로 해독하는 GreaseMonkey 스크립트를 작성하셨습니다. 데모는 여기서 확인하실 수 있습니다.

John Resig씨가 이 스크립트를 분석하고 설명한대로, 몇 가지 재밌는 방법이 사용되었습니다.
  1. HTML 5 Canvas의 getImageData API를 CAPTCHA 이미지에서 픽셀 데이터를 가져오기 위해 사용했습니다. Canvas는 이미지를 포함할 수 있고, 그로부터 픽셀 데이터를 추출할 수 있습니다.
  2. 스크립트는 순수 자바스크립트로 작성된 신경망 구현체를 포함하고 있습니다.
  3. Canvas를 사용해 이미지로부터 추출한 픽셀 데이터는 신경망으로 전달되어 정확한 문자로 분리하고, 이 문자들은 광학 문자 인식(OCR)에서 사용됩니다.
사실, Megaupload의 CAPTCHA는 매우 기본적입니다. 그렇지만, 자바스크립트로 그것을 크랙할 수 있다는 것은 여전히 인상적입니다. getImageData API는 좀 더 살펴봐야 할 것 같습니다.

from Captcha cracking in JavaScript with Canvas and neural nets
Posted by 행복한고니 트랙백 1 : 댓글 0
[code:js]
typeof maybeArray ==  "Array"; // 에, 농담이시죠?
maybeArray instanceof Array; // 어쩌면요
maybeArray.constructor == Array; // 어쩌면요
typeof maybeArray.sort == 'function'; // 비슷합니다
Object.prototype.toString.call(maybeArray) === '[object Array]'; // 딩 동 댕

자바스크립트를 처음 접하는 사람들은 어떤 객체가 실제로 배열인지 아닌지 알아내는 간단한 방법이 있을 것이라고 생각합니다. 고수들은 저 너머에 있는 위험을 알고 있습니다. 특히, 프레임 사이를 탐색하며 작업할 때의 문제를 말이죠. 자바 진영은 클래스 로더에 이런 문제가 있었는데, 다른 클래스 로더로부터 객체를 가져올 때 이상한 문제가 일어나곤 했습니다.

Kangax씨는 이 문제에 대해 자세히 다루며 기술적으로 새로운 해결책을 제시했습니다. 덕분에 Prototype과 그 외의 라이브러리들이 그들의 코드를 덕 타이핑에서 다음 코드로 바꿀 수 있게되었습니다.

[code:js]
function isArray(o) {
    return Object.prototype.toString.call(o) == '[object Array]';
}

from isArray: Why is it so bloody hard to get right?
Posted by 행복한고니 트랙백 1 : 댓글 3

alert()에게 작별을

2008.10.17 07:44 from [IT] Web Tech
Blackbird는 G. Scott Olson씨가 작성한 JavaScript 로깅 라이브러리로 "Blackbird에게 환영을, alert()에게 작별을" 이라는 캐치 슬로건을 가지고 있습니다. 그 슬로건이야말로 Blackbird 라이브러리가 목표하는 바를 명확히 알 수 있게 해줍니다. 자바스크립트에서 메시지 로깅을 아주 쉽게 해주는 것입니다. 그리고 설치도 매우 쉽습니다. 아래 몇 줄의 코드만으로, 관심있는 특정 데이터를 표현해주는 체크포인트를 설정할 수 있게 됩니다.
[code:html]
<html>
    <head>
        <script type="text/javascript" src="/PATH/TO/blackbird.js"></script>
        <link type="text/css" rel="Stylesheet" href="/PATH/TO/blackbird.css" />
        ...
    </head>
</html>
콘솔에 결과를 보내는 Blackbird의 메소드를 사용하는 것은 매우 간단한 일입니다.
[code:js]
log.debug( 'this is a debug message' );
log.info( 'this is an info message' );
log.warn( 'this is a warning message' );
log.error( 'this is an error message' );
이러한 공개 메소드들이 보내진 메시지들의 유형을 쉽게 파악할 수 있도록 고유한 방식으로 데이터를 표현한다는 것도 장점입니다.
Blackbird는 다음 브라우저들에서 테스트되었습니다.
  • Internet Explorer 6+
  • Firefox 2+
  • Safari 2+
  • Opera 9.5
Brian Dillard씨는 이번 릴리즈를 처음 알린 분들 중 한 명으로 Blackbird에 대한 좋은 글을 쓰기도 하셨습니다.

from Say Goodbye to alert()
Posted by 행복한고니 트랙백 2 : 댓글 3

런던에서 열린 Google Developer Day에서 Harry Guillermo씨가 JavaScript로 팩맨을 만드셨습니다.

from Pacman bites back
Posted by 행복한고니 트랙백 1 : 댓글 0

티스토리 툴바