Logo

CSS로 반응형 글꼴 스타일하기

이번 포스팅에서는 화면(viewport) 너비에 따라서 적절히 반응하는 글꼴을 CSS로 어떻게 스타일하는지 알아보겠습니다.

Media Queries

반응형 글꼴하면 가장 쉽게 떠올리리 수 있는 방법이 바로 미디어 쿼리(media query)를 사용하는 것입니다.

예를 들어, 화면 너비가 480px 이상과 1024px 이상이 되었을 때 HTML의 여러 요소(element)와 클래스(class)에 적용된 글자 크기가 자동으로 늘어나게 스타일해볼까요?

h1 {
  font-size: 2rem;
}

h2 {
  font-size: 1.5rem;
}

p {
  font-size: 1rem;
}

a {
  font-size: 1rem;
}

blockquote {
  font-size: 1.5rem;
}

.hero {
  font-size: 2rem;
}

@media (min-width: 480px) {
  h1 {
    font-size: 2.75rem;
  }

  h2 {
    font-size: 2rem;
  }

  p {
    font-size: 1.25rem;
  }

  a {
    font-size: 1.25rem;
  }

  blockquote {
    font-size: 2em;
  }

  .hero {
    font-size: 2.75rem;
  }
}

@media (min-width: 1024px) {
  h1 {
    font-size: 3.5rem;
  }

  h2 {
    font-size: 2.5rem;
  }

  a {
    font-size: 1.5rem;
  }

  p {
    font-size: 1.5rem;
  }

  blockquote {
    font-size: 2.5rem;
  }

  .hero {
    font-size: 3.5rem;
  }
}

오래전 부터 쓰이던 이 방법은 보시다시피 동일한 CSS 선택자에 대해서 여러 번 스타일 선언을 해야하기 때문에 유지보수가 매우 어렵다는 단점이 있습니다. 예제 코드에서는 단지 2개의 브레이크포인트(breakpoint)를 사용했지만, 만약에 실제 프로젝트와 같이 많은 브레이크포인트를 지원해야한다면 CSS 코드의 양이 급격히 늘어날 것입니다.

뿐만 아니라 프로젝트의 규모가 커짐에 따라 자연스럽게 클래스의 종류가 늘어날텐데요. 혹시 웹사이트 디자인의 대대적인 리뉴얼(renewal) 필요하여 글꼴 크기가 전반전으로 바꿔야한다면 이런 방식으로 작성된 CSS 코드는 수정 작업이 매우 고통스러울 수 있을 것입니다.

CSS Variables

CSS 변수(CSS variable)를 활용하면 미디어 쿼리를 사용할 때 발생하는 중복 CSS 코드를 획기적으로 줄일 수 있습니다.

CSS 사용자 속성(CSS custom property)라고도 불리는 CSS 변수에 대한 자세한 설명은 별도 포스팅을 참고 바랍니다.

예를 들어, 위의 경우 아무리 스타일할 요소(element)나 클래스(class)가 많더라도 결국에는 3가지 종류의 글꼴 크기가 필요하다는 것을 알 수 있는데요. 이 말은 미디어 쿼리로 3개의 CSS 변수에 화면 너비에 따라서 다른 글자 크기를 설정해주면 된다는 얘기겠죠?

:root {
  --font-size-lg: 2rem;
  --font-size-md: 1.5rem;
  --font-size-sm: 1rem;
}

@media (min-width: 480px) {
  :root {
    --font-size-lg: 2.75rem;
    --font-size-md: 2rem;
    --font-size-sm: 1.25rem;
  }
}

@media (min-width: 1024px) {
  :root {
    --font-size-lg: 3.5rem;
    --font-size-md: 2.5rem;
    --font-size-sm: 1.5rem;
  }
}

h1 {
  font-size: var(--font-size-lg);
}

h2 {
  font-size: var(--font-size-md);
}

p {
  font-size: var(--font-size-sm);
}

a {
  font-size: var(--font-size-sm);
}

blockquote {
  font-size: var(--font-size-md);
}

.hero {
  font-size: var(--font-size-lg);
}

이렇게 CSS 변수를 사용하니 같은 원소나 클래스에 대해서 딱 한번만 스타일해주면 되므로 CSS 코드를 유지보수하기가 훨씬 수월해지겠죠?

그래도 한 가지 아쉬운 부분이 있는데요… 한번 위 화면을 코드펜에서 열고 브라우저 창의 너비를 늘렸다가 줄였다가 해보세요. 글자 크기가 마치 계단처럼 뚝뚝 끊기면서 다소 투박하게 커지거나 작아지는 게 보이시나요?

이러한 현상은 반응형 UI를 구현할 때 항상 겪게 되는 미디어 쿼리의 한계라고 볼 수 있는데요. 브레이크포인트를 아주 촘촘히 잡아주면 어느 정도 상쇄할 수 있지만 근본적인 해결책은 되지 않을 것입니다.

vw 상대 유닛

어떻게 하면 좀 더 물흐르듯이 부드럽게 화면 너비에 따라서 글자 크기를 늘리거나 줄을 수 있을까요?

바로 vw라는 상대 유닛(relative unit)을 사용하면 되는데요. vw는 현재 화면(viewport) 너비에 따라서 수시로 변할 수 있는 단위입니다. 1vw가 화면 너비의 1%의 길이를 의미하고, 100vw는 화면 너비의 100%의 길이를 의미합니다. 예를 들어, 4vw는 화면 너비가 400px 일 때는 16px를 의미하지만, 화면 너비가 800px 일 때는 32px을 의미하게 됩니다.

이러한 vw 유닛의 특성을 활용하면 굳이 미디어 쿼리 없이도 글자 크기가 화면 너비에 비례해서 자동으로 늘어나거나 줄어들게 할 수 있습니다.

:root {
  --font-size-lg: 5vw;
  --font-size-md: 4vw;
  --font-size-sm: 3vw;
}

h1 {
  font-size: var(--font-size-lg);
}

h2 {
  font-size: var(--font-size-md);
}

p {
  font-size: var(--font-size-sm);
}

a {
  font-size: var(--font-size-sm);
}

blockquote {
  font-size: var(--font-size-md);
}

.hero {
  font-size: var(--font-size-lg);
}

하지만 이 방법은 웹 접근성(accessibility) 측면에서 치명적인 문제가 있는데요. 바로 화면 너비가 너무 좁아지면 글자가 읽기 힘들 정도로 작아질 수 있으며, 화면 너비가 너무 넓어지면 글자가 지나치게 커질 수 있다는 점입니다. 🙄

calc() 함수

CSS의 calc() 함수를 사용하면 vw 단위를 사용했을 때 발생할 수 있는 문제를 어느 정도 해결할 수 있는데요. vw 단위를 rem이나 px과 같은 단위와 함께 사용하면 글자가 너무 작아지는 것을 방지해줄 수 있습니다.

:root {
  --font-size-lg: calc(2rem + 4vw);
  --font-size-md: calc(1.5rem + 3vw);
  --font-size-sm: calc(1rem + 2vw);
}

h1 {
  font-size: var(--font-size-lg);
}

h2 {
  font-size: var(--font-size-md);
}

p {
  font-size: var(--font-size-sm);
}

blockquote {
  font-size: var(--font-size-md);
}

a {
  font-size: var(--font-size-sm);
}

이론적으로는 calc() 함수에 넘기는 공식에 따라 다르겠지만 실제 프로젝트에서는 이 방법이 그렇게 많이 쓰이지는 않는데요. 아무래도 여러 화면 너비를 아우를 수 있는 공식을 만들어내기가 현실적으로 좀 어렵기 때문이 아닐까 싶습니다.

clamp() 함수

마지막으로 소개해드릴 방법은 비교적 CSS에 최근에 추가된 clamp() 함수를 활용하는 것인데요. clamp() 함수를 사용하면 글자 크기의 상하한선을 정할 수 있어서 좀 더 안전하게 반응형 글꼴을 구현할 수 있기 때문입니다.

clamp() 함수를 3개의 인자를 받는데요. 첫 번째 인자로는 최소값을 넘기고, 두 번째 인자로는 화면 크기에 따라서 얼마나 늘어나고 줄어들지를 넘기며, 세 번째 인자로는 최대값을 넘깁니다.

:root {
  --font-size-lg: clamp(2rem, 4vw, 3.5rem);
  --font-size-md: clamp(1.5rem, 3vw, 2.5rem);
  --font-size-sm: clamp(1rem, 2vw, 1.5rem);
}

h1 {
  font-size: var(--font-size-lg);
}

h2 {
  font-size: var(--font-size-md);
}

p {
  font-size: var(--font-size-sm);
}

blockquote {
  font-size: var(--font-size-md);
}

a {
  font-size: var(--font-size-sm);
}

이렇게 clamp() 함수를 사용하면 아무리 화면 너비가 좁아지거나 넓어지더라도 글자 크기가 정해진 범위 내에서만 변하기 때문에 웹 접근성 걱정을 완전히 떨쳐낼 수 있습니다.

예전에는 clamp() 함수를 지원하지 않는 브라우저가 좀 있어서 이 방법을 알더라도 쓰기는 꺼리는 경우가 있었는데요. 요즘에는 대부분의 브라우저에서 clamp() 함수를 지원하기 때문에 그러한 걱정없이 사용할 수 있게 되었습니다. 그래도 걱정이 되신다면 아래와 같이 clamp() 함수 대신에 max 함수와 min 함수를 조합해서 쓰는 방법도 있으니 참고 바랍니다.

:root {
  --font-size-lg: max(2rem, min(4vw, 3.5rem));
  --font-size-md: max(1.5rem, min(3vw, 2.5rem));
  --font-size-sm: max(1rem, min(2vw, 1.5rem));
}

마치면서

이상으로 CSS로 반응형 글꼴을 구현하기 위한 다양한 방법에 대해서 살펴보고 각 접근 방법에서 발생할 수 있는 문제점을 같이 해결해보았습니다. 디바이스의 화면 크기가 맞춰서 최적화된 글꼴을 제공하는데 본 포스팅이 조금이나마 도움이 되었으면 좋겠습니다. 😄