Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

grunt-spritesmith로 CSS Sprites 자동화하기

CSS Sprites란?

CSS Sprites라는 기법은 충분히 보편화한 기술이지만 간단히 설명하면 작은 이미지를 하나의 이미지로 합치는 방법을 말한다. 웹사이트에서 아이콘 등을 사용할 때 작은 이미지들이 다수 필요한데 네트워크에서 다운로드 받을 때 작은 파일 다수를 다운로드 받는 것보다는 큰 파일을 한번에 다운로드 받는 것이 더 빠르므로 여러 이미지를 한 파일로 합치고 이를 한번에 다운로드 받은 뒤에 CSS로 위치를 조절해서 표현하는 것이다.

Gmail의 Sprite 이미지

Gmail 같은 경우도 위 이미지처럼 아이콘을 하나로 합쳐서 사용하고 있고 이를 다음과 같은 CSS로 제어하고 있다.

.aos {
  background: no-repeat url(//ssl.gstatic.com/mail/sprites/general_black-16bf964ab5b51c4b7462e4429bfa7fe8.png) 0 -472px;
  height: 21px;
  width: 21px;
}

각 아이콘 사이즈에 맞게 크기를 정하고 이미지를 배경이미지로 지정해서 위치를 크기에 정확히 맞게 제어하는 식이다.

Sprites 이미지 만들기

보통 디자인은 각 아이콘을 따로 만들기 때문에 이를 하나의 이미지로 다시 맞추는 과정이 필요하다. 이전에도 Sprites를 사용해 보기는 했지만 보통 이미 만들어진 상태에서 사용했을 뿐 디자인된 이미지에서 직접 CSS Sprites를 만들어 보게 된 건 이번이 처음이었다.

SpritePad의 화면

관련 도구를 찾아보다가 SpritePad라는 도구를 써봤는데 이미지를 추가하면 자동으로 파일명에 따라 CSS를 만들어 주기는 하는데 위치를 잡아주는 건 모두 수동으로 해야 하므로 Sprite 이미지를 만드는 게 쉽지 않았다. 원하는 위치에 딱 위치하게 하는 게 많이 귀찮았다. Sprite Cow라는 도구도 있지만, 이는 Sprite 이미지를 만들어 준다기보다는 만들어진 이미지에서 CSS를 만들어 주는 역할에 더 가까웠다. 달리 맘에 드는 도구도 없다 보니 결국 포토샵을 열어서 포토샵으로 작업했다.

포토샵에서 Sprite 만들기

간단한 포토샵 정도는 다룰 수 있었지만, 아이콘은 보통 배경이 투명한데 포토샵에서는 투명한 배경의 크기를 잡아주지 않으므로 아이콘을 크기에 맞게 이어 붙이기가 어려웠다. 그래서 위처럼 각 아이콘 이미지에 배경을 넣어서 크기를 고정해서 이어 붙이는 수밖에 없었고 나중에 저장할 때는 배경레이어는 감추고 저장하는 방식을 사용했다.(예전엔 다 이렇게 한 건가...) 이렇게 작업하는 시간도 상당히 걸렸지만 이런 식으로 작업해놓고 보니 유지보수가 상당히 신경이 쓰였다. 각 CSS의 위치를 수동으로 알아내서 CSS 파일에 적용해야 하는 것도 불편했지만, 작업하다 보니 추가적인 아이콘이 계속 발생하고 그때마다 Sprite 이미지도 수정해야 하는데다가 이미지 크기를 최소로 해야 하니 나중에는 위치도 바꾸어야 할 것 같았다.(위치를 바꾸면 당연히 CSS도 그것에 맞게 다시 수정해 주어야 한다.)

grunt-spritesmith

fallroot님과 얘기를 하다가 Sprite 이미지를 만드는데 Compass를 써서 이미지 아이콘을 CSS로 자동화하고 있다는 얘기를 들었다. 처음에는 Compass를 도입하려고 문서를 보다가 Grunt를 이미 쓰고 있었으므로 Grunt에도 비슷한 도구가 있을 것 같아서 찾아낸 게 grunt-spritesmith다.

grunt-spritesmith는 이미지 셋을 하나의 CSS Sprite를 이미지로 만들어 주고 이에 대한 CSS까지 만들어 주는 Grunt 테스크이다. Stylus를 사용하고 있었으므로 CSS뿐만 아니라 SASS, SCSS, Less, Stylus같은 전처리자까지 지원하는 점도 맘에 들었다.

사용방법

Sprite로 만들 이미지를 원하는 폴더에 모두 넣는다.(여기서는 icons라는 폴더 밑에 넣었다.)

icons 폴더의 이미지 목록

grunt-spritesmith에서 지정한 이미지를 모두 하나의 파일로 만들어 주므로 각 아이콘 파일을 모두 복사해 넣으면 된다.

Gruntfile.js에 다음과 같은 설정을 추가한다.

module.exports = function (grunt) {
  // Configure grunt
  grunt.initConfig({
    sprite:{
      all: {
        src: 'icons/*.png',
        dest: 'sprites/icons.png',
        destCss: 'css/sprites.css'
      }
    }
  });

  // Load in `grunt-spritesmith`
  grunt.loadNpmTasks('grunt-spritesmith');
};

sprite 테스크를 추가했다. src는 사용할 이미지의 위치를 지정하면 되고 여기서는 위에서 만든 icons폴더의 .png파일을 지정했다. dest는 생성한 Sprite 이미지의 위치로 sprites/icons.png로 지정했고 destCss는 해당 CSS 파일을 생성할 위치이다.

$ grunt sprite
Running "sprite:all" (sprite) task
Files "css/sprites.css", "sprites/icons.png" created.

Done, without errors.

grunt sprite를 실행하면 위처럼 CSS와 png 이미지가 새로 생성된 것을 볼 수 있다.(예시 아이콘은 Pace Icon Set을 사용했다.)

만들어진 sprite 이미지

앞에서 추가했던 아이콘 파일을 하나로 합쳐서 위와 같은 sprites/icons.png를 생성해 주고 css/sprites.css는 다음과 같다.

/*
Icon classes can be used entirely standalone. They are named after their original file names.



<img class="icon-home" /> */ .icon-apple { background-image: url(../sprites/icons.png); background-position: 0px 0px; width: 32px; height: 32px; } .icon-battery { background-image: url(../sprites/icons.png); background-position: -32px 0px; width: 32px; height: 32px; } .icon-bluetooth { background-image: url(../sprites/icons.png); background-position: 0px -32px; width: 32px; height: 32px; } .icon-cloud { background-image: url(../sprites/icons.png); background-position: -32px -32px; width: 32px; height: 32px; }

각 아이콘의 파일명을 그대로 사용해서 CSS 클래스를 자동으로 생성하고 크기와 위치도 자동으로 만들어 준다. 이제 이 클래스를 사용해서 아이콘을 사용하기만 하면 된다. 파일명이 바뀌지 않는 이상 클래스 명도 바뀔 일이 없으므로 추가 아이콘이 생기거나 기존 아이콘의 크기가 달라지더라도 아이콘 이미지를 교체하고 다시 생성해 주기만 하면 된다.

Stylus에서 사용하기

CSS가 아니라 Stylus 같은 전처리자를 사용하더라도 아주 쉽게 사용할 수 있다.

module.exports = function (grunt) {
  // Configure grunt
  grunt.initConfig({
    sprite:{
      all: {
        src: 'icons/*.png',
        dest: 'sprites/icons.png',
        destCss: 'css/sprites.styl',
        cssSpritesheetName: 'icons-spritesheet'
      }
    }
  });

  // Load in `grunt-spritesmith`
  grunt.loadNpmTasks('grunt-spritesmith');
};

destCss에서 확장자를 .styl로 지정하기만 하면 자동으로 CSS 대신 Stylus 용으로 만들어 준다.

$apple_name = 'apple';
$apple_x = 0px;
$apple_y = 0px;
$apple_offset_x = 0px;
$apple_offset_y = 0px;
$apple_width = 32px;
$apple_height = 32px;
$apple_total_width = 64px;
$apple_total_height = 64px;
$apple_image = '../sprites/icons.png';
$apple = 0px 0px 0px 0px 32px 32px 64px 64px '../sprites/icons.png' 'apple';

생성된 css/sprites.styl를 보면 위처럼 이미지별로 변수를 생성해 준다. 처음 봤을 때는 클래스로 만들어 주지 않고 왜 파일별로 많은 변수를 만들어 주나 싶었는데 전처리자인 만큼 이 변수를 사용하면 다양한 상황에서 더 유용하게 사용할 수 있다.

$icons_spritesheet_width = 64px;
$icons_spritesheet_height = 64px;
$icons_spritesheet_image = '../sprites/icons.png';
$icons_spritesheet_sprites = $apple $battery $bluetooth $cloud;
$icons_spritesheet = 64px 64px '../sprites/icons.png' $icons_spritesheet_sprites;

각 아이콘에 대한 변수 외에도 전체 아이콘을 모두 가리키는 변수도 위와 같이 만들어 준다. 여기서 $icons_spritesheet_라는 접두사는 앞에 Gruntfile.js에서 cssSpritesheetName로 지정한 이름이다. 이 값을 지정하지 않으면 기본 접두사인 $spritesheet_가 붙으므로 Sprite 이미지를 여러 개 만드는 경우 겹치지 않도록 이름을 지정할 필요가 있다.

spriteWidth($sprite) {
  width: $sprite[4];
}

spriteHeight($sprite) {
  height: $sprite[5];
}

spritePosition($sprite) {
  background-position: $sprite[2] $sprite[3];
}

spriteImage($sprite) {
  background-image: url($sprite[8]);
}

sprite($sprite) {
  spriteImage($sprite)
  spritePosition($sprite)
  spriteWidth($sprite)
  spriteHeight($sprite)
}

sprites($sprites) {
  for $sprite in $sprites {
    $sprite_name = $sprite[9];
    .{$sprite_name} {
      sprite($sprite);
    }
  }
}

변수 외에도 위와 같은 믹스인을 만들어 주어서 필요한 CSS를 쉽게 만들 수 있도록 해주고 있다. 예를 들어 $apple 아이콘의 CSS를 생성하려면 sprite($sprite) 믹스인을 사용해서 sprite($apple)처럼 해당 아이콘의 변수를 전달하면 된다. 그럼 다음과 같은 CSS가 만들어진다.

background-image:url("../sprites/icons.png");
background-position:0 0;
width:32px;
height:32px;

전체 아이콘을 앞에서 본 CSS 클래스처럼 모두 만들려면 sprites($sprites) 믹스인을 사용하면 된다. 생성된 변수 중에 $icons_spritesheet_sprites가 모든 아이콘에 대한 변수를 담고 있으므로 sprites($icons_spritesheet_sprites)와 같이 사용하면 변수명에 따라 다음과 같은 CSS 클래스를 생성해 준다.

.apple{background-image:url("../sprites/icons.png");background-position:0 0;width:32px;height:32px}
.battery{background-image:url("../sprites/icons.png");background-position:-32px 0;width:32px;height:32px}
.bluetooth{background-image:url("../sprites/icons.png");background-position:0 -32px;width:32px;height:32px}
.cloud{background-image:url("../sprites/icons.png");background-position:-32px -32px;width:32px;height:32px}

가장 일반적인 접근인 전체 아이콘을 CSS 클래스로 사용하는 경우에는 sprites믹스인을 사용하면 된다. :before:after같은 pseudo 요소라서 CSS 클래스를 사용하기 어려운 경우에는 sprite 믹스인을 사용하면 된다. line-height등을 맞추는 등 아이콘의 크기가 필요한 경우에는 각 변수를 가져다가 사용하면 된다.

기타 사항

기본적으로 최적화해서 이미지를 Sprite로 합쳐주기는 하지만 algorithm옵션을 주면 세로로 길게 배열하거나 가로로 배열하는 등의 조절도 가능하고 전체 이미지의 품질도 조절할 수 있다. 위에서는 기본적으로 제공되는 전처리자 템플릿을 사용했지만 템플릿을 직접 설정하면 업무에 맞게 만들어진 값이나 변수명 등을 제어할 수 있다. 아무래도 사용하면 가장 신경이 쓰일 만한 변수명을 언더스코어(_)로 연결할지 대시(-)로 연결할지 등의 설정도 가능하다.

좀 써보니 실제로 사용한다면 아무래도 가장 관리가 필요한 부분은 이미지 파일명일 것 같다. 초기에 환경 세팅을 해 놓고 이미지의 아이콘 형식을 미리 정의해 놓는다면 좀 편할 것 같고 이런 부분이 디자인 쪽에 전달해서 이미지 파일명이 그대로 나오면 편할 것 같다.

2015/03/29 17:11 2015/03/29 17:11