아티클

[개발일지] 성능 최적화하기

2011-01-05 11:19:26

"성능 최적화"라는 말이 상당히 거창하게 느껴지지만 더 쉽게 말해본다면 "웹 페이지 로딩 속도 빠르게 하기"나 "사용자 체감 속도 빠르게 하기"라는 말로도 풀이할 수 있을 것 같습니다. 웹 사이트의 로딩 속도에 영향을 주는 요소는 PC 사양이나 브라우저와 같은 클라이언트 환경, 서버 세팅, 불러오는 파일의 갯수, 용량 등 굉장히 다양합니다. 실제로 이런 요소들이 복합적으로  모여 성능에 영향을 준다고 할 수 있습니다. 이 중에서도  마크업 개발을 진행하면서 신경을 쓸 수 있는 부분은 이미지 갯수, HTML/CSS 작성 방법이 있을 수 있겠습니다. 특히, 네이버me 마크업 개발 당시 유독 신경을 많이 썼던 CSS Sprite와 CSS Minify에 대해 구체적으로 이야기해보려고 합니다.

CSS Sprite

CSS Sprite 기법은 아래 그림에서 볼 수 있는 것 처럼 여러개의 이미지를 하나로 모아 이미지 파일의 리퀘스트를 줄이는 방법입니다. 사용자 입장에서는 세개나 받아야 했던 이미지를 한개만 받으면 되니 이미지를 불러오는 시간이 단축됩니다. 이미지 여러개를 하나로 합치면 파일 용량이 커지진 않을까 하는 우려가 들수도 있지만, 실제로 세개의 이미지를 합친 한개의 파일 용량은 개별 이미지의 파일 용량을 더한 값보다 작아서 용량을 줄이는 효과도 동시에 얻을 수 있습니다.

1.gif

규칙을 세우는 것이 중요합니다.

막상 CSS Sprite를 적용하기로 마음먹었다면 여러가지 세워야 할 규칙들이 많이 있습니다. 초반에 규칙을 잘 세워놓으면 마크업 개발이 중반쯤 되었을 때부터 발생할 수 있는 문제를 미리 예방할 수가 있기 때문입니다.  이미지가 너무 커지거나 퀄리티가 저하되는 것을 방지하기 위해 이미지 유형별로 배경, 버튼, 블릿(또는 아이콘), 숫자, 텍스트로 분류를 하였습니다. 그리고 파일명을 다음과 같이 정하였습니다. (파일의 확장자가 PNG인것은 PNG8 형식이 GIF 형식보다 용량이 더 적기 때문입니다.)

  • 배경 : bg_sp.png
  • 버튼 : btn_sp.png
  • 블릿(아이콘) : bu_sp.png
  • 숫자 : num_sp.png
  • 텍스트 : tx_sp.png

그 다음에는 이미지 배치 방법을 정합니다. 기본적으로 포지션값을 쉽게 파악할 수 있도록 하나의 이미지는 최소 20*20 크기의 공간을 가지고 있어야 합니다.

버튼/배경/텍스트일 경우의 이미지 배치
2.gif

숫자일 경우 이미지 배치
3.gif 

블릿(아이콘)일 경우 이미지 배치
4.gif

다른 이미지는 가로/세로 너비의 제한이 없지만 블릿의 경우 40px로 너비가 제한이 되어 있는 것은 이미지의 오른쪽에 다른 이미지가 있어서는 안되거나, 왼쪽에 다른 이미지가 없어야 하는 경우가 있기 때문입니다. 

5.gif

무엇이, 얼마나 줄었을까?

네이버me에서 사용된 이미지에 전부 Sprite를 적용한 것은 아니지만 위에서 언급한 다섯개의 파일(bg_sp.png, btn_sp.png, bu_sp.png, num_sp.png, tx_sp.png)에 대한 적용 효과는 다음과 같습니다. (2010년 12월 9일 기준)

6.gif

CSS Sprite 기법을 쓰지 않았다면 이미지를 불러오기 위해 584번의 리퀘스트가 요청될 것이고 그만큼의 시간이 필요했겠지만, Sprite를 적용함으로써 5번의 리퀘스트만으로 584개의 이미지를 확인할 수가 있게 된 것입니다. 이미지가 많이 필요한 사이트일 수록 그 효과는 더 크게 나타날 것입니다.

마크업은 어떻게 달라져야 할까?

CSS Sprite 기법을 적용하면 이미지를 모두 배경으로 처리해야 하기 때문에 마크업 방식에도 변화가 생기기 마련입니다. 그 중 버튼과 메뉴, 이미지 숫자에 대해 어떤 마크업이 적용되었는지 간단히 소개해보도록 하겠습니다.

버튼
네이버me에서 사용되는 버튼은 단순 인터랙션에 사용되는 것과, 폼 값을 전송할 때 사용되는 것 모두 <button> 엘리먼트로 마크업 하였습니다.

<button> 태그 바깥을 감싸고 있는 <span> 태그는 버튼을 눌렀을 때 이미지가 움직이는 것을 방지하기 위함이고, <button> 태그 안쪽에 있는 <span> 태그는 대체 텍스트 제공을 위한 것입니다. 구체적인 설명은 NULI에 있는 "Button의 브라우저별 랜더링 비교, 사용방법, 문제점, 해결방법(http://html.nhncorp.com/blog/2038)"을 참고하세요.

<span class="btn_sv"><button type="submit" title="저장하기"><span class="blind">저장하기</span></button></span>
<span class="btn_del"><button type="button" title="삭제"><span class="blind">삭제</span></button></span>

 그리고 CSS는 아래와 같이 선언하게 됩니다.

.btn_sv{display:inline-block;width:60px;height:25px;background:url(btn_sp.gif) no-repeat -40px -60px}
.btn_sv button{width:60px;height:25px;border:0;background:none;cursor:pointer}

만약, 버튼의 상태(마우스 오버, 비활성 등)에 따라서 이미지가 변경되어야 한다면,  배경 이미지의 포지션만 변경시키는 클래스를 추가해줍니다.

.btn_sv_ovr{background-position:-40px -100px}
.btn_sv_dis{background-position:-40px -140px}

메뉴
메뉴를 마크업 할 때는 각 메뉴를 구분할 수 있는 클래스(m~m5)를 생성하고 선택된 메뉴를 다르게 표시 하는 클래스(on)도 함께 선언합니다. 이 때에도 버튼과 마찬가지로 대체 텍스트는 반드시 넣어줍니다.

<li class="m"><a href="#" class="on"><span class="blind">미투데이</span></a></li>
<li class="m2"><a href="#"><span class="blind">블로그</span></a></li>
<li class="m3"><a href="#"><span class="blind">메일</span></a></li>
<li class="m4"><a href="#"><span class="blind">쪽지</span></a></li>
<li class="m5"><a href="#"><span class="blind">문자</span></a></li>

그리고 CSS에서는 각 메뉴에 따라 달라지는 이미지를 포지션 값만 변경해서 선언해줍니다.

.ccator_sel li{float:left}
.ccator_sel li a{display:block;width:100%;height:28px;background:url(btn_sp.png)undefinedundefinedundefined}
.ccator_sel li.m a{width:53px;background-position:-240px -380px}
.ccator_sel li.m a.on{width:84px;background-position:-240px -420px}
.ccator_sel li.m2 a{width:46px;background-position:-300px -380px}
.ccator_sel li.m2 a.on{width:83px;background-position:-340px -420px}
...

사실, 위에 제시된 방법으로 마크업을 진행했을 경우 고대비 모드에서 컨텐츠 확인이 어려운 문제가 있습니다.
그렇지만 고대비에서도 문제가 없도록 마크업을 진행한다면, 키보드 포커스 표시가 제대로 되지 않는 문제가 또 다른 발생할 수 있어 현재로서는 고대비 모드에서의 확인이 어려운 대신, 키보드 포커스 표시가 될 수 있도록 마크업을 진행하였으며, 나중에는 두마리 토끼를 다 잡는 방안으로 고민하여 어떠한 장애 환경에도 문제가 되지 않도록 개선될 필요가 있습니다.

7.gif

숫자
숫자의 경우는 키보드 포커스 표시에 영향을 받지 않기 때문에, 고대비에서도 문제가 되지 않는 방법으로 마크업을 진행합니다. 먼저 2010.04라는 대체 텍스트를 반드시 제공해주고, 각각의 숫자는 <span> 엘리먼트를 이용하여 독립적으로 컨트롤 할 수 있게 마크업 합니다.

2010.04
<span class="nums"><span class="n2"></span><span class="n0"></span><span class="n1"></span><span class="n0"></span><span class="d"></span><span class="n0"></span><span class="n4"></span></span>

위의 HTML에서 모든 숫자를 감싸는 nums라는 <span>이 있습니다. nums 가 하는 역할은 모든 숫자를 하나의 덩어리로 묶어 이미지 숫자가 대체 텍스트를 가릴 수 있도록 해줍니다. 따라서 nums에 선언된 스타일에는 절대위치값(position:absolute)이 잡혀 있습니다.  그리고 배경 이미지의 포지션 값을 변경하여 각기 다른 이미지 숫자를 표시해줍니다.  

.nums{position:absolute;top:0;left:0}
.nums span{display:inline-block;width:13px;height:18px;background:url(num_sp.gif)undefinedundefinedundefinedundefinedundefinedundefinedundefined}
.nums span.n0{background-position:0 0}
.nums span.n1{background-position:0 -20px}
...
.nums span.d{background-position:0 -200px}

 

반드시 고려해야 할 점

CSS Sprite 기법이 성능에 좋은 영향을 미치는 것은 너무 당연하지만 위에서 살짝 언급하였던 것 처럼 경우에 따라서는 접근성이 문제가 되어 더 많은 시간과 고민이 필요할 수도 있고, 그 외에 발생할 수 있는 문제로는 다음과 같은 것이 있습니다.

첫번째는 이미지의 퀄리티입니다.
GIF나 PNG8 형식을 사용할 경우, 한 이미지당 표현할 수 있는 컬러의 숫자는 256으로 제한됩니다. 따라서 다양하고 많은 이미지를 하나의 이미지에 합치게 되면 이미지가 원래 가지고 있는 색상을 제대로 표현하지 못하는 경우가 발생합니다.

8.gif

만약, 이미지의 퀄리티가 어느정도 보장되어야 한다면 PNG24형식의 Sprite 이미지를 별도로 생성하는 것이 좋습니다. 물론 PNG24 형식은 IE6에서 투명모드를 지원하지 않으니 투명한 부분이 없도록 하거나, filter 속성을 사용해야 합니다.

두번째는 이미지의 크기입니다.
이미지기의 갯수를 줄이기 위해 하나의 이지에 계속 합치다 보면 이미지의 크기는 커지기 마련입니다. 사실 얼마전까지만해도 이미지의 크기가 영향을 미칠 것이라고는 생각하지 못했는데요, 모바일 환경(특히 iOS)을 고려하다보니 이미지 크기에도 제한이 있다는 사실을 발견했습니다. iOS의 경우는 GIF, PNG 형식의 이미지 파일에 대해 너비와 높이를 곱한 값이 1024x1024x3(3,145,728)을 넘지 말아야 한다고 합니다. 이와 관련된 더 자세한 내용은 NULI의 "iOS에서의 이미지 사이즈 제한(http://html.nhndesign.com/blog/17978)" 에서 확인하실 수 있습니다. iOS 외에도 어떤 환경에서 어떤 제한이 있을지 모르니 이미지 크기가 너무 커지지 않도록 유의하는 것이 좋겠습니다.

CSS Minify

CSS 작성 시 불필요한 선언을 하지 않는 것 역시 파일의 용량을 줄이고, 렌더링 속도를 빠르게 할 수 있는 방법 중 하나입니다. 또한 깔끔하고 군더더기 없는 코드는 다른 사람이 보기에도 좋을 것입니다. (개인적으로 다른 사람이 봤을 때도 쉽게 이해할 수 있는 코드가 잘 짜여진 코드라고 생각합니다.  그러긴 쉽지 않지만 말입니다.^^;) 물론 구글에서 만든 Page Speed(http://code.google.com/speed/page-speed/)나 야후의 YUI Compressor(http://developer.yahoo.com/yui/compressor/)와 같은 툴을 이용하여 CSS 코드를 최적화 할 수 있습니다. 특히 Page Speed의 경우에는 Firefox 에드온도 재공하고 있어 사용하기가 참 편리합니다. 하지만 툴에 전적으로 의존하는 것보다는 어떤 부분을 신경써서 용량을 줄이고 속도를 빠르게 할 수 있는지 인식해서 내가 짠 코드가 자연스럽게 깔끔해지도록 하는 것이 더 좋지 않을까 생각합니다.

불필요한 문자는 쓰지 말자!

NHN 마크업 코딩 컨벤션에는 CSS 코드 작성 시 불필요한 문자는 쓰지 않도록 다음과 같은 규칙을 정해놓고 있습니다. 구체적인 예시는 컨벤션 문서(http://html.nhncorp.com/markup_convention)를 참고하실 수 있습니다.

  • 마지막 세미콜론은 삭제합니다.
  • 들여쓰기는 하지 않습니다.
  • 쉼표로 구분되는 선택자 간, 속성 간 공백은 제거합니다.
  • 중괄호 좌우의 공백은 제거합니다.
  • 의미있는 코드를 구분하기 위해 빈 행을 허용하지만  1줄은 초과하지 않습니다.
  • 선택자간에는 줄바꿈을 하지 않습니다.
  • 속성값을 축약합니다. (#555555 -> #555, 0px -> 0)

전체 코드가 길면 길 수록 위 규칙을 지켰을 때 줄어드는 파일 용량은 커질  것입니다. 처음에는 익숙치 않아서 한두개씩 빠드리기도 하지만 어느새 자연스러워지게 됩니다. 네이버me 역시 컨벤션을 잘 준수하여 불필요한 문자는 선언되지 않도록 하였습니다.

선택자는 간단 명료하게!

선택자를 선언하는 방법은 속도에 영향을 미칩니다. 선택자를 어떻게 선언해야 속도를 개선할 수 있는지에 대해서는 Mozilla Depveloper Center의 "Writing Efficient CSS for use in the Mozilla UI(https://developer.mozilla.org/en/CSS/Writing_Efficient_CSS)"에서 자세하게 다루고 있습니다. 간단히 말하면, 사용빈도가 낮은 ID 선택자에 스타일을 적용하는 시간은 상대적으로 빠르고 모든 엘리먼트를 가리키는 공통 선택자(*)가 스타일을 적용하는 시간은 오래 걸리게 됩니다. 따라서 공통 선택자의 사용은 지양하고 스타일을 빨리 적용할 수 있도록 선택자를 선언하는 것이 좋습니다.

선택자를 간단하게 선언할 수 있도록 하기 위해서는 페이지 내에 있는 오브젝트가 철저하게 구분될 수 있는 네이밍이 필요합니다. 그래서 네이버me에서는 각 영역별로 아래와 같은 네이밍 규칙을 세웠습니다.

 9.gif

만약, 캘린더에 사용되는 목록과 커뮤니케이션 캐스트에 사용되는 목록을 모두 동일한 클래스명(.lst)으로 사용하였다면 선택자는 최소 두개가 필요할 것입니다. 그렇다면 브라우저는 #section_ccast라는 오브젝트에 먼저 접근을 하고 그 다음에 .lst 클래스가 선언된 오브젝트를 가리키게되어 총 두 단계가 필요합니다.

  • #section_ccast .lst
  • #section_ccator .lst

그렇지만 위 그림에 제시된 것 처럼 각 영역별로 구분된 네이밍을 사용한다면 하나의 선택자만으로도 해당 엘리먼트를 가리킬 수 있고 브라우저는 지정된 엘리먼트에 바로 접근이 가능합니다.

  • .lst_ccast
  • .cld_lst

첫 부분에 이야기한 것 처럼, 웹 사이트의 성능은 단순히 이미지의 갯수를 줄이고 CSS를 최적화하는 것 만으로 급격하게 좋아지진 않습니다. 그러나 웹 사이트를 만드는 모든 사람들이 각자 자기가 맡은 분야에서 성능 최적화에 힘쓴다면 마크업 개발자가 한 작은 노력도 분명 영향을 미치게 될 것입니다. 따라서, 지금 작성하는 코드 한 줄, 지금 막 만든 이미지 한개도 그냥 지나치지 않는 세심한 관심이 필요합니다.^^

댓글 0
댓글을 작성하려면 해주세요.