part2. Media Query 실전 적용

Media Query 기초편과 이어집니다.

미디어 쿼리를 사용하면 실행 중인 기기나 조건에 따라 스타일을 다르게 지정할 수 있고,
반응형 웹, 다크 모드와 같은 다채롭고 트렌디한 웹 구현이 가능해집니다.
이렇게 활용도 높은 미디어 쿼리를 실무에 어떻게 적용할 수 있는지 예시와 함께 설명드리겠습니다.

목차

  1. 너비&높이 분기
  2. 가로모드 분기
  3. 종횡비 분기
  4. 다크 모드 분기
  5. 해상도 분기
  6. 마우스 오버 분기

1. 너비 분기 & 높이 분기

먼저 너비&높이 분기는 미디어 쿼리가 가장 많이 활용되는 케이스로, 반응형 웹에서는 필수적으로 사용됩니다.

실무 하다 보면 이 케이스를 ‘해상도 분기’라는 표현으로 사용하기도 합니다.
하지만 ‘해상도’와 ‘화면 너비&높이’의 정의는 다르기 때문에 오용된 표현입니다.
해상도 분기와 관련된 설명은 아래 해상도 분기에서 확인해 주시고, 이번 케이스에서는 ‘너비 분기’, ‘높이 분기’로 표현하여 설명하겠습니다.

1-1. 뷰포트(viewport) 설정하기

‘뷰포트’란 화면에 보이는 창의 영역을 말합니다.
PC에서는 브라우저를 리사이즈할 수 있기 때문에 뷰포트 크기를 수시로 변경할 수 있는 반면,
대부분의 모바일 기기에서는 뷰포트 크기를 조절할 수 없습니다.

만약 모바일 기기에서 뷰포트가 설정되지 않은 페이지를 열었다면,
<html> 태그는 기본 980px 너비를 가지고 980px보다 넓은 화면을 가진 기기에서는 화면 너비만큼 가지게 됩니다.

👉 테스트 페이지 확인

위 이미지처럼 화면 너비 375px인 기기에서 뷰포트 너비가 980px로 되어 있을 경우, 콘텐츠가 980px에 비례하여 작게 보이므로 가독성이 떨어집니다.
따라서 반응형 웹을 구현할 때는 콘텐츠가 어떤 기기에서든 가독성 있도록 뷰포트 너비를 화면 너비와 동일하게 설정해주는 것이 좋습니다.

뷰포트 설정 방법은 아래와 같습니다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    …
    <meta name="viewport" content="width=device-width, initial-scale=1">
    …
  </head>
  …

<head> 태그 내에 <meta name="viewport"> 요소를 추가한 후,
뷰포트 너비(width)를 기기 너비(device-width)와 동일하게 설정하고, 처음 접근했을 때 화면 비율(initial-scale)을 1배로 설정해 줍니다.
이렇게 설정하면 아래 이미지와 같이 뷰포트 너비가 기기의 화면 너비와 동일해집니다.

👉 테스트 페이지 확인

1-2. 적용하기

너비&높이 분기를 구현할 때는 widthheight 미디어 특성을 사용하는데, 주로 min-max- 접두사를 붙여 미디어 쿼리를 구현합니다.

아래 다양한 너비&높이 분기 예시들이 있습니다.

@media (width: 375px) { /* 뷰포트 너비 375px에서만 적용 */ }
@media (min-width: 375px) { /* 뷰포트 너비 375px이상에서 적용 */ }
@media (max-width: 375px) { /* 뷰포트 너비 375px이하에서 적용 */ }
@media (min-height: 768px) { /* 뷰포트 높이 768px이상에서 적용 */ }
@media (min-width: 375px) and (max-width: 767px) { /* 뷰포트 너비 375px이상 767px이하에서 적용 */ }
@media (min-width: 768px) and (min-height: 1024px) { /* 뷰포트 너비 768px이상이면서 뷰포트 높이 1024이하에서 적용 */ }

이 중 min-width 미디어 특성을 사용해서 너비 분기 예시를 살펴보겠습니다.

아래 예시에 적용한 리스트 디자인은 반응형 웹에서 가장 잘 활용되는 디자인입니다.
min/max-width 미디어 특성과 CSS width/height에 % 단위를 활용하면
각 뷰포트 너비 범위에 맞춰 UI를 최적의 크기로 보여줄 수 있습니다.
직접 화면을 리사이즈하면서 아래 예시에 적용된 미디어 쿼리 동작을 확인해 보세요.

1-3. 모바일 퍼스트 혹은 데스크톱 퍼스트

반응형 웹 UI를 개발하기 전에 해당 서비스가 모바일 퍼스트 디자인인지 데스크톱 퍼스트 디자인인지 파악하셔야 합니다.

모바일 퍼스트(Mobile First) 

모바일 퍼스트란 모바일 사이즈의 화면을 먼저 디자인하여 모바일 사용자의 경험을 우선한다는 것을 의미합니다.
이 경우 UI 개발도 모바일 사이즈의 화면부터 마크업 해 나가면 됩니다.
디자인 분기점을 확인하고 min-width 미디어 특성을 사용하여 점점 화면을 넓혀가며 스타일을 변경시켜줍니다.

// 예제
body {
    background-color: red;
}
@media (min-width: 375px) {
    body {
        background-color: green;
    }
}
@media (min-width: 768px) {
    body {
        background-color: blue;
    }
}

데스크톱 퍼스트(Desktop First)

반대로 데스크톱 퍼스트란 데스크톱 사이즈의 화면을 먼저 디자인하는 것을 의미합니다.
이 경우엔 UI 개발을 데스크톱 사이즈의 넓은 화면부터 마크업 합니다.
이때도 디자인 분기점을 확인하고 max-width 미디어 특성을 사용하여 점점 화면을 좁혀가며 스타일을 적용시킵니다.

// 예제
body {
    background-color: blue;
}
@media (max-width: 767px) {
    body {
        background-color: green;
    }
}
@media (max-width: 374px) {
    body {
        background-color: red;
    }
}

브라우저를 리사이즈하며 아래 두 예시를 비교해 보세요.
(codepen내 배율(1× 0.5× 0.25×)버튼을 이용해 확인 가능합니다.)

<h1>Mobile First</h1>
body {
    background-color: red;
}
@media (min-width: 375px) {
    body {
        background-color: green;
    }
}
@media (min-width: 768px) {
    body {
        background-color: blue;
    }
}

h1 {
  color: #fff;
}
<h1>Desktop First</h1>
body {
    background-color: blue;
}
@media (max-width: 767px) {
    body {
        background-color: green;
    }
}
@media (max-width: 374px) {
    body {
        background-color: red;
    }
}

h1 {
  color: #fff;
}

각 스타일로 구현한 결과는 동일합니다.
다만 두 스타일을 선택하는 전략 면에서 차이가 있는데, 자세한 내용은 이후 등록될 더 알아보기 파트의 ‘성능 팁: 전략 – 모바일 퍼스트 vs 데스크톱 퍼스트’ 내용에서 확인해 보세요.

2. 가로모드 분기

뷰포트가 가로로 넓은 스타일인지 세로로 길쭉한 스타일인지에 따라 스타일을 다르게 적용하고 싶다면 orientation 미디어 특성을 사용합니다.
보통 모바일/태블릿의 세로모드나 가로모드 스타일을 분기할 때 유용합니다.

노파심에 덧붙이면,
미디어 쿼리로 ‘가로모드’라는 모바일/태블릿의 기능을 선택하는 것이 아니라
뷰포트의 너비와 높이를 비교하여 가로모드/세로모드를 구분합니다.

@media (orientation: portrait) { /* (너비 <= 높이)인 화면에서 적용 */ }
@media (orientation: landscape) { /* (너비 > 높이)인 화면에서 적용 */ }

아래는 orientation 속성을 활용한 풀스크린 예시 화면입니다.
왼쪽에는 모바일 화면에 적합한 영상을 적용한 모습이고, 오른쪽에는 데스크톱 화면에 적합한 영상을 적용한 모습입니다

구현 방법은 간단합니다. orientation 미디어 특성을 사용해서 뷰포트의 너비/높이 비율에 맞춰 영상을 교체해서 노출시킵니다.
너비가 넓은 화면에서는 가로가 긴 형태의 영상을 노출하고, 높이가 긴 화면에서는 세로가 긴 형태의 영상을 노출시킵니다.

<video src="세로가 긴 형태의 영상" class="portrait_video"></video>
<video src="가로가 긴 형태의 영상" class="landscape_video"></video>
.portrait_video {
    width: auto;
    height: 100%;
}
.landscape_video {
    display: none;
    width: 100%;
    height: auto;
}

@media (orientation: landscape) {
    .portrait_video {
        display: none;
    }
    .landscape_video {
        display: block;
     }
}

3. 종횡비 분기

앞서 살펴본 가로모드 분기와 유사하지만, 좀 더 상세한 수치값으로 뷰포트의 종횡비를 쿼리 할 수 있습니다.
종횡비란 뷰포트의 너비를 높이로 나눈 값입니다. 뷰포트의 너비가 길수록, 높이가 낮을수록 종횡비 값이 커집니다.

종횡비 분기는 aspect-ratio 미디어 특성을 사용합니다.
해당 속성으로 특정 종횡비의 화면만 선택할 수도 있고, min-max- 접두사를 붙여 종횡비 범위를 지정할 수도 있습니다.

@media (aspect-ratio: 9/16) { /* 종횡비 9:16인 뷰포트에서 적용 */ }
@media (min-aspect-ratio: 9/16) { /* 종횡비가 9:16보다 크거나 같은 뷰포트에서 적용 */ }
@media (max-aspect-ratio: 2/3} { /* 종횡비가 2:3보다 작거나 같은 뷰포트에서 적용 */ }

종횡비 분기는 뷰포트를 꽉 채우는 풀스크린 디자인의 UI를 개발할 때 유용합니다.
화면의 종횡비에 따라 UI의 배치를 바꿀 필요가 있다면 aspect-ratio 속성을 활용하면 됩니다.
종횡비에 따라 UI를 바꿀 일이 있을까 의문이 드신다면, 예제로 같이 살펴보겠습니다.

아래에 각기 다른 비율의 두 가지 이미지가 있습니다.
하나는 넓어서 데스크톱 화면에 잘 어울리고, 하나는 길쭉해서 모바일 화면에 잘 어울립니다.

1. 데스크톱 화면에 적합한 넓은 이미지2. 모바일 화면에 적합한 길쭉한 이미지

풀스크린 웹페이지 디자인이라는 가정 하에, 두 이미지만으로 어느 화면에서든 최적의 뷰로 구현하려면 어떻게 할까요?

aspect-ratio 미디어 특성을 사용해서 이미지 종횡비와 뷰포트 종횡비를 비교해가면서 스타일을 쿼리 합니다.
아래 테스트 페이지를 리사이즈하면서 이미지가 언제 교체되고 언제 CSS 스타일이 바뀌는지 직접 확인해 보세요.
(각 분기 구간은 테두리 점선 색으로 구분)

👉 테스트 페이지 확인

뷰포트 종횡비 > 16/9 이상16/9 >= 뷰포트 종횡비 > 11/1411/14 >= 뷰포트 종횡비

aspect-ratio 미디어 쿼리 또한 동작 원리는 간단합니다.
먼저 넓은 이미지(미니언즈 3명)를 뷰포트 너비에 꽉 채우고,
‘넓은 이미지의 종횡비(16/9)’보다 ‘뷰포트 종횡비’가 작아질 경우 이미지를 뷰포트 높이에 꽉 채웁니다.
이후 ‘뷰포트 종횡비’가 ‘길쭉한 이미지(미니언즈 2명)의 종횡비(11/14)’보다 더 작아지면 길쭉한 이미지를 노출시키고 뷰포트 높이에 꽉 채워줍니다.

4. 다크 모드 분기

브라우저를 다크 테마로 설정하면 CSS 스타일을 따로 선언하지 않아도 브라우저에서 테마에 맞게 색상을 변경해 줍니다.

하지만 어색한 부분이 생기기도 해서 다크 모드 디자인을 직접 적용하기도 합니다.
이때 prefers-color-scheme 미디어 특성을 사용하면 라이트 모드와 다크 모드를 분기할 수 있고, 각 모드에 맞춰 색상 값이나 UI를 변경할 수 있습니다.

@media (prefers-color-scheme: light) { /* 라이트모드에서 적용 */ }
@media (prefers-color-scheme: dark) { /* 다크모드에서 적용 */ }

4-1. 적용하기

prefers-color-scheme 미디어 특성을 사용해서 CSS 스타일을 분기해 보았습니다.
브라우저에서 직접 라이트/다크 테마를 바꿔가며 스타일이 어떻게 다르게 적용되는지 비교해 보세요.

<div class="status">
  <strong class="text_lightmode"><span class="point">라이트모드</span> 실행 중</strong>
  <strong class="text_darkmode"><span class="point">다크모드</span> 실행 중</strong>
  <svg class="svg_icon" xmlns="http://www.w3.org/2000/svg"
       viewBox="0 0 100 100">
    <path d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
      <animateTransform 
          attributeName="transform" 
          attributeType="XML" 
          type="rotate"
          dur="1s" 
          from="0 50 50"
          to="360 50 50" 
          repeatCount="indefinite" />
    </path>
  </svg>
</div>
body {
  background-color: #fff;
}
.text {
  color: #000;
}
.point {
  color: #0a7afb;
}
.svg_icon path {
  fill: #0a7afb;
}
.text_darkmode {
  display: none;
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #222;
  }
  .text {
    color: #ddd;
  }
  .point {
     color: #47b4a4;
  }
  .svg_icon path {
    fill: #47b4a4;
  }
  .text_darkmode {
    display: inline;
  }
  .text_lightmode {
    display: none;
  }
}

/* ************************** */

html, body {
  height: 100%;
  margin: 0;
}
.status {
  display: flex;
  height: 100%;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.text_lightmode, .text_darkmode {
  font-size: 20px;
  line-height: 1.2;
}
svg {
  width: 50px;
  height: 50px;
}

아래와 같이 브라우저 색상 테마에 따라 다른 스타일이 적용되는 것 확인하실 수 있습니다.

예시 코드를 보면 동일한 색(#0a7afb)이 두 번 사용되고, 다크 모드 시 동일하게 다른 색(#47b4a4)으로 바뀌고 있습니다.
이때 중복되는 값을 변수로 활용하면 더 쉽고 빠르게 적용할 수 있습니다.
라이트 모드가 기본인 서비스일 경우, 라이트모드 기준으로 색상 값을 변수에 저장하고,
다크 모드에서는 대응되는 변수의 색상 값만 교체해 주면 됩니다.

적용 방법은 아래 예시 코드를 참고해 보세요.

:root {
  --background-color: #fff;
  --font-color: #000;
  --point-color: #0a7afb;
}
body {
  background-color: var(--background-color);
}
.text {
  color: var(--font-color);
}
.point {
  color: var(--point-color);
}
.svg_icon path {
  fill: var(--point-color);
}

@media (prefers-color-scheme: dark) {
  :root {
    --background-color: #222;
    --font-color: #ddd;
    --point-color: #47b4a4;
  }
}

4-2. 브라우저 지원 범위

prefers-color-scheme은 미디어 쿼리 레벨5로 일부 브라우저에서 지원하지 않을 수 있습니다.

‘적용하기’의 예시 코드를 3개의 모바일 브라우저에서 실행해 보았습니다.

prefers-color-scheme 미디어 특성에 대한 지원 여부와 별개로 삼성 인터넷 브라우저에서는 미디어 쿼리 대신 자체 로직을 통해 다크 모드를 대응하고 있습니다.
따라서 @media (prefers-color-scheme: dark) {} 블록 내에 선언한 CSS 스타일이 바로 적용되지 않습니다.
삼성 인터넷 브라우저 내에 prefers-color-scheme으로 적용된 스타일을 확인하는 기능이 있다고는 하지만,
사용자가 직접 설정해야 하기 때문에 실무에서 활용하기는 어려워 보입니다.

다크모드에 대해서 이 글에서는 이 정도로 다루고, 좀 더 깊은 이해를 위해서는 다크 모드를 주제로 작성된 다른 글들을 참고 바랍니다.

5. 해상도 분기

기기의 디스플레이 해상도에 따라 스타일을 분기할 수 있습니다.

5-1. 해상도 이해하기

해상도란 단위 안에 표현되는 점의 수로, 간단하게 ‘선명도’ 혹은 ‘화질’ 로 이해하면 쉽습니다.
해상도는 다양한 단위를 기준으로 삼을 수 있습니다.
inch가 될 수도, cm가 될 수도, px이 될 수도 있는데, 이 기준 단위에 따라 해상도 단위도 다양해집니다.

단위
dpiinch 당 점의 수
dpcmcm 당 점의 수
dppxpx 당 점의 수

5-2. 적용하기

출력 기기마다 해상도가 다르기 때문에, 각 기기의 해상도에 맞춰 이미지 화질을 대응하고 싶은 경우 활용할 수 있습니다.
실무에서 이미지를 삽입할 때 PC 웹에서는 정 사이즈 이미지를, 모바일 웹에서는 2배 사이즈의 이미지를 사용하는 것과 동일한 원리입니다.

해상도 분기는 resolution 미디어 특성을 사용합니다.
특정 해상도만 선택할 수도 있고, min-max- 접두사를 붙여 해상도 범위를 지정할 수도 있습니다.

@media (resolution: 192dpi) { /* 해상도 192dpi인 뷰포트에서 적용 */ }
@media (min-resolution: 2dppx) { /* 해상도 2dppx보다 크거나 같은 뷰포트에서 적용 */ }
@media (max-resolution: 100dpcm} { /* 해상도 100dpcm보다 작거나 같은 뷰포트에서 적용 */ }

해상도에 따라 적합한 화질의 이미지를 적용해 볼 수 있는 테스트 페이지를 만들어보았습니다.
먼저 💻mydevice.io에서 현재 기기의 dppx를 확인하고, 👉테스트 페이지에서 비교해 보세요.

직접 비교하기 어렵다면 아래 결과 표를 참고해 보셔도 좋습니다.

LG Gram/저해상도 모니터 > ChromeMacbook Pro > ChromeGalaxy21+ > Samsung Internet

5-3. 브라우저 지원 범위

Safari 브라우저 15.6 이전 버전은 resolution 미디어 특성을 지원하지 않습니다.
Safari 구 브라우저에 대한 대안으로 비표준 미디어 쿼리 속성인 -webkit-device-pixel-ratio를 사용할 수 있습니다.

@media (min-resolution: 2dppx) {  }
@media (-webkit-min-device-pixel-ratio: 2) {  }

👉 webkit-device-pixel-ratio 테스트 페이지 확인

Mac > Safari 15.5Mac > Safari 15.5Mac > Safari 16.0Mac > Safari 16.0
min-resolution 적용-webkit-min-device-pixel-ratio 적용min-resolution 적용-webkit-min-device-pixel-ratio 적용

6. 마우스 오버 분기

반응형 웹에서 :hover 가상 선택자로 마우스 오버 스타일 선언 시,
모바일 기기에서는 터치 스타일로 적용되는 이슈가 발생합니다.

이때 입력 수단과 관련한 미디어 특성을 활용하면 마우스 오버 기능을 분기해서 적용할 수 있습니다.

hover

hover 미디어 특성을 사용하면 사용자의 주 입력 메커니즘이 요소 위에 올라갈 수 있는지를 확인할 수 있습니다.

  • none : 주 포인팅 입력 메커니즘이 없거나, 주 입력 메커니즘이 요소 위에 올라갈 수 없음
  • hover : 주 입력 메커니즘이 요소 위에 올라갈 수 있음

해당 미디어 특성을 활용해서 아래 예시 코드와 같이 마우스 오버 스타일을 구현할 수 있습니다.

@media (hover: hover) {
  a:hover {  }
}

하지만 hover 특성만으로는 입력 수단을 세분화하기 어려워 pointer 특성도 결합하여 미디어 쿼리를 만들어야 합니다.

pointer

pointer 미디어 특성을 사용하면 사용자가 마우스와 같은 포인팅 장치를 가졌는지, 주 포인팅 장치가 얼마나 정확한지에 따라 쿼리 할 수 있습니다.

  • none : 주 입력 메커니즘에 포인팅 장치가 없음
  • coarse : 주 입력 메커니즘에 정확도가 높진 않지만 포인팅 가능한 장치가 있음
  • fine : 주 입력 메커니즘에 정확한 포인팅 장치가 있음
@media (pointer: fine) {  }
@media (pointer: coarse) {  }

적용하기

hoverpointer 두 특성을 결합하면 입력 수단에 따른 기기를 좀 더 세분화해서 분류할 수 있습니다.
아래 두 미디어 특성의 결합에 따라 분류되는 기기들입니다.

위 분류를 참고해서 hover: hover와 pointer: fine을 결합하여 쿼리하면,
hover 기능과 정확한 포인팅 장치를 가진 기기에만 스타일을 적용할 수 있습니다.

@media (hover: hover) and (pointer: fine) {  }

이번 글에서 정리한 내용은 여기까지이며 이어지는 ‘Media Query 더 알아보기‘ 글을 통해 미디어 쿼리와 관련한 더 깊은 내용을 알아보세요.

참조


0개의 댓글

답글 남기기

아바타 플레이스홀더

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다