교차 기기 웹앱을 빌드하는 비응답 방식

미디어 쿼리는 유용하지만…

미디어 쿼리는 다양한 크기의 기기에서 사용자에게 더 나은 환경을 제공하기 위해 스타일시트를 약간 수정하려는 웹사이트 개발자에게 유용한 기능입니다. 미디어 쿼리를 사용하면 화면 크기에 따라 사이트의 CSS를 맞춤설정할 수 있습니다. 이 도움말을 살펴보기 전에 반응형 디자인에 대해 자세히 알아보고 mediaqueri.es에서 미디어 쿼리 사용의 좋은 예를 확인하세요.

이전 도움말에서 브래드 프로스트가 지적한 것처럼 모바일 웹을 빌드할 때 고려해야 할 사항은 디자인 변경뿐만이 아닙니다. 모바일 웹사이트를 빌드할 때 미디어 쿼리로 레이아웃을 맞춤설정하는 것만 수행하는 경우 다음과 같은 상황이 발생합니다.

  • 모든 기기가 동일한 JavaScript, CSS, 애셋 (이미지, 동영상)을 가져오므로 로드 시간이 필요 이상으로 길어집니다.
  • 모든 기기가 동일한 초기 DOM을 가져오므로 개발자가 지나치게 복잡한 CSS를 작성해야 할 수 있습니다.
  • 각 기기에 맞게 맞춤 상호작용을 지정하는 유연성이 부족합니다.

웹앱에는 미디어 쿼리 이상의 기능이 필요합니다.

오해하지 마세요. 미디어 쿼리를 통한 반응형 디자인을 싫어하지 않으며, 이 디자인이 세상에서 차지하는 자리가 있다고 생각합니다. 또한 위에 언급된 문제 중 일부는 반응형 이미지, 동적 스크립트 로드 등의 접근 방식으로 해결할 수 있습니다. 하지만 어느 시점에는 너무 많은 증분 조정이 필요할 수 있으며, 다른 버전을 제공하는 것이 더 나을 수 있습니다.

빌드하는 UI의 복잡성이 증가하고 단일 페이지 웹 앱으로 기울어짐에 따라 각 기기 유형에 맞게 UI를 맞춤설정하는 작업이 더 필요해집니다. 이 도움말에서는 최소한의 노력으로 이러한 맞춤설정을 하는 방법을 알아봅니다. 일반적인 접근 방식은 방문자의 기기를 올바른 기기 클래스로 분류하고 해당 기기에 적절한 버전을 제공하면서 버전 간 코드 재사용을 극대화하는 것입니다.

어떤 기기 클래스를 타겟팅하고 있나요?

인터넷에 연결된 기기가 많이 있으며 거의 모든 기기에 브라우저가 있습니다. 문제는 다양성에 있습니다. Mac 노트북, Windows 워크스테이션, iPhone, iPad, 터치 입력이 있는 Android 휴대전화, 스크롤 휠, 키보드, 음성 입력, 압력 감도가 있는 기기, 스마트 시계, 토스터, 냉장고 등 다양한 기기가 있습니다. 이러한 기기 중에는 어디에나 있는 기기도 있고 매우 희귀한 기기도 있습니다.

다양한 기기
다양한 기기 (소스)

좋은 사용자 환경을 만들려면 사용자가 누구인지, 어떤 기기를 사용하는지 알아야 합니다. 마우스와 키보드를 사용하는 데스크톱 사용자를 위해 사용자 인터페이스를 빌드하여 스마트폰 사용자에게 제공하면 다른 화면 크기와 다른 입력 모달리티를 위해 설계되었기 때문에 인터페이스가 불편해집니다.

접근 방식 스펙트럼에는 두 가지 극단적인 끝이 있습니다.

  1. 모든 기기에서 작동하는 하나의 버전을 빌드합니다. 기기마다 디자인 고려사항이 다르기 때문에 UX가 저하됩니다.

  2. 지원하려는 각 기기에 맞는 버전을 빌드합니다. 애플리케이션의 버전을 너무 많이 빌드하므로 시간이 오래 걸립니다. 또한 다음 새 스마트폰이 출시되면(대략 매주 출시됨) 또 다른 버전을 만들어야 합니다.

여기에는 근본적인 트레이드오프가 있습니다. 기기 카테고리가 많을수록 더 나은 사용자 환경을 제공할 수 있지만 설계, 구현, 유지관리하는 데 더 많은 작업이 필요합니다.

성능상의 이유로 또는 서로 다른 기기 클래스에 제공하려는 버전이 크게 다른 경우 기기 클래스별로 별도의 버전을 만드는 것이 좋습니다. 그렇지 않은 경우 반응형 웹 디자인이 적절한 접근 방식입니다.

가능한 해결 방법

여기서 타협안이 있습니다. 기기를 카테고리로 분류하고 각 카테고리에 가장 적합한 환경을 설계하는 것입니다. 선택하는 카테고리는 제품과 타겟 사용자에 따라 다릅니다. 다음은 오늘날 존재하는 인기 있는 웹 지원 기기를 적절히 포괄하는 샘플 분류입니다.

  1. 작은 화면 + 터치 (주로 휴대전화)
  2. 대형 화면 + 터치 (주로 태블릿)
  3. 대형 화면 + 키보드/마우스 (주로 데스크톱/노트북)

이는 가능한 여러 분류 중 하나일 뿐이지만, 작성 시점에서 매우 합리적인 분류입니다. 위 목록에는 터치 스크린이 없는 모바일 기기 (예: 피처폰, 일부 전용 전자책 리더)가 누락되어 있습니다. 하지만 대부분의 기기에는 키보드 탐색 또는 화면 리더 소프트웨어가 설치되어 있으므로 접근성을 고려하여 사이트를 빌드하면 문제없이 작동합니다.

폼 팩터별 웹 앱의 예

다양한 폼 팩터에 완전히 다른 버전을 제공하는 웹 속성의 예가 많이 있습니다. Google 검색과 Facebook도 마찬가지입니다. 여기에는 성능 (애셋 가져오기, 페이지 렌더링)과 일반적인 사용자 환경이 모두 포함됩니다.

네이티브 앱의 세계에서 많은 개발자는 기기 클래스에 맞게 환경을 맞춤설정합니다. 예를 들어 iPad용 Flipboard는 iPhone의 Flipboard와 비교해 UI가 매우 다릅니다. 태블릿 버전은 양손 사용과 가로 뒤집기에 최적화되어 있으며 휴대전화 버전은 한 손 상호작용과 세로 뒤집기를 위해 설계되었습니다. 아래에 소개된 Things (할 일 목록) 및 Showyou (소셜 동영상)와 같이 다른 많은 iOS 애플리케이션에서도 휴대전화 버전과 태블릿 버전을 크게 다르게 제공합니다.

휴대전화 및 태블릿의 UI를 크게 맞춤설정합니다.
휴대전화 및 태블릿의 UI를 크게 맞춤설정합니다.

접근 방식 1: 서버 측 감지

서버에서는 처리 중인 기기에 대해 훨씬 제한적인 이해를 갖습니다. 가장 유용한 단서는 사용자 에이전트 문자열일 것입니다. 이 문자열은 모든 요청의 User-Agent 헤더를 통해 제공됩니다. 따라서 여기에서도 동일한 UA 스니핑 접근 방식이 작동합니다. 실제로 DeviceAtlas 및 WURFL 프로젝트는 이미 이 작업을 실행하고 있으며 기기에 관한 추가 정보를 많이 제공합니다.

하지만 이러한 방법에는 각각 고유한 문제가 있습니다. WURFL은 매우 커서 20MB의 XML이 포함되어 있으므로 각 요청에 대해 상당한 서버 측 오버헤드가 발생할 수 있습니다. 성능상의 이유로 XML을 분할하는 프로젝트가 있습니다. DeviceAtlas는 오픈소스가 아니며 사용하려면 유료 라이선스가 필요합니다.

Detect Mobile Browsers 프로젝트와 같은 더 간단한 무료 대안도 있습니다. 물론 단점은 기기 감지가 필연적으로 덜 포괄적이라는 것입니다. 또한 모바일 기기와 비모바일 기기만 구분하며 임시 수정사항 집합을 통해서만 제한적인 태블릿 지원을 제공합니다.

접근 방식 2: 클라이언트 측 감지

기능 감지를 사용하면 사용자의 브라우저와 기기에 관해 많은 것을 알 수 있습니다. 확인해야 할 주요 사항은 기기에 터치 기능이 있는지, 화면이 큰지 작은지입니다.

작은 터치 기기와 큰 터치 기기를 구분하기 위해 어딘가에 선을 그어야 합니다. 5인치 갤럭시 노트와 같은 특이 사례는 어떻게 되나요? 다음 그래픽은 인기 있는 Android 및 iOS 기기를 오버레이한 것입니다 (해당 화면 해상도 포함). 별표는 기기가 밀도가 두 배로 제공되거나 제공될 수 있음을 나타냅니다. 픽셀 밀도가 두 배가 될 수 있지만 CSS는 동일한 크기를 보고합니다.

CSS의 픽셀에 관한 간단한 참고 사항: 모바일 웹의 CSS 픽셀은 화면 픽셀과 같지 않습니다. iOS Retina 기기에서는 픽셀 밀도를 두 배로 늘리는 방식을 도입했습니다 (예: iPhone 3GS와 4, iPad 2와 3). 레티나 모바일 Safari UA는 웹이 깨지는 것을 방지하기 위해 동일한 device-width를 계속 보고합니다. 다른 기기 (예: Android)은 더 높은 해상도 디스플레이를 사용하지만 동일한 기기 너비 트릭을 사용합니다.

기기 해상도 (픽셀)입니다.
기기 해상도 (픽셀)입니다.

하지만 세로 모드와 가로 모드를 모두 고려해야 한다는 점이 이 결정을 복잡하게 만듭니다. 기기를 다시 방향을 지정할 때마다 페이지를 새로고침하거나 추가 스크립트를 로드하고 싶지는 않지만 페이지를 다르게 렌더링하고 싶을 수도 있습니다.

다음 다이어그램에서 정사각형은 세로 및 가로 윤곽선을 오버레이하고 정사각형을 완성한 결과로 각 기기의 최대 크기를 나타냅니다.

세로 + 가로 해상도 (픽셀)
세로 + 가로 해상도 (픽셀)

기준점을 650px으로 설정하면 iPhone, Galaxy Nexus는 smalltouch로 분류되고 iPad, Galaxy Tab은 'tablet'으로 분류됩니다. 양성적인 Galaxy Note는 이 경우 '휴대전화'로 분류되어 휴대전화 레이아웃이 적용됩니다.

따라서 적절한 전략은 다음과 같습니다.

if (hasTouch) {
  if (isSmall) {
    device = PHONE;
  } else {
    device = TABLET;
  }
} else {
  device = DESKTOP;
}

기능 감지 접근 방식의 최소 샘플을 확인하세요.

여기서 대안은 UA 스니핑을 사용하여 기기 유형을 감지하는 것입니다. 기본적으로 휴리스틱 세트를 만들고 이를 사용자의 navigator.userAgent와 일치시킵니다. 의사 코드는 다음과 같습니다.

var ua = navigator.userAgent;
for (var re in RULES) {
  if (ua.match(re)) {
    device = RULES[re];
    return;
  }
}

UA 감지 접근 방식의 실제 사례를 확인하세요.

클라이언트 측 로드에 관한 참고사항

서버에서 UA 감지를 실행하는 경우 새 요청을 받을 때 제공할 CSS, JavaScript, DOM을 결정할 수 있습니다. 하지만 클라이언트 측 감지를 수행하는 경우 상황이 더 복잡해집니다. 다음과 같은 몇 가지 옵션이 있습니다.

  1. 이 기기 유형의 버전을 포함하는 기기 유형별 URL로 리디렉션합니다.
  2. 기기 유형별 애셋을 동적으로 로드합니다.

첫 번째 접근 방식은 간단하며 window.location.href = '/tablet'과 같은 리디렉션이 필요합니다. 하지만 이제 위치에 이 기기 유형 정보가 추가되므로 History API를 사용하여 URL을 정리하는 것이 좋습니다. 하지만 이 접근 방식에는 리디렉션이 포함되어 있어 특히 휴대기기에서 속도가 느릴 수 있습니다.

두 번째 방법은 구현하기가 훨씬 더 복잡합니다. CSS와 JS를 동적으로 로드하는 메커니즘이 필요하며 브라우저에 따라 <meta viewport>를 맞춤설정하는 등의 작업을 하지 못할 수도 있습니다. 또한 리디렉션이 없으므로 제공된 원래 HTML이 그대로 유지됩니다. 물론 JavaScript로 조작할 수 있지만 애플리케이션에 따라 속도가 느리거나 비효율적일 수 있습니다.

클라이언트 또는 서버 결정

다음은 접근 방식 간의 장단점입니다.

Pro 클라이언트:

  • UA가 아닌 화면 크기/기능을 기반으로 하므로 향후에도 더 유용합니다.
  • UA 목록을 지속적으로 업데이트할 필요가 없습니다.

Pro 서버:

  • 어떤 기기에 어떤 버전을 제공할지 완전히 제어할 수 있습니다.
  • 성능 향상: 클라이언트 리디렉션이나 동적 로드가 필요하지 않습니다.

개인적으로는 device.js와 클라이언트 측 감지로 시작하는 것을 선호합니다. 애플리케이션이 발전함에 따라 클라이언트 측 리디렉션이 성능에 심각한 단점이라고 판단되면 device.js 스크립트를 쉽게 삭제하고 서버에서 UA 감지를 구현할 수 있습니다.

device.js 소개

Device.js는 특수한 서버 측 구성 없이 시맨틱 미디어 쿼리 기반 기기 감지를 실행하는 시작점으로, 사용자 에이전트 문자열 파싱에 필요한 시간과 노력을 절약해 줍니다.

사이트의 어떤 버전을 제공할지 나타내는 검색엔진 친화적인 마크업 (link rel=alternate)을 <head> 상단에 제공하면 됩니다.

<link rel="alternate" href="http://foo.com" id="desktop"
    media="only screen and (touch-enabled: 0)">

그런 다음 서버 측 UA 감지를 수행하고 버전 리디렉션을 직접 처리하거나 device.js 스크립트를 사용하여 기능 기반 클라이언트 측 리디렉션을 실행할 수 있습니다.

자세한 내용은 device.js 프로젝트 페이지와 클라이언트 측 리디렉션에 device.js를 사용하는 가짜 애플리케이션을 참고하세요.

권장사항: 폼 팩터별 뷰가 있는 MVC

지금까지의 내용을 보면 기기 유형별로 완전히 별도의 앱 3개를 빌드해야 한다고 생각할 수 있습니다. 아니요 코드 공유가 핵심입니다.

Backbone, Ember 등 MVC와 유사한 프레임워크를 사용하고 계실 것입니다. 이러한 프레임워크를 사용해 왔다면 관심사 분리 원칙, 특히 UI (보기 레이어)가 로직 (모델 레이어)에서 분리되어야 한다는 원칙에 익숙하실 것입니다. 처음 사용하는 경우 MVC에 관한 리소스JavaScript의 MVC를 참고하여 시작하세요.

크로스 디바이스 스토리는 기존 MVC 프레임워크에 깔끔하게 맞습니다. 뷰를 별도의 파일로 쉽게 이동하여 기기 유형별 맞춤 뷰를 만들 수 있습니다. 그러면 뷰 레이어를 제외한 모든 기기에 동일한 코드를 제공할 수 있습니다.

교차 기기 MVC
교차 기기 MVC.

프로젝트의 구조는 다음과 같을 수 있습니다. 물론 애플리케이션에 따라 가장 적합한 구조를 선택할 수 있습니다.

models/ (공유 모델) item.js item-collection.js

controllers/ (공유 컨트롤러) item-controller.js

versions/ (기기별 항목) tablet/ desktop/ phone/ (휴대전화별 코드) style.css index.html views/ item.js item-list.js

이러한 구조를 사용하면 각 기기에 맞춤 HTML, CSS, JavaScript가 있으므로 각 버전에서 로드하는 애셋을 완전히 제어할 수 있습니다. 이는 매우 강력하며 적응형 이미지와 같은 트릭에 의존하지 않고도 크로스 디바이스 웹을 개발하는 가장 간결하고 성능이 뛰어난 방법을 제공합니다.

선호하는 빌드 도구를 실행하면 JavaScript와 CSS가 모두 하나의 파일로 연결되고 최소화되어 로드 속도가 빨라집니다. 프로덕션 HTML은 다음과 같습니다 (휴대전화의 경우 device.js 사용).

<!doctype html>
<head>
  <title>Mobile Web Rocks! (Phone Edition)</title>

  <!-- Every version of your webapp should include a list of all
        versions. -->
  <link rel="alternate" href="http://foo.com" id="desktop"
      media="only screen and (touch-enabled: 0)">
  <link rel="alternate" href="http://m.foo.com" id="phone"
      media="only screen and (max-device-width: 650px)">
  <link rel="alternate" href="http://tablet.foo.com" id="tablet"
      media="only screen and (min-device-width: 650px)">

  <!-- Viewport is very important, since it affects results of media
        query matching. -->
  <meta name="viewport" content="width=device-width">

  <!-- Include device.js in each version for redirection. -->
  <script src="device.js"></script>

  <link rel="style" href="phone.min.css">
</head>
<body>
  <script src="phone.min.js"></script>
</body>

(touch-enabled: 0) 미디어 쿼리는 비표준 (moz 공급업체 접두사 뒤에 Firefox에서만 구현됨)이지만 device.js에 의해 올바르게 처리됩니다 (Modernizr.touch 덕분).

버전 재정의

기기 감지가 잘못되는 경우가 있으며, 사용자가 휴대전화에서 태블릿 레이아웃을 선호하는 경우도 있습니다 (예: Galaxy Note 사용). 따라서 사용자가 수동으로 재정의하려는 경우 사용할 사이트 버전을 선택할 수 있도록 하는 것이 중요합니다.

일반적인 방법은 모바일 버전에서 데스크톱 버전으로 연결되는 링크를 제공하는 것입니다. 이 기능은 쉽게 구현할 수 있지만 device.js는 device GET 매개변수를 사용하여 이 기능을 지원합니다.

마무리

요약하자면, 반응형 디자인의 세계에 딱 맞지 않는 교차 기기 단일 페이지 UI를 빌드할 때는 다음을 실행하세요.

  1. 지원할 기기 클래스 집합과 기기를 클래스로 분류할 기준을 선택합니다.
  2. 관심사 분리가 확실한 MVC 앱을 빌드하여 뷰를 나머지 코드베이스에서 분리합니다.
  3. device.js를 사용하여 클라이언트 측 기기 클래스 감지를 실행합니다.
  4. 준비가 되면 기기 클래스별로 스크립트와 스타일시트를 각각 하나로 패키징합니다.
  5. 클라이언트 측 리디렉션 성능이 문제인 경우 device.js를 포기하고 서버 측 UA 감지로 전환하세요.