SVG "내보내기"에 대한 정리 - (3)

조경석·2022년 12월 24일
0
post-thumbnail

Sketch

사실상 최초의 UI 디자인 전용 툴. 배포 초기에만 해도 단순한 이름 때문에 검색결과가 스케치업만 나오는 수준이었지만, 서비스에서의 UX/UI 대중적인 개념이 되면서 sketch로만 검색해도 UI 디자인의 기본 툴로 소개하는 미디어가 검색될 정도로 표준적인 툴이 되었다.
맥에서만 사용 가능하며, 30일의 짧은 무료 체험 기간이 있지만 완전히 유료 플랜만을 제공해 UI 디자인을 막 시작한 디자이너에게 부담을 주는 툴이기도 하다.
역사가 가장 긴 툴이다보니 사용성이 입증된 방대한 양의 플러그인이 장점이지만, 그만큼 레거시 환경의 잔재 역시 많이 남아있어 일장일단이 있다.

아래 아이콘이 Sketch에서 스타일가이드를 따라 작업한 뒤 기본 설정으로 내보내기한 SVG 파일이다.

웹 표준인 SVG 1.1의 새로운 기능들이 적용된것은 2011년이고, 스케치는 무려 2010년에 발표된 역사깊은(?) 툴이기 때문에 최근 일반적으로 사용하는 SVG 형식과 차이가 있는 편이다.

<!-- XML 선언 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 버전과 xmlns:xlink까지 포함된 SVG 태그 -->
<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- 내보내기 그룹 이름이 적용된 title -->
    <title>Sketch</title>
    <defs>
        <!-- Rect 2의 그림자 영역을 위해 defs에 rect를 포함했다. 스타일가이드의 Path 1과는 다른, 스케치가 생성한 도형이다. -->
        <rect id="path-1" x="1" y="1" width="16" height="22" rx="2"></rect>
        <!-- 퍼센트 좌표가 적용된 filter -->
        <filter x="-12.5%" y="-4.5%" width="125.0%" height="118.2%" filterUnits="objectBoundingBox" id="filter-2">
            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.2 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
        </filter>
        <!-- linearGradient에도 퍼센트 좌표가 적용되어 있다. -->
        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-3">
            <stop stop-color="#35F39D" offset="0%"></stop>
            <stop stop-color="#169DF6" offset="100%"></stop>
        </linearGradient>
    </defs>
    <!-- Figma와 동일한 작업 단위인 Page가 최상위 그룹으로 포함되어 있다. -->
    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <!-- title이 있지만 한번 더 적용된 내보내기 그룹 -->
        <g id="Sketch">
            <!-- fill도 stroke도 적용되지 않은 Rect 1 -->
            <rect id="Rect-1" x="0" y="0" width="36" height="36"></rect>
            <!-- 그룹의 위치를 transform="translate()"로 지정해둠 -->
            <g id="Group" transform="translate(10.000000, 6.000000)">
                <!-- def에서 선언한 path-1을 use로 사용해 그림자가 적용된 Rect 2를 그린다. -->
                <g id="Rect-2">
                    <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
                    <use fill="#E8EDED" fill-rule="evenodd" xlink:href="#path-1"></use>
                </g>
                <!-- fill-rule="nonzero"가 적용된 Path 1 -->
                <path d="M15,2 C15.55,2 16,2.45 16,3 L16,21 C16,21.55 15.55,22 15,22 L9,22 C8.45,22 8,22.45 8,23 C8,23.55 8.45,24 9,24 L15,24 C16.66,24 18,22.66 18,21 L18,3 C18,1.34 16.66,0 15,0 L3,0 C1.34,0 0,1.34 0,3 L0,8 C0,8.55 0.45,9 1,9 C1.55,9 2,8.55 2,8 L2,3 C2,2.45 2.45,2 3,2 L15,2 Z" id="Path-1" fill="url(#linearGradient-3)" fill-rule="nonzero"></path>
            </g>
            <rect id="Rect-3" fill="#35F39D" x="15" y="10" width="5" height="2" rx="1"></rect>
            <rect id="Rect-4" fill="#35F39D" x="21" y="10" width="2" height="2" rx="1"></rect>
            <rect id="Rect-5" stroke="#169DF6" stroke-width="2" fill="#FFFFFF" x="7" y="18" width="8" height="11" rx="2"></rect>
            <!-- inner stroke가 고려되어있지 않아 아예 형태가 달라진 Circle 1  -->
            <circle id="Circle-1" stroke="#169DF6" cx="19" cy="26" r="1"></circle>
            <circle id="Circle-2" fill="#169DF6" cx="11" cy="26" r="1"></circle>
        </g>
    </g>
</svg>

호환을 위한 요소는 왠만하면 다 들어가 있는 수준이다.

XML 선언은 SVG를 파일 그대로 사용하는 경우 환경에 상관없이 XML 형식과 인코딩을 확인할 수 있도록 포함된 것으로, 웹 환경에서 인라인 SVG로 사용하려는 경우 제외해야 한다.
내보낸 SVG 에셋에 이를 포함하는 것은 일러스트레이터나 잉크스케이프와 같은 기존의 벡터 드로잉 툴과 비슷한 부분으로, 후기에 만들어진 다른 UI 디자인 툴들에서는 과감하게 생략한 것과는 비교된다.

defs 영역에 도형인 rect가 포함되어 있고, 퍼센트 좌표를 적극적으로 사용한다. 더 아래에서 필터와 그라디언트 적용 방식을 알아보면서 자세히 분석해보겠다.

title 태그도 포함되어 있는데, 이 태그는 호환을 위해 반드시 상위 요소의 첫번째 자손 태그여야 한다.
대부분의 웹 브라우저는 title 내의 텍스트를 툴팁으로 표기하지만, 접근성을 위해 해당 태그 대신 WAI-ARIA 요소를 사용하는 것을 권고하고 있다.

Figma와 동일하게 Sketch에서도 Page 단위로 작업 문서 내에서 단위 환경을 구성하는데, 그 Page가 최상위 그룹으로 포함되어 있다.
뜬금없이 stroke-width에다가 fill-rule까지 적용되어 있는데 이유는 차치하고, fill-rule에 대해서 간단히 알아보자.

fill-rule

도형의 패스가 교차하는 경우에 fill을 적용해야 할 '안쪽'을 지정할 규칙을 지정하는 특성으로, 속성값은 nonzero와 evenood가 있다.

nonzero
0이 아니라는 뜻으로, 배열의 인덱스 검사에서 사용하는 이름으로 익숙한 사람도 있을것이다.
여기서는 0부터 시작하여 경로 방향이 교차하는 방향에 따라 1을 더하거나 빼서 결과값이 0이 아닌 경우 내부로 지정하는 방식이다.
말이 복잡한데, 단순히 안쪽 도형의 각 꼭지점이 그려진 순서가 반대 방향인 경우에만 안쪽으로 취급한다는 뜻이다.
SVGWG에 잘 설명된 이미지가 있어 첨부한다.

별은 단일 방향의 단일 도형이라 '안쪽 도형'이 없으며, 가운데 원은 점이 그려진 순서가 같은 시계방향이라 안쪽으로 지정되지 않았다.
이와 같이 의도적으로 '칠해진 안쪽 도형'이 필요할 때 사용할 수 있다.

evenodd
짝수-홀수 규칙이라는 뜻으로, 직관적으로 가장 '바깥쪽'에 있는 도형이 아닌 '교차된 영역'을 안쪽으로 지정하는 알고리즘이다.
이름이 짝수-홀수인 이유가 궁금했는데, 직관적으로 설명된 포럼 답변을 찾았다.


그려지는 도형의 안쪽에서 바깥쪽으로 직선을 그렸을 때, 지나는 선의 갯수의 짝수-홀수 여부에 따라 fill 여부를 결정한다.
직관적으로 보이지만, '안쪽 도형'을 지정하는 방식이라기보단 '인접한 도형이 둘다 칠해지지 않는' 방식에 가깝다.

fill-rule은 컴퓨터 그래픽스에서 자주 거론되는 PIP(point-in-polygon) 문제와 관련된 내용으로 꽤나 흥미로운 부분이다.
even-odd와 nonzero는 이를 위해 어도비의 Postscript에서 먼저 적용한 방식으로, 이후 SVG2에서 표준으로 적용되어 사실상 벡터 그래픽에서 일반적으로 사용하게 되었다.

use

다시 Sketch의 SVG 형식으로 돌아와서, Rect 2 부분을 보면 그림자를 그리기 위해 use 태그를 사용했다.
use는 xlink:href의 id 값으로 요소를 가져와 복제하는 요소로, 기존 요소에 선언된 특성을 대체하지는 않고, 추가된 속성만 적용하여 하나 더 복제한다.
복잡한데다 a나 image에서 사용하는 형태와 헷갈리는 꼴을 봐도 알겠지만, SVG2에서 제외되었다. 웹 표준은 아직 1.1이므로 대부분의 브라우저에서는 정상적으로 작동한다.

기존 도형을 기준으로 효과를 적용하는 그림자 효과에 적절한 방식이라고 할 수는 있겠다.
이를 위해 defs에서 path-1이라는 새로운 id를 부여한, 스타일이 설정되지 않은 도형을 생성해둔 것이다.

이후는 기존 분석해보았던 다른 툴과 비슷하게 feOffset, feGaussianBlur, feColorMatrix를 사용하는데, 또 뜬금없이 필터 영역에 퍼센트 좌표가 적용되어 있다.
이 영역은 Drop Shadow의 Offset과 Blur(, Spread)로 그림자가 그려지는 영역을 표기한 것으로, 별도의 마스킹이 적용되어있지 않으므로 쓸모없는 값이다.
(그라디언트도 마찬가지로 기준선 영역을 지정해두었다.)

Stroke를 보정하지 않는 Sketch

위는 1편에서 첨부했던 스타일가이드, 아래는 이 글 위쪽에서 첨부했던 스케치에서 내보낸 SVG 파일을 Inkscape에서 확대만 적용한 파일이다.
자세히 볼것도 없이 Circle 1의 형태가 다른데, 이는 스케치가 Inner Stroke를 보정하지 않기 때문이다.
앞서 살펴봤던 XD와 Figma가 내보내기 각자의 방식으로 옵션을 설정하거나 결과를 보정한 것과 달리 아예 도형의 원래 형태를 유지하고 stroke-alignment를 무시해버렸다.

당연하게도 포럼에서는 SVG 내보내기 결과가 이상하다는 문의가 빗발치고 있지만, 현재 82버전에서도 옵션도, 플러그인도 제공하지 않고 있다.

SVGO 플러그인

Sketch는 자체적으로 공식 SVGO 플러그인을 제공하고 있다.
플러그인 활성화 시 자체 내보내기 기능으로 SVG를 내보냈을때 자동 적용되는 방식으로, 원하지 않는 경우 플러그인을 해제하거나 Context Menu(오른쪽 클릭)에서 직접 SVG 코드 복사를 실행해야 한다.
간단하게 코드만 비교하고 마무리하겠다.

<!-- XML선언과 SVG 버전, px 단위가 제거됨 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36" height="36" viewBox="0 0 36 36">
    <!-- 삭제된 title -->
    <defs>
        <!-- 왠지 순서가 뒤집힌 defs, 여전히 남아있는 퍼센트 좌표 영역 -->
        <linearGradient id="c" x1="50%" x2="50%" y1="0%" y2="100%">
            <stop offset="0%" stop-color="#35F39D" />
            <stop offset="100%" stop-color="#169DF6" />
        </linearGradient>
        <filter id="b" width="125%" height="118.2%" x="-12.5%" y="-4.5%" filterUnits="objectBoundingBox">
            <!-- 불필요한 dx="0"삭제 -->
            <feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
            <feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation=".5" />
            <!-- 행렬 구분을 위한 공백이 삭제되고, 기본값인 type="matrix" 삭제 -->
            <feColorMatrix in="shadowBlurOuter1" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" />
        </filter>
        <rect id="a" width="16" height="22" x="1" y="1" rx="2" />
    </defs>
    <!-- id와 stroke를 삭제했지만 여전히 불필요하게 남아있는 Page-1 -->
    <g fill="none" fill-rule="evenodd">
        <!-- 불필요한 위치좌표가 삭제된 Rect 1 -->
        <rect width="36" height="36" />
        <!-- 소수점과 id만 삭제된 Group 1 -->
        <g transform="translate(10 6)">
            <!-- 불필요한 fill-opacity가 삭제됨 -->
            <use xlink:href="#a" fill="#000" filter="url(#b)" />
            <!-- fill-rule="evenodd"는 기본값이 아닌데도 삭제되었다. -->
            <use xlink:href="#a" fill="#E8EDED" />
            <!-- 딱히 압축되지 않은 Path 1 -->
            <path fill="url(#c)" fill-rule="nonzero" d="M15,2 C15.55,2 16,2.45 16,3 L16,21 C16,21.55 15.55,22 15,22 L9,22 C8.45,22 8,22.45 8,23 C8,23.55 8.45,24 9,24 L15,24 C16.66,24 18,22.66 18,21 L18,3 C18,1.34 16.66,0 15,0 L3,0 C1.34,0 0,1.34 0,3 L0,8 C0,8.55 0.45,9 1,9 C1.55,9 2,8.55 2,8 L2,3 C2,2.45 2.45,2 3,2 L15,2 Z" />
        </g>
        <!-- Rect 3 -->
        <rect width="5" height="2" x="15" y="10" fill="#35F39D" rx="1" />
        <!-- Rect 4 -->
        <rect width="2" height="2" x="21" y="10" fill="#35F39D" rx="1" />
        <!-- Rect 5 -->
        <rect width="8" height="11" x="7" y="18" fill="#FFF" stroke="#169DF6" stroke-width="2" rx="2" />
        <!-- Circle 1 -->
        <circle cx="19" cy="26" r="1" stroke="#169DF6" />
        <!-- Circle 2 -->
        <circle cx="11" cy="26" r="1" fill="#169DF6" />
    </g>
</svg>

마무리

Sketch를 마지막으로, SVG 내보내기에 대한 정리는 마무리하려 한다.

각 툴마다 적용하는 형태가 상이하기 때문에 장단점이 있고 이 시리즈에서의 서술도 중립적으로만 작성하지는 않았지만,
개인적으로는 결국 디자이너나 개발자의 SVG에 대한 이해가 필요하다는 기존의 생각을 증명하는 과정이 되었다고 생각한다.

스타일가이드를 통해 디자인 원칙을 세우는 디자이너는 내/외부 CSS를 위한 클래스 선택자 명세를 작성할 수 있고, 그림자나 그라디언트같은 효과 역시 디자인 툴에서 제공하는 범위 이상으로 세부 조정되고, 최적화된 형태를 사용할 수 있을것이다.

개발자 역시 효율적인 최적화를 위해 불필요한 마크업이나 비효율적인 효과 적용 방식을 변경하되, 이 과정에서 SVG 요소에 대한 정확한 이해가 있다면 디자이너에게 명확한 디자인 의도를 요구하고, 조정할 수 있을 것이라고 생각한다.

당연히 상세하게 분석해본 내용들이 실무에서 사용할 수 있을지는 의문이 들지만, SVG를 읽을 줄 아는 개자이너라는 타이틀은 내밀어 볼 수 있지 않을까 싶다.

0개의 댓글