폼팩터들이 워낙 다양해지면서 디자인이 적용될 레이아웃과 컴포넌트, 텍스트에 대한 사이즈를 유연하게 다룰 수 있는 능력 또한 매우 중요해진 상황이 된 것 같다. 크게는 뷰포트 크기가 가변됨에 따라서 레이아웃을 구성하는 Adaptive(적응형), Responsive(반응형), Fluid 방식 중에 어떤 것을 도입하게 될 지를 고민해야 될 것 같고, 세세하게는 엘리먼트들도 뷰포트에 따라 커지거나 작아질 지, 혹은 min/max 값에 따라서 줄바꿈될 지 등등 고려해야할 것들 투성이다. 이런 내용을 설명하기 위한 단초로서 값(value)이나 단위(unit)부터 정리해보면 좋을 것 같아서 먼저 끄적여볼까한다.
절대 단위 : px, pt
상대 단위 : %, em, rem, ch, vw, vh
단위 그 자체로써 크기를 결정할 수 있는 것을 절대 단위라고 부른다고 한다면, 부모 엘리먼트나 브라우저 설정값, 등에 대해서 상대적으로 영향을 받는 단위를 상대 단위라고 할 수 있겠다.
디지털 이미지를 구성하는 단위
픽셀(pixel, px)은 디바이스나 브라우저, 뷰포트 등에 영향을 받지 않는 절대적인 단위로 사용해야 할 때 사용가능하며, OS에 따라서 실제 보이는 크기가 달라질 수 있음.
디스플레이의 물리적인(?) 좌표
포인트 크기는 1pt = 1/72inch
로 정해져 있으며, 72개의 points가 모이면 1inch가 된다. 따라서 px과 달리 OS에 따라서 크기가 달라지지 않는다.
포인트(point, pt)는 기기의 물리적인 inch 기준으로 절대적인 단위로서 사용 가능하며, 디자인툴에서 주로 폰트를 pt 단위로 정의하고 있음.
ppi vs dpi
ppi(Pixel per inch)
: 1인치당 픽셀의 갯수, 디지털 이미지의 해상도
dpi(Dots per inch)
: 1인치당 점의 갯수, 출력물 이미지의 해상도
Mac
mac
은 72ppi : 1인치당 72개의 pixel이 있음
1inch = 72points = 72px (1pt = 1px, 1:1로 매칭됨)Windows
windows
는 96ppi : 1인치당 96개의 pixel이 있음
1inch = 72points = 96px (1pt = 1.333333px)
pt를 3의 배수로 써야 px로 표현됐을 때 Aliasing 없이 깔끔하게 떨어짐
9pt = 12px
10pt = 13.333333px
11pt = 14.666666px
12pt = 16px
13pt = 17.333329px
14pt = 18.666666px
15pt = 20px
18pt = 24px
21pt = 32px
모바일앱(iOS/AOS)으로 넘어가면 더 헷갈리는데, iOS는 ppi, pt를 사용하고, AOS는 dpi, dp(density independent pixel)/sp(scale independent pixel)를 사용하기 때문이다. 이 부분은 나중에 기회되면 더 다뤄보겠다.
장점 : 환경 변화에 상관없이 의도한 크기로 보여주고 싶을 때 사용할 수 있음
단점 : 디스플레이 크기에 동적으로 대응할 수 없음, 상대 단위처럼 어떤 기준에 대한 디펜던시가 없기 때문에 수정 시 노가다가 필요함 (변수로 관리하면 어느정도 해결될 수 있음)
%
단위는 상위 부모 엘리먼트의 동일한 프로퍼티값을 기준으로 백분율로 할당된다.
예를 들어 부모 엘리먼드의 width: 100px
일 때, width: 100%
을 지정하면 동일하게 100px
로, 부모 엘리먼트의 padding: 16px
일 때 padding: 50%
를 지정하면 8px
로 지정된다.
%
는 부모 element의 동일한 프로퍼티값을 참조한다면, em
은 현재 element의 font-size
를 참조한다. 백분율과는 다르게 1이 100%, 0.5가 50%의 크기라고 보면된다. 예를들어 해당 엘리먼트의 font-size
가 12px
이라면 1em = 12px
로 할당된다.
font-size
를 em
단위로 지정하고 싶다면 약간 헷갈리는 지점이 생기는데, 참조해야 할 본인의 font-size
를 em
으로 정의한다면 서로가 서로를 참조하는 것이기 때문이다. 하지만 여기서 font-size
는 기본적으로 부모 font-size
를 상속하기 때문에 내 font-size
를 1em
으로 둔다면 부모의 font-size
와 동일하게 된다.
장점 : 해당 폰트 사이즈를 기준으로 할 수 있다는 점 때문에 line-height나 letter-spacing과 같은 속성에 적용하기 유리하다.
단점 : 엘리먼트의 하이라키가 복잡해질수록 내가 지정한 em 값이 몇 px/pt인지 직관적으로 계산하기 어려워진다. (eg. 조부모 폰트사이즈 : 32pt, 부모 폰트사이즈 : 0.5em, 나의 폰트사이즈 : 0.75em = 12px)
em
단위와 비슷하지만 root element인 html 태그(현재 보고있는 페이지)의 font-size
하나만을 참조한다.
기본적으로 html
태그의 기본 폰트 사이즈는 16px
이며, 1rem = 16px
이다.
px/pt처럼 직관적으로 적용할 수 있고, 브라우저의 글꼴 사이즈 설정이 곧 html 기본 폰트 사이즈를 조절하는 것이기 때문에 rem으로 지정된 모든 값들이 연동되어서 확대/축소된다. 즉, 저시력자를 위한 웹접근성에 유리하다고 볼 수 있다.
장점 : 상대 단위이면서 기준이 하나이기 때문에 절대 단위처럼 직관적으로 사용할 수 있다. 폰트 사이즈에 적용하기 좋으며, padding/ margin 같은 프로퍼티 같은 경우에는 px과 같은 절대 단위를 주로 추천하지만, 폰트 사이즈 연동이 필요하다면 rem/em값으로 지정할 수도 있다.
단점 : px을 rem으로 일일히 변환하기 번거롭고, 소수점으로 떨어지는 경우가 많다. 이 단점을 해결하기 위해 px to rem 변환툴을 쓰거나font-size: calc(21rem / 16);
를 쓰는 방식으로 해결할 수는 있다.
viewport 단위는 현재 보고있는 브라우저의 viewport 크기를 백분율 기준으로 정의한 값이다. vw
는 viewport width, vh
는 viewport height이고, width: 100vw, height: 100vh
를 지정한다면 사용자가 보고 있는 브라우저의 전체 화면을 사용할 수 있다.
유용하게 쓸 수 있는 점을 2가지로 나눠볼 수 있다.
1. 유저가 브라우저 뷰포트 크기를 언제든 가변적으로 늘리거나 줄일 수 있는 상황에서 대응이 가능하다.
2. 사용자의 기기나 브라우저의 크기를 모르더라도 뷰포트의 최대 width/height를 활용할 수 있다.
viewport 단위를 사용할 때 주의해야 할 점이 있다면 iOS mobile safari 브라우저와 같이 스크롤할 때 브라우저의 UI(네비게이션바, 주소창, 등)들이 가변적으로 변하면서 viewport 영역도 같이 변하는 경우이다. 브라우저 UI가 다 살아있고, 뷰포트 영역이 최소일 때 기준으로는 svh/svw 단위를 쓰고, 브라우저 UI가 최소화되고 뷰포트 크기가 최대일 때 기준으로는 lvh/lvw 단위를 쓰게 된다. 여기서 헷갈리는 지점은 vh가 lvh와 동일하게 작동하기 때문에 아래 제일 오른쪽 이미지처럼 브라우저 UI 컴포넌트 영역에 가져져서 잘리게 되는 경우가 생긴다. 이러한 문제를 해결하기 위해서 뷰포트 단위를 동적으로 처리하기 위한 dvh, dvw 단위가 생겨나게 되었고 safari 특정 버전 이상에서만 지원이 가능하기 때문에 확인하고 사용하면 좋을 것 같다.
참고 : https://flaming.codes/ko/posts/new-dynamic-viewport-sizes-dvh-lvh-svh/
viewport의 최소/최대값을 백분율 기준으로 정의한 값이다. PC 같이 가로가 긴 모니터 같은 경우 가로가 100vmax
, 세로가 100vmin
이 될 것이고, 모바일같이 세로가 길 경우 가로가 100vmin
, 세로가 100vmax
가 된다. 쓰이는 경우를 잘 못보긴했는데, 1:1 비율의 정방형 엘리먼트를 만들 때 aspect-ratio
를 사용하지 않는 방법으로 활용이 가능할 것 같다.
ch : 숫자 '0' 캐릭터의 width
영문 텍스트 중 숫자 0의 width가 제일 넓기 때문에 숫자 0의 width를 기준으로 하며, 예를 들어 10ch
는 0000000000
의 넓이이다.
읽기 좋은 문단을 구성하기 위해서는 한줄에 50-75ch 정도가 적당하다고 한다.
간혹 텍스트(특히 숫자)가 변하면서 주변 레이아웃에 영향을 주게되는 컴포넌트 같은 경우가 있을 수 있는데 이런 부분들 때문에 UI가 덜컹거리거나 버튼 위치가 수시로 바뀌거나하는 문제가 생길 수 있다. 이 부분을 해결할 수 있는 방법은 2가기 정도가 있다.
1. 텍스트가 들어갈 충분한 공간을 확보한다.
텍스트는 보통 inline으로 활용되기 때문에, 텍스트가 차지하고 있는 공간 만큼만 가변적으로 늘어나거나 줄어드는데, 이 공간 자체에 대한 최소 사이즈(min-width) 충분히 지정함으로써 해결할 수 있다 (eg. 숫자가 최대 3자리까지 늘어날 수 있다고 가정하면min-width: 3ch
로 지정하는 방식이다)
2. monospace 폰트 패밀리를 사용한다.
아래 케이스 같은 경우에 일반적인 폰트 패밀리를 사용하면hh:mm:ss
라는 형태는 같지만, 숫자 0과 1의 자폭이 다르기 때문에 매초마다 UI가 덜컹거리게 된다. 따라서 모든 숫자가 0의 자폭과 동일한 monospace 폰트 패밀리를 사용해야 이런 현상을 방지할 수 있다.
lh : line-height 높이
ex : 소문자 x의 높이, x-height
font-size
: 가능하면 rem
으로, 가변이 필요하다면 rem
과 vw
를 적절히 섞어쓰면 좋을 듯하다. (eg. calc(3rem + 0.1vw)
)
line-height
: 디자이너들이 보통 행간을 텍스트 사이즈의 1.2-1.5배 정도로 지정하기 때문에 가능하면 해당 텍스트 사이즈 기준으로하는 em
으로 하는 것을 추천한다. 혹시나 px로 환산했을 때 소수점으로 떨어져 anti-aliasing이 발생하는게 싫다면, 짝수 px
로 지정하는 것으로 대체하면 좋다. (짝수인 이유는 텍스트를 padding으로 감싸거나 했을 때 짝수가 버튼 height가 짝수인게 정렬에 유리하기 때문이다)
letter-spacing
: 폰트 디자이너가 잘 고려해서 만든 글리프를 조절할 필요가 없긴한데, 굳이 조절하고자 한다면 보통 95%~98% 정도의 자폭기준으로 -0.02em ~ -0.05em을 지정한다.