Thumbnail

9분

Should I use pixels? or ems/rems?

https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/

들어가면서

"Pixel을 써야할까요? em/rem을 써야할까요?"라는 질문은 많은 프론트엔드 개발자들이 고민하는 주제 중 하나입니다. 이 주제는 때때로 갈등을 불러일으키기도 합니다. 접근성이 좋은 제품을 만들고 싶다면 pixel과 em/rem 모두 사용해야 합니다. 상황에 따라 rem이 더 접근성이 좋을 경우가 있고 pixel이 더 나은 경우가 있습니다.

이 글에서 다뤄볼 내용은 다음과 같습니다.

  1. 각 단위가 어떻게 동작하는지 간략하게 설명합니다.
  2. 접근성 고려 사항과 각 단위가 어떻게 영향을 미치는지 다룹니다.
  3. 모든 시나리오에서 각 단위를 결정하는데 사용할 수 있는 멘탈 모델을 소개합니다.
  4. 이와 관련한 몇 가지 팁과 트릭을 제공합니다.

Unit

Pixel

가장 많이 쓰는 unit인 pixel(px) 입니다.

.box {
  width: 1000px;
  margin-top: 32px;
  padding: 8px;
}

이론상, 1px은 computer 모니터 또는 phone 스크린에서 점 하나 와 같습니다.

하드웨어 vs 소프트웨어 pixels

px 단위는 조금 페이크입니다. 실제로 하드웨어 pixel에 깔끔하게 매핑 되지 않습니다. 현미경으로 하드웨어 디스플레이를 보면, 더이상 R/G/B 사각형으로 만들어지지 않는 것을 볼 수 있습니다.

apple-watch

iphone

심지어 제조업체가 pixel grid로 위 이미지와 같이 창의력 을 발휘하기 전에도 화면의 물리적인 pixel과 CSS로 작성하는 소프트웨어 pixel사이에는 여전히 차이가 있었습니다. 사용자가 화면의 해상도를 변경하거나 확대할 때마다 소프트웨어 pixel을 하드웨어 pixel에 매핑하는 방식을 변경합니다.

즉, 이러한 것들이 px단위에 대해 생각하는 방식에 영향을 주어서는 안됩니다. px은 여전히 우리가 가진 가장 구체적인 단위입니다.

Em

em은 흥미로운 단위입니다. element의 계산된 font size를 기반으로 하는 상대적인 단위입니다.

em-ex1

em-ex2

기본적으로 em은 비율입니다. 만약 paragraph가 1.5em의 margin-bottom을 가진다면, 해당 paragraph의 font-size의 1.5배의 크기를 가진다는 것을 말합니다. 이는 하나의 값을 다른 값에 anchor(고정) 하기에, 값을 비례하여 scale 하게 해줍니다.

여기서는 em이 어떻게 동작하는지 더 쉽게 이해하기 위해서 예시로 pixel 기반 font-size를 사용했지만, 이것은 좋지 않습니다. 실제 어플리케이션에서는 이렇게 사용하지 마세요.

Rem

root element의 font size를 기반으로 하는 상대적인 단위입니다.

em 단위의 일부 문제를 해결하기 위해 등장했습니다. rem 단위는 항상 최상위 노드의 폰트 크기를 기반으로 계산합니다.

<style>
  main {
    font-size: 1.125em;
  }
  article {
    font-size: 0.9em;
  }
  p.intro {
    font-size: 1.25em;
  }
</style>
<main>
  <article>
    <p class="intro">What size is this text?</p>
  </article>
</main>

.intro paragraph의 pixel은 얼마일까요?

이를 확인하기 위해서는 각 비율을 계산해봐야 합니다. default로 root font size는 16px, 그래서 16 * 1.125 * 0.9 * 1.25 이기에 정답은 20.25 pixel 입니다.

왜 그럴까요? 이는 font size가 상속되기 때문입니다. paragraph는 1.25em font size를 가집니다. 이는 "현재 font size의 1.25배"를 말합니다.

그러나 "현재 font size"가 무엇인가요? 부모로 부터 상속된 font size: 0.9em, 그래서 부모의 1.25배, 그것의 부모의 0.9배, 그것의 부모의 1.125배 입니다.

기본적으로 "고정된" 값(pixel을 사용하는)에 도달하거나, tree의 맨 끝까지 모든 em을 곱해야합니다. 보는 것처럼 굉장히 귀찮습니다.

이 문제를 해결할기 위해서 CSS는 rem 단위를 만들었습니다.

rem단위는 root node(<html>) 의 font size에 기반한 것을 제외하면 em단위와 동일합니다. 상속된 font size를 무시하고 항상 top-level node의 font size에 기반하여 계산합니다.

Document는 default로 font size가 16px입니다. 그래서 1rem은 "기본" 값이 16px을 의미합니다.(user-configurable, 아래에서 다룹니다.)

rem-ex1

rem-ex2

html font size를 변경해서 사용할 수 있지만 해서는 안됩니다.

이를 이해하기 위해서는 접근성에 대해 이야기할 필요가 있습니다.

접근성 고려 사항

pixel-vs-em/rem과 관련하여 주요 접근성 고려 사항은 vision 입니다.

시각이 불편한 사람들이 우리 웹사이트와 웹 애플리케이션에서 편하게 문장과 문단을 읽으려면 어떻게 해야할까요?

브라우저의 zoom 기능을 사용하는 것이 하나의 방법입니다.

여기서는 zooming 이라고 부르겠습니다.

WCAG(Web Content Accessibility Guilelines)에 따르면 접근성을 위해서 사이트가 200% zoom까지 사용할 수 있어야 한다고 안내합니다. 이 숫자는 실제로 최소값이며 많은 시각 장애가 있는 사람들이 그보다 더 높게 사용한다는 이야기를 들었습니다.

다른 하나는 브라우저 설정에서 기본 font size를 늘리는 것입니다.

여기서는 font scaling 이라고 부르겠습니다.

font scaling은 모든 상대 단위(rem, em, %)를 기반으로 하는 "baseline" font size를 재정의하여 동작합니다.

앞서서 1rem이 16px과 동일하다고 한 것을 기억하시나요? 기본 font size를 건들지 않았을 때만 사실입니다. 만약 기본 font size를 32px로 올린다면, 1rem는 16px대신 32px이 됩니다.

여기서 첫번째 걸림돌이 발생했습니다. 페이지에서 font-size에 pixel을 사용할 때, 더 이상 사용자가 선택한 기본 font size의 영향을 받지 않습니다.

font-scaling-ex01

font-scaling-ex02

이것이 remem 같은 상대 단위를 써야하는 이유입니다. 사용자 필요에 맞추고 값을 재정의할 수 있는 기능을 제공합니다.

사용자가 zoom in/out 하면 모든것 이 더 커집니다. 기본적으로 pixel을 포함한 모든 단위에 배수를 적용합니다. viewport 단위(vw, vh와 같은)를 제외하곤 모든 것에 영향을 미칩니다. 모든 주요 브라우저에 수 년 동안 마찬가지였습니다.

따라서 사용자가 font size를 늘리기 위해 항상 zoom in/out 할 수 있다면, font scaling 을 지원하는 것에 대해 걱정할 필요가 있나요? 하나의 옵션으로 충분한거 아닌가요? 그렇지 않습니다. 모든 경우의 옵션을 제공해야 합니다.

zoom이 실제로 각 사이트별로 사용되는 점이 문제입니다. 누군가는 새 사이트를 방문할 때마다 수동으로 zoom을 수정해야되는 일을 해야할지도 모릅니다. 그들이 편안하게 읽을 수 있을 만큼 충분히 크고, 보편적으로 사용되는 크기로 기본 font size를 설정할 수 있다면 더 좋지 않을까요?

일반적으로 사용자에게 최대한 많은 권한을 부여해야 하며 설정이 작동하지 않도록 비활성화하거나 차단해서는 안됩니다.

이러한 이유로 typography에 rem 같은 상대적인 단위를 사용하는 것이 매우 중요합니다.

각 단위의 사용 시나리오

rem 단위가 zoomingfont-scaling 둘 다 지원된다면 항상 rem 값을 사용해야 되는거 아닌가요? 왜 pixel을 사용해야되나요? 라고 생각이 들 수 있습니다.

rem을 padding으로 사용할 때 어떤 일이 발생하는지 보겠습니다.

padding-ex01

padding-ex02 |

rem 값은 사용자 기본 font size에 의해 조정된다는 것을 기억하세요. 이는 typography에 사용할 때 좋습니다.

다른 곳에서 사용할 때도 좋을까요? 실제로 모든것 에 font size로 scale되기를 원하시나요?

여기에는 text size에 사용될 때 암묵적인 trade-off가 있습니다. text size가 클 수록 각 줄에 들어갈 수 있는 글자 수는 줄어듭니다. 사용자가 text를 250%로 늘리면 한 줄에 몇 단어만 들어갈 수 있습니다.

rem을 horizontal padding(left, right)에 사용할 때, 이러한 side-effect가 증폭됩니다. 사용 가능한 공간을 줄이고, 각 줄에 들어갈 수 있는 단어 수를 더욱 제한합니다.

한 줄에
몇 단어만
포함된
이와 같은 문단은
읽기
불편하기
때문에
좋지 않습니다.

마찬가지로, border width는 어떤가요? 사용자가 원하는 text 크기로 크게 한 만큼 border가 더 커지기 때문에 말이 되지 않습니다. 그렇지 않나요?

이것이 단위를 전략적으로 사용하고자 하는 이유입니다. pixel과 rem 중에서 선택할 때 다음과 같은 질문을 스스로에게 던져야 합니다.

사용자가 브라우저의 기본 font size를 늘리면 이 값도 늘려야 할까?

이 질문은 아래에서 설명할 멘탈 모델의 기본입니다.

값이 기본 font size와 함께 증가해야 하는 경우 rem, 그렇지 않다면 px을 사용합니다.

즉, 이 질문에 대한 정답 은 항상 명확하지 않습니다. 몇 가지 예시를 보겠습니다.

미디어 쿼리

미디어쿼리 값으로 pixel을 사용해야 하나요? rem을 사용해야 하나요?

/* Should we do this: */
@media (min-width: 800px) {
}
/* …Or this: */
@media (min-width: 50rem) {
}

차이점이 무엇인지 명확하지 않을 수 있으므로, 자세하게 나눠보겠습니다.

사용자가 기본 font size를 32px(기본 text size의 2배인)로 설정했다고 가정해보겠습니다. 50rem이 800px이 아닌 1600px이라는 것을 의미합니다.

이처럼 breakpoint를 올림으로써, window가 최소 1600px이 될 때까지 사용자가 mobile 레이아웃으로 본다는 것을 의미합니다. 만약 laptop을 사용한다면, desktop 레이아웃 대신에 mobile 레이아웃으로 보게될 가능성이 높습니다.

처음에는 별로라고 생각했습니다. 실제로 mobile 사용자가 아니기에 mobile 레이아웃으로 보여져야 하는 이유가 있을까요?

그러나 일반적으로 media query에 rem을 사용해야 한다는 것을 깨달았습니다.

real-world 예제를 보겠습니다.

왼쪽에는 네비게이션 리스트가 있고, 오른쪽에는 content가 있습니다.

media-query-ex01

더 작은 스크린에서, content 공간을 최대화하고 네비게이션은 토글이 되도록 합니다.

여기서 기본 font size를 32px로 변경했을 때 어떻게 되는지 보겠습니다.

media-query-ex01-rem

media-query-ex01-pixel

text size를 증가했기 때문에 왼쪽 네비게이션은 점점 더 넓어집니다.(rem 기반 너비를 사용하기 때문에). 그 결과 오른쪽 메인 content 영역은 점점 더 작아집니다.

그러나 우리가 rem 기반 media query를 사용할 때, "mobile" 레이아웃으로 변경됩니다. 그 결과, content가 더 읽기 편해지고 사용자경험은 더 향상됩니다.

mobile/tablet/desktop 측면에서 media query를 익숙하게 생각하지만 사용 가능한 공간 측면에서 생각하는 것이 더 도움이 된다고 생각합니다.

mobile 사용자는 desktop 사용자보다 사용 가능한 공간이 더 적습니다. 그래서 해당 공간에 최적화된 레이아웃을 디자인합니다. 마찬가지로 누군가가 기본 font size를 늘리면 사용 가능한 공간이 줄어 듭니다. 그래서 동일한 최적화를 받아야 합니다.

수직 Margin

margin top, bottom

vertical-margin-ex01-rem

vertical-margin-ex01-pixel

텍스트(영어과 같이 수평으로 쓰여진 언어에서 작업한다고 가정)에서 vertical margin는 일반적으로 가독성을 높이는데 사용됩니다. 문단 사이에 여분의 공간을 넣으므로 인해 한 문단의 끝과 다음 문단의 시작을 빠르게 알릴 수 있습니다.

이 공간은 텍스트와 관련하여 "기능적인" 목적을 가집니다. 그것을 미학적으로 사용하지 않습니다.

이러한 이유로 사용자가 선택한 root font size로 이러한 margin을 조정하는 것이 합리적이라고 생각합니다.

"em"이 필요한 곳이 있나요?

상대 단위가 필요할 때, 대부분 rem을 사용합니다. 앞서서 언급한 "복합적인" 이슈 때문에 em보다 더 간단하고 예측가능합니다.

em 단위는 heading과 paragraph에서 margin으로 쓸 때 특히 잘 동작합니다.

h1 {
  font-size: 3rem
  margin-top: 6rem;
  margin-bottom: 1.5rem;
}
h2 {
  font-size: 2rem
  margin-top: 4rem;
  margin-bottom: 1rem;
}
h3 {
  font-size: 1.5rem;
  margin-top: 3rem;
  margin-bottom: 0.75rem;
}

rem을 사용할 땐 각 heading level이 자신의 font size를 가지기 때문에 각각에 대한 유니크한 margin을 계산해야 합니다.

대신 em을 사용하면,

h1 {
  font-size: 3rem;
}
h2 {
  font-size: 2rem;
}
h3 {
  font-size: 1.5rem;
}
h1,
h2,
h3 {
  margin-top: 2em;
  margin-bottom: 0.5em;
}

각 heading level이 유니크한 font size를 가지지만 em으로 margin 선언을 공유할 수 있습니다. em이 현재 element의 font size에 기반해서 계산되기 때문입니다.

즉, 각 heading level은 margin-top에 "2배", margin-bottom에 "0.5배"를 가집니다. 이 비율은 heading의 font size에 의해 적용됩니다.

두 개의 접근법 모두 유효하며 동일하게 접근성을 가집니다.

Widths and heights

다른 하나 더 시나리오를 보겠습니다.

width-and-height-ex01-rem

width-and-height-ex01-pixel

font-size는 rem으로 설정했습니다. 그런데 width는 어떨까요?

여기에 정말 흥미로운 trade-off가 있습니다:

  • 만약 너비를 240px로 설정하면, 버튼이 font size와 함께 더 커지지 않아 줄 바꿈이 되고 버튼 높이가 더 커집니다.
  • 만약 너비를 15rem으로 설정하면, 버튼이 font size와 함께 더 넓어집니다.

어떤 접근이 최고인가요? 상황에 따라 다릅니다!

대부분의 경우, rem을 쓰는게 더 합리적이라고 생각합니다. 버튼의 비율과 미학이 보존됩니다. 버튼이 특히 긴 단어를 가질 경우 overflow 위험이 줄어듭니다.

그러나 어떤 경우에는 pixel이 더 나은 옵션일 수 있습니다. 매우 구체적인 레이아웃을 가지고 있고 수평 공간보다 수직 공간이 더 많은 경우일 수 있습니다.

Constraints
일반적으로, 고정 너비와 높이를 설정할 때 정말로 주의깊게 해야합니다. 위 예시에서 width: 15rem을 설정하는 것은 mobile 레이아웃을 깨뜨립니다. 사용자가 기본 font size를 올릴 때 컨테이너에 비해 너무 큰 값으로 설정할 수 있기 때문입니다. maximum 100%를 사용하여 완화할 수 있습니다. max-width: 100%
비슷하게, 높이를 사용할 때 height 대신에 min-height을 사용할 때가 있습니다. 이렇게 하면 컨테이너가 자식을 포함하기 위해 필요한 만큼 커질 수 있습니다. 사용자가 font size를 늘릴 때 텍스트가 더 많은 줄로 줄바꿈되기 때문에 이것이 중요합니다.

Rem을 사용한 개발

지금까지 살펴본 것처럼 최상의 결과를 위해 rem 값을 사용해야 하는 경우가 많이 있습니다.

불행히도, 이 단위와 함께 작업하는 것은 꽤 답답할 수 있습니다. 우리의 머리로 변환하기에는 쉽지 않습니다.

  • 14px: 0.875rem
  • 15px: 0.9375rem
  • 16px: 1rem
  • 17px: 1.0625rem
  • 18px: 1.125rem
  • 19px: 1.1875rem
  • 20px: 1.25rem
  • 21px: 1.3125rem

이 리스트를 외우기 전에 rem으로 작업 경험을 개선하기 위해 할 수 있는 몇 가지 사항을 살펴보겠습니다.

The 62.5% trick

온라인에서 공유된 가장 일반적인 옵션 중 하나를 보겠습니다.

html {
  font-size: 62.5%;
}
p {
  /* Equivalent to 18px */
  font-size: 1.8rem;
}
h3 {
  /* Equivalent to 21px */
  font-size: 2.1rem;
}

이 아이디어는 root font size를 줄여 각 rem 단위가 16px가 아닌 10px이 되도록 하는 것입니다.

계산이 훨씬 쉬워지기 때문에 사람들은 이 해결법을 좋아합니다. 18px에 해당하는 rem값을 얻으려면 18을 16으로 나누는(1.125rem) 대신 소수점으로 표시(1.8rem)하면 됩니다.

하지만 솔직히 이 방법은 추천하지 않습니다. 몇 가지 이유가 있습니다.

첫째, third-party 패키지와 호환성을 깨뜨릴 수 있습니다. rem 기반 font size를 사용하는 툴팁 라이브러리를 사용하는 경우 해당 툴팁의 텍스트는 원래보다 37.5% 작아집니다! 마찬가지로 사용자가 가지고 있는 브라우저 확장프로그램을 막을 수 있습니다.

웹에서는 1rem 이 가독성있는 텍스트를 생산할 것이라는 기본 가정이 있습니다. 그 가정을 망치게 하고 싶진 않습니다.

또한 이 접근 방식에는 상당한 마이그레이션 문제가 있습니다. "점진적 적용" 할 합리적인 방법이 없습니다.(사실 할 수는 있지만 악몽에 가깝습니다) 앱 전체에 rem 단위를 사용하는 모든 선언을 업데이트해야 합니다. 게다가 모든 팀원들에게 이 문제를 해결할 가치가 있다고 논리적으로 설득해야 합니다. 논리적이지 않은 일을 논리적으로 설득해야 합니다. "기능적"으로만 사용될 트릭을 사용하는 것이 대부분의 팀에 얼마나 현실적인지 잘 모르겠습니다.

Cacluated values

calc CSS 함수는 pixel 값을 rem으로 변환하는데 사용될 수 있습니다.

p {
  /* Produces 1.125rem. Equivalent to 18px */
  font-size: calc(18rem / 16);
}
h3 {
  /* Produces 1.3125rem. Equivalent to 21px */
  font-size: calc(21rem / 16);
}
h2 {
  /* Produces 1.5rem. Equivalent to 24px */
  font-size: calc(24rem / 16);
}
h1 {
  /* Produces 2rem. Equivalent to 32px */
  font-size: calc(32rem / 16);
}

이것인 실행가능한 접근법이지만 꽤 손이 많이 갑니다. rem 값을 사용하기 위해 매번 입력해야 하는 작업이 많습니다.

Leveraging CSS variables

저자의 최애 옵션

html {
  --14px: 0.875rem;
  --15px: 0.9375rem;
  --16px: 1rem;
  --17px: 1.0625rem;
  --18px: 1.125rem;
  --19px: 1.1875rem;
  --20px: 1.25rem;
  --21px: 1.3125rem;
}
h1 {
  font-size: var(--21px);
}

모든 계산을 한 번만 수행하고 CSS 변수로 사용하여 저장할 수 있습니다. 사용해야 할 때 pixel 값을 입력하는 것 만큼 쉽습니다.

CSS 변수를 숫자로 시작하는 것이 unconventional 하지만 사양을 준수하며, 모든 주요 브라우저에서 작동하는 것으로 나타납니다.

만약 spacing scale을 가진 디자인 시스템을 사용한다면, 아래와 같은 트릭을 사용할 수 있습니다.

html {
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-md: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.3125rem;
  --font-size-2xl: 1.5rem;
  --font-size-3xl: 2.652rem;
  --font-size-4xl: 4rem;
}

저는 주로 계산해서 직접 넣는 경우가 많았던 것 같고, spotlight 계산기능을 이용하여 처음엔 16을 곱하거나 나누면서 주로 쓰다가, 자주 쓰다보니 많이 사용되는 font size는 자연스럽게 외워진 것 같습니다.
CSS-in-JS인 경우 styled-component진영에서 나온 polished 라이브러리에서 rem함수도 같이 사용했습니다.

Quick tricks vs mental models

저는 학습에 관한 철학을 가지고 있습니다. 연습과 암기에 의존하는 것보다 직관을 만드는 것이 더 좋습니다. > - mental models

이 블로그에서는 "X에서는 pixel을 사용하고, Y에는 rem을 사용"과 같은 규칙 리스트일 수 있습니다. 그러나 실제로 얼마나 유용했을까요?

사실 세상은 어지럽고 복잡합니다. 모든 가능한 시나리오를 포괄할 만큼 포괄적인 규칙 집합은 없습니다. 15년 동안 CSS를 작성한 후에도 여전히 새로운 레이아웃 문제에 직면해 있습니다.

직관을 만드는데 집중하는 것이 좋습니다. 규칙을 암기할 필요가 없습니다. 우리는 올바른 답을 찾기 위해 mental model에 의존할 수 있습니다. 훨씬 실용적입니다.

그러나 우리 대부분은 "빠른 속임수"를 배웁니다. Twitter에서 흥미로운 이야기를 가져옵니다. div를 중앙에 배치하거나, flexible grid를 적용하는 작은 snippet 을 암기합니다. 그리고 불가피하게 snippet이 예상대로 작동하지 않는 문제에 부딪힙니다. 그리고 그 이유를 알지 못합니다.

이것이 많은 개발자들이 CSS 작성을 싫어하는 이유라고 생각합니다. 고르지 못한 mental model을 가지고 있으며, 그 구멍은 언어가 항상 부서지기 쉽고 예측할 수 없게 만듭니다.

직관을 만드는데 집중합니다. CSS가 실제로 동작하는 하는 방법을 배우는 데 집중할 때, 그 언어를 사용하는 것이 즐거워집니다. CSS가 답답하긴 했지만 지금은 웹 개발에서 가장 좋아하는 부분 중 하나입니다. CSS 작성하는 것을 좋아 합니다.

마무리하며

접근성이 좋은 CSS 작성에 지속적으로 고민하는 것은 중요하지만, 실제로 꾸준히 실천하기는 쉽지 않습니다. pixel과 rem 사용에 대한 깊은 고민이 필요하며, 적절한 상황에 맞게 사용하는 것이 중요합니다.

새로운 CSS 기능들이 출시되어도 mental model을 잘 설정하는 것이 CSS를 효과적으로 다루는 데 좋은 자세인 것 같습니다. CSS 작성에 대한 즐거움과 고통은 동시에 존재하며, 디테일에 대한 집중력을 키워야 한다는 생각을 갖는 것이 중요하다고 생각합니다.

reference

마지막 업데이트

6/4/2022


Avatar

JHSeo

배우는 것을 좋아하고 관심이 많은 웹 엔지니어 입니다. 느리더라도 꾸준하게 성장하려고 노력하는 개발자입니다.