• 반응형 웹 뚝딱 만들기 (1) - 뷰포트 메타태그와 미디어 쿼리

    2020. 8. 12.

    by. 나나 (nykim)

    320x100

     

    이 글은 공동 기술 블로그(tech.yeon.me)에도 올린 글입니다.
    (여기에서도 숨겨진 좋은 글을 발견할지도 몰라요!)

     

     

    프롤로그 

    모바일 사용자가 점점 늘어나는 요즘 반응형으로 만든 웹 사이트를 쉽게 찾아볼 수 있습니다.

    빠트릴 수 없는 기술이 된 만큼, 오늘은 반응형을 만들기 위한 몇 가지 방법들을 정리해봤습니다 🤘

     

    이 글에서는 반응형을 위한 기본인 뷰포트와 미디어 쿼리를 다루며,

    다음 글에서는 CSS 속성을 활용해 빠르게 반응형을 만드는 법을 다룰 생각입니다. (투비 컨티뉴!)

     

     

     


     

    1. meta 태그 - viewport

     

    VS CODE 등에서 자동 html을 생성해 보신 적이 있으신가요? (! 치고 tab키를 누르면 뿅하고 나오는 그거요)

    이때 기본으로 작성되는 <header> 내에 아래의 태그가 들어간 것을 볼 수 있습니다.

     

    <meta name="viewport" content="width=device-width,initial-scale=1">

     

    이게 얼마나 좋은 거길래 묻지도 따지지도 않고 넣어주는 걸까요?

    이 뷰포트 메타 태그는 모바일 반응형을 만들 때 필요합니다.

     

    뷰포트(viewport)란, 웹 페이지에서 사용자의 보이는 영역(visible area)를 말합니다. 따라서 이는 기기별로 달라지게 됩니다.

    같은 페이지라 하더라도 모바일과 태블릿은 화면 크기가 다르기 때문에 사용자가 볼 수 있는 범위가 다르니까요.

    즉, 이 태그는 뷰포트를 지정해주는 역할을 합니다.

     

     

     

    속성을 좀 더 살펴보죠!

     

    우선, width는 뷰포트의 가로 크기를 정합니다. 만약 500이란 값을 넣으면, 500만큼의 픽셀 개수를 가진 너비로 설정됩니다.

    device-width라고 지정했으니, 기기의 스크린 너비에 맞추라는 뜻이 됩니다.

     

    한편, initial-scale은 페이지에 처음 접속했을 때 보여질 확대 배율을 설정합니다.

    1로 정하면 CSS 픽셀과 화면에 보여질 픽셀이 1:1을 이룹니다.

    달리 말해, 이 값이 1보다 작다면 페이지는 축소되어 보이고, 1보다 크다면 확대되어 보입니다.

     

     

    width 뷰포트의 가로 크기
    initial-scale 페이지 처음 접속 시 보여질 배율

     

     

    만약 이러한 값들이 정해지지 않은 페이지를 모바일에서 본다면 어떤 느낌일까요?

    뷰포트를 정하지 않으면 width는 기본값인 980이 됩니다. 그럼, width:100%를 지정한 요소는 그냥 980px 값을 가지게 되겠죠.

    따라서 모바일은 이 980px짜리 페이지를 어떻게든 사용자에게 보여주려고 배율을 줄이게 됩니다. 바로 이렇게요.

     

    (좌) 뷰포트를 지정하지 않은 경우 / (우) 뷰포트 너비를 device-width로 지정한 경우 

     

    반면, 오른쪽 이미지처럼 메타태그를 지정한 경우엔 width:100%가 iPhone5에선 320px, iPad에선 768px이 됩니다.

    따라서 사용자는 보다 편안하게 웹 페이지를 탐색할 수 있습니다.

     

    뷰포트 메타태그에는 아래의 속성들도 들어갈 수 있어요.

     

     

    user-scalable 사용자의 축소/확대 허용 여부. 초기값은 yes며, no로 금지 가능
    minimum-scale 뷰포트의 최소 배율값 (0~10)
    maximum-scale 뷰포트의 최대 배율값 (0~10)

     

     

    네이버 모바일의 마크업을 보면 user-scalable=no이 지정된 걸 볼 수 있습니다.

    즉, 사용자가 줌인이나 줌아웃을 할 수 없도록 막아둔 상태입니다.

    minimum-scalemaximum-scale도 1.0으로 지정했기 때문에 고정된 UI를 항시 보여줄 수가 있습니다.

     

    다만, 이 경우엔 웹 접근성 관련해 경고 카드를 받을 수도 있어요.

    아래는 이걸 본 validator.w3.orglighthouse가 날린 경고장입니다 😞

     

     

    정리하자면, 메타 태그로 뷰포트를 지정해 주면 모바일 친화적인 UI를 제공할 수 있게 됩니다.

     

    적응형(반응형이 아닌 거요!)이라면 여기에다 모바일에 맞춘 CSS를 작성하면 됩니다.

    하지만 우리가 필요한 건 반응형이에요. 그럴 때 필요한 건? 미디어쿼리죠!

     

     


     

    2. 미디어 쿼리

    미디어 쿼리는 반응형을 위한 기본이자 핵심입니다.

    잘 활용하면 어떤 디바이스에서도 예쁜 웹 페이지를 만들 수 있어요.

     

    media라는 키워드로 사용하며, 아래는 미디어 쿼리를 활용한 예시입니다.

     

    .title {
      font-size: 40px;
    }
    
    @media (max-width: 600px) {
      .title {
        font-size: 20px;
      }
    }

     

    타이틀의 텍스트 크기를 40px로 하되, 600px보다 작은 화면에서는 20px로 줄인다는 내용입니다. 참 쉽죠? 🎨

     

    See the Pen dyPOKKZ by nanalike (@nykim_) on CodePen.

     

     

     

     

     

     

    그러니 모바일 시안을 받았다면, 분기점을 정한 뒤 시안의 사이즈에 맞춰 미디어 쿼리를 지정해 주면 됩니다.

    (Zeplin 등으로 전달 받았다면 사이즈를 그대로 사용하시면 돼요.

    다만, PSD의 경우 이미지 사이즈 때문에 2배 사이즈로 받는 경우가 있기에 나누기 2만큼의 값을 지정해 줍니다.)

     

     

     

     

    미디어쿼리 적용

    미디어 쿼리를 적용하는 법은 간단합니다.

     

    1) CSS 파일 내에 직접 작성하거나

    @media (min-width:768px) {}

     

    2) <link> 태그에 media 속성을 설정해 사용합니다

    <link rel="stylesheet" media="all and (min-width:1200px)" href="desktop.css" >
    <link rel="stylesheet" media="all and (min-width:768px) and (max-width:1199px)" href="laptop.css" >

     

    하지만 대부분의 경우 첫 번째 방법을 써요!

    왜냐하면 두 번째 방법처럼 CSS를 분리해 두더라도, 브라우저는 '에라 모르겠다'며 일단 모든 CSS 파일을 다운로드하기 때문입니다.

    HTTP 요청이 많아지면 성능이 그만큼 저하되므로, CSS 파일 내에서 @media로 분리하는 것을 추천합니다.

     

     

     

     

    구문

    미디어 쿼리 구문은 '미디어 유형 / 논리 연산자 / 특성'으로 이루어지며 대소문자를 구분하지 않습니다.

     

     

    미디어 유형

     

    all 모든 디바이스 대상
    print 인쇄 결과물 및 인쇄 미리보기 문서
    screen 화면 대상

     

    @media screen {}을 쓰면, 스크린이 있는 디바이스에서만 적용이 됩니다.

    이외에도 여러 유형이 있지만 제일 많이 쓰는 건 all과 screen이에요.

    유형을 지정하지 않는 경우엔 디폴트인 all이 지정됩니다.

     

     

     

    논리 연산자

     

    and 모든 쿼리가 참이여야 적용
    not 모든 쿼리가 거짓이여야 적용
    , (comma) 어느 하나라도 참이면 적용 (or에 해당)
    only 미디어쿼리를 지원하는 브라우저만 적용

     

    여기서 only라는 생소한 키워드가 나왔네요!

    @media only screen {}을 사용하면 미디어쿼리를 지원하지 않는 브라우저(IE8 이하)에서는 해당 스타일을 무시합니다.

     

    더 정확히 얘기하자면, 미디어쿼리를 지원하는 브라우저는 only 키워드를 만나면 무시하고 그대로 읽어들입니다.

    반면에 지원하지 않는 브라우저는 only라는 이름의 미디어 유형이 있다고 생각합니다.

    하지만 only라고 정의된 미디어 종류는 없기 때문에, 결국 아무것도 적용되지 않는 거죠.

     

     

     

    특성

     

    width 뷰포트 너비
    height 뷰포트 높이
    aspect-ratio 뷰포트 가로세로비
    orientation 뷰포트 방향

     

    orientation의 경우, 아쉽게도(?) 모바일 전용 속성이 아닙니다.

    '가로가 세로보다 긴지' 또는 '세로가 가로보다 긴지'에 따라 가로 모드/세로 모드라고 판단합니다.

    만약 모바일 환경에만 특정 CSS를 적용해야 한다면 User Agent를 판단 후 분기해야 합니다.

     

    이런 식으로 유형, 연산자, 특성을 조합해 다양한 구문을 만들어 낼 수 있습니다.

     

    @media all and (min-width:768px) and (max-width:1080px) {}
    /* '모든 디바이스 & 최소 너비가 768 이상 & 최대 너비가 1080 이하'라는 조건을 모두 만족할 때 */
    @media (min-height: 680px), screen and (orientation: portrait) { ... }
    /* '최소 높이가 680 이상'이거나 '세로 모드의 스크린 기기' 중 하나를 만족하는 경우에 적용*/

     

     

    미디어 쿼리의 스펙 문서를 살펴보면 이외에도 정말 많은 특성을 만나볼 수 있어요.

    예를 들면, 미디어 쿼리 레벨 4 스펙에서 추가된 hoverpointer등이 있습니다.

    @media (hover: hover) and (pointer: fine) { }를 써서 마우스나 트랙패드가 있는 기기를 조건으로 하는 구문을 만들 수 있습니다.

    하지만 최신 스펙에는 언제나 지원 브라우저가 제한되어 있다는 한계가 존재하죠 😕

     

    실무에서는 특성에 주로 width 값을 사용합니다. 사실 이것만 써도 거의 커버할 수 있어요!

     

     

     

    모바일과 데스크탑, 누가 먼저?

    미디어 쿼리의 분기는 대부분 width 값을 사용하는데, 이때 min을 쓸지 max를 쓸지 고민될 때가 있죠.

    방법은 여러가지가 있겠지만, 저의 경우에는 아래와 같이 사용하고 있습니다.

    - 모바일 퍼스트: min

    - 데스크탑 퍼스트 : max

     

     

    모바일 퍼스트는 말 그대로 모바일 우선입니다.

    따라서 모바일에 대한 스타일이 우선 적용되도록, 분기점(break point)가 낮은 순대로 작성해 나갑니다.

     

    /* Mobile First */
    
    .title { font-size: 12px; }
    
    @media (min-width: 640px) {
      .title { font-size: 14px; }
    }
    
    @media (min-width: 768px) {
      .title { font-size: 16px; }
    }
    
    @media (min-width: 1024px) {
      .title { font-size: 18px; }
    }

     

    위와 같이 작성한 경우, iPhoneX 사이즈(375px)의 기기에서는 폰트 사이즈가 어떻게 될까요?

    어떠한 미디어 쿼리 구문도 만족하지 못하므로 12px이 적용됩니다.

     

    한편, 800px 너비의 뷰포트에서 본다면요?

    우선 12px이 적용되지만, 최소 너비 640이란 조건을 만족하므로 스타일을 덮어써서 14px이 됩니다.

    또, 최소 너비 768이라는 조건도 만족하므로 스타일을 다시 덮어써서 16px이 됩니다.

    하지만 최소 너비 1024는 만족하지 못한 상태이므로 16px이 그대로 유지돼요.

     

     

     

     

    반면에 데스크탑 퍼스트는 분기점(break point)를 높은 순서부터 작성합니다.

    이 경우에는 뷰포트 값이 작아질수록 스타일을 덮어쓰는 방식으로 적용이 되겠죠!

     

    /* Desktop First */
    
    .title { font-size: 18px; }
    
    @media (max-width: 1023px) {
      .title { font-size: 16px; }
    }
    
    @media (max-width: 767px) {
      .title { font-size: 14px; }
    }
    
    @media (max-width: 639px) {
      .title { font-size: 12px; }
    }

     

    보여지는 결과물은 똑같기에 크게 상관은 없으며, 작성 방식에만 차이가 있어요.

     

     

     

    도와줘요 믹스인

    미디어쿼리를 멋지게 쓰는 건 좋지만, 구문과 분기점을 항상 외우고 다닐 순 없죠.

    그럴 땐 믹스인을 만들어 쓰면 좋습니다. 유지보수도 매우 편해져요!

     

    미디어 쿼리를 관리하기 위한 scss 파일을 만들어 주세요.

    분기점은 변수로 만들고, @content로 내용이 비워져있는 믹스인을 작성합니다.

     

    // Break Point
    $tablet: 768px;
    $laptop: 1020px;
    $desktop: 1400px;
    
    
    // Mixins
    @mixin tablet {
      @media (min-width: #{$tablet}) {
        @content;
      }
    }
    
    @mixin laptop {
      @media (min-width: #{$laptop}) {
        @content;
      }
    }
    
    
    @mixin desktop {
      @media (min-width: #{$desktop}) {
        @content;
      }
    }

     

    이제 @include로 불러와 사용합니다.

     

    // SCSS
    
    .logo {
      width: 20px;
    
      @include desktop {
        width: 40px;
      }
    }
    /* 컴파일된 CSS */
    
    .logo {
      width: 20px;
    }
    
    @media (min-width: 1400px) {
      .logo {
        width: 40px;
      }
    }

     

    짜잔- 아주 잘 나오네요!

     

     

     

    중첩 미디어 쿼리

    믹스인을 사용할 때 아래처럼 미디어 쿼리를 별도로 작성할 수도 있지만,

     

    /* 미디어 쿼리를 별도로 작성하는 경우 */
    
    .title {
      font-size: 12px;
    }
    
    .logo {
      width: 20px;
    }
    
    
    @include laptop {
      .title {
        font-size: 14px;
      }
      
      .logo {
        width: 30px;
      }
    }

     

    요소 바로 밑에 작성하는 방법도 있습니다. 바로 아래처럼요.

     

    /* 미디어 쿼리를 바로 아래에 작성하는 경우 */
    
    .title {
      font-size: 12px;
      
      @include laptop {
        font-size: 14px;
      }
    }
    
    
    .logo {
      width: 20px;
      
      @include laptop {
        width: 30px;
      }
    }
    

     

    위의 방식대로 작성하는 경우, 반응형 디자인을 바로 확인할 수 있어 직관적이라는 장점이 있습니다.

    특히 분기점이 많아질수록 보기가 편하며 유지보수에도 좋습니다.

     

    한 가지 아쉬운 점이 있다면, 컴파일한 CSS에서 미디어 쿼리 구문이 계속 중복된다는 점입니다.

     

    /* 컴파일 후 중복되는 미디어 쿼리 구문 */
    
    .title {
      font-size: 12px;
    }
    @media (min-width: 1020px) {
      .title {
        font-size: 14px;
      }
    }
    @media (min-width: 1600px) {
      .title {
        font-size: 16px;
      }
    }
    
    .logo {
      width: 20px;
    }
    @media (min-width: 1020px) {
      .logo {
        width: 30px;
      }
    }
    @media (min-width: 1600px) {
      .logo {
        width: 40px;
      }
    }

     

    성능에 큰 영향을 줄 만한 요소는 아닌 것 같지만, 이왕이면 짧은 CSS가 좋겠죠.

    이럴 땐 중복되는 미디어 쿼리구문을 합쳐주는 도구를 쓰면 아래처럼 깔끔해져요 (❁´◡`❁)

    gulp를 사용 중이시라면 'gulp-group-css-media-queries'를 설치해서 사용할 수 있습니다. (21.02.24 / 댓글 감사합니다!)

    var gulp = require('gulp');
    var gcmq = require('gulp-group-css-media-queries');
     
    gulp.task('default', function () {
        gulp.src('src/style.css')
            .pipe(gcmq())
            .pipe(gulp.dest('dist'));
    });

     

     

    /* 중복되는 미디어 쿼리 구문 제거 */
    
    .title {
      font-size: 12px;
    }
    
    .logo {
      width: 20px;
    }
    
    @media (min-width: 1020px) {
      .title {
        font-size: 14px;
      }
      .logo {
        width: 30px;
      }
    }
    
    @media (min-width: 1600px) {
      .title {
        font-size: 16px;
      }
      .logo {
        width: 40px;
      }
    }
    

     


     

     

    지금까지 뷰포트 메타 태그와 미디어 쿼리에 대해 살펴보았습니다. 사실 이 두 개만 활용해도 멋진 반응형을 만들 수 있습니다.

    하지만 시간이 없는데 빠르게 반응형을 만들어야 하거나, 분기점을 하나하나 고려하기가 힘들 때가 있죠.

    그럴 때 유용하게 써먹을 수 있는 vw이나 rem 같은 CSS 속성들이 있습니다.

     

    CSS 속성을 활용하는 반응형 제작법은 시리즈의 다음 글에서 다룰 예정입니다.

    다음 글에서 만나요- 스테이 튠! (??) 📺🕹

     

     

    이 시리즈의 다음 글: 반응형 웹 뚝딱 만들기 (2)

     

     


     

     

    참고 글

     

    CSS media query에 대하여 :: 널리

    미디어 쿼리의 only 키워드 :: wystan's tales

    The complete guide to CSS media queries :: polypane

     

    728x90

    댓글