스프라이트 이미지는 여러 개의 작은 이미지를 하나의 이미지 파일로 결합한 것이다.
주로 아이콘이나 버튼, 배경 등 작은 이미지들을 포함하고 있으며 각 이미지의 위치와 크기 정보를 통해 필요한 이미지만을 표시하는 기법을 통틀어 스프라이트
라고 일컫는다.
이 기법을 활용하여 웹 페이지나 게임에서 필요한 각각의 이미지를 별도로 요청하지 않고 단일 이미지로 요청할 수 있는데, 이에 따라 이미지를 불러 올 때 발생하는 네트워크 요청 수를 줄일 수 있다.
Sprite 라는 단어가 본래 영어로 "요정"이나 "정령"을 의미하고 있어, 컴퓨터 그래픽스 분야에서는 스프라이트가
배경 이미지를 덮어 쓰지 않고 배경 이미지 위에 "떠다니는"
현상으로부터 차용된 것으로 보인다(출처: wikipedia).
이 포스트에서는 위와 같은 스프라이트 이미지를 웹 페이지에서 사용하는 방법에 대해 정리해 보았다.
사용할 이미지는 총 416×96 px 크기 이미지로, 한 아이콘 당 16×16 px 크기를 가지도록 만들어졌다. (중간의 "START", "SELECT" 아이콘 제외, 해당 아이콘만은 32×16 px)
우선, 가장 간단한 방법으로는 CSS의 background-position
속성을 활용해 개별 아이콘을 구별할 수 있다.
여기서는 아이콘을 보여줄 부분에 "icon"이라는 클래스를 만들어 공통적으로 적용하였고, 그 후 이미지에서 아이콘이 속한 위치에 따라 x/y 위치 값을 지정한 "dark-X"와 "light-X" 라는 클래스를 개별 적용하였다.
이렇게 하면 우리가 원하는 대로 보여줄 수는 있지만, 만약 a-z까지 그리고 0-9까지 모든 아이콘에 대해 클래스를 정의해야 한다면 무척이나 번거롭고 피곤한 작업이 될 수 있다.
나의 경우에는 모든 아이콘의 크기가 동일했기 때문에 위로 이동하든 아래로 이동하든 position을 16px씩만 움직이면 된다는 사실이 자명했다. 그리하여 SCSS의 반복문을 활용해 여러 아이콘의 클래스를 정의하는 방법에 대해 찾아보게 되었다.
Sass 문법에서 제공하는 여러 기능을 활용해, 동적으로 각 아이콘을 보여줄 수 있도록 하는 여러 클래스를 만들어 보자.
목표:
letter-dark-X
,letter-light-X
,number-dark-X
,number-light-X
,button-X
,bubble-X
형식의 이름을 갖는 클래스를 동적으로 생성하기
반복을 수행할 수 있도록 아이콘 이름으로 이루어진 리스트를 만들어 준다. 스프라이트 이미지를 살펴 보면 1~2번째 줄에 알파벳이, 3번째 줄에 버튼이, 4~5번째 줄에 숫자가, 6번째 줄에 말풍선이 있다. 이렇게 각 행을 기준으로 리스트 변수를 만들어보고자 한다.
변수는 다음과 같이 $
기호를 사용해 선언할 수 있다.
$letters: a b c d e f g h i j k l m n o p q r s t u v w x y z;
$buttons: a b start start select select up down left right;
$numbers: 0 1 2 3 4 5 6 7 8 9 bang;
$bubbles: dollar question devil x ellipsis check bang heart;
반복문 종류에는 @each
, @for
, @while
이 있다. 여기서는 각 항목에 대해 index 값이 필요하기 때문에 @for
문을 활용하였다.
@for $i from 1 through length($letters) {
$letter: nth($letters, $i);
.letter-dark-#{$letter} {
// ...
}
}
이 코드는 반복문을 사용해 $letters
에 담긴 각 문자에 대해 "letter-dark-X" 라는 이름의 클래스를 동적으로 생성한다.
@mixin
은 재사용 가능한 스타일을 정의하기 위한 기능이다. 아이콘의 위치를 계산하기 위해 아래와 같은 믹스인을 생성하였다.
@mixin icon-sprite($offset, $line, $width: 1) {
$offset: $offset - $width;
$line: $line - 1;
background-position: 16px * -$offset 16px * -$line;
}
필수 매개변수 $offset
, $line
과 선택적 매개변수 $width
를 정의했다.
$offset
: 오프셋은 아이콘이 스프라이트 이미지의 왼쪽 가장자리에서 얼마나 떨어져 있는 지 의미$line
: 스프라이트 이미지에서 행을 의미$width
: 32×16 px 사이즈 아이콘을 처리하기 위해 정의이제 @for
루프의 나머지를 완성할 차례이다. 믹스인은 다음과 같이 @include
를 이용해 참조할 수 있다.
@for $i from 1 through length($letters) {
$letter: nth($letters, $i);
.letter-dark-#{$letter} {
@include icon-sprite($i, 1);
}
}
이렇게 하면 컴파일된 CSS는 다음과 같은 모습이 된다.
.letter-dark-a { background-position: 0px 0px; }
.letter-dark-b { background-position: -16px 0px; }
.letter-dark-c { background-position: -32px 0px; }
/* 생략.. */
.letter-dark-z { background-position: -400px 0px; }
이런 식으로 for 문을 돌려 나머지 letter-light-X
, number-dark-X
, number-light-X
, button-X
, bubble-X
들도 생성해 주면 된다.
다른 것들과 다르게 "START", "SELECT" 아이콘 같은 경우에는 사이즈가 32×16 px, 2:1 비율로 조금 특수하다.
이 경우에는 @for
루프가 아닌 @while
루프를 사용하여 index를 적절히 증가시키는 작업을 추가해 너비가 다른 아이콘에 대해 처리해 주었다.
$button_start: 1;
$button_end: length($buttons);
@while $button_start <= $button_end {
$button: nth($buttons, $index);
$next: $button_start;
.button-#{$button} {
@if $button == "start" or $button == "select" {
@include icon-sprite($index + 1, 3, 2);
width: 32px;
$next: $next + 2;
} @else {
@include icon-sprite($index, 3);
$next: $next + 1;
}
}
$button_start: $next;
}
중간에 if-else 문으로 분기를 하고 있는데, 아이콘이 "start" 혹은 "select" 이면 2:1 비율을 차지하도록 하고 index 변수의 값을 2 증가시키는 설정을 추가했다. 이런 식으로 활용하여 1:1 비율이 아닌 아이콘에 대한 처리도 가능하다는 걸 확인하였다.
가장 아래 "HELLO WORLD!" 라고 된 부분에 scale: 2
라고 지정해 2배 사이즈로 보여주고 싶었다.
scale 속성은 transform
속성과는 독립적으로 요소를 확대하거나 축소할 수 있는 속성이다.
그런데 화면에 나타나는 걸 확인해 보니, 테두리가 흐릿하게 보이는 문제가 있었다.
이러한 현상은 image-rendering에 pixelated
값을 지정해 해결할 수 있다.
해당 옵션은 이미지를 확대시켜 보여줄 때 "nearest neighbor"와 유사한 알고리즘을 사용해, 원래 이미지 크기의 가장 가까운 정수 배율로 크기를 조정한 다음, 부드러운 보간(smooth interpolation)을 사용해 원하는 크기로 최종 이미지를 가져오도록 하는 옵션이라고 한다.
scale: 2;
image-rendering: pixelated;