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

미디어 쿼리는 좋지만…

미디어 쿼리는 멋진 기능입니다. 스타일시트를 약간만 조정하여 다양한 크기의 기기에서 사용자에게 더 나은 환경을 제공하려는 웹사이트 개발자에게는 신의 선물과도 같습니다. 미디어 쿼리를 사용하면 기본적으로 화면 크기에 따라 사이트의 CSS를 맞춤설정할 수 있습니다. 이 도움말을 자세히 살펴보기 전에 반응형 디자인에 관해 자세히 알아보고 mediaqueri.es에서 미디어 쿼리 사용의 훌륭한 예시를 확인하세요.

브래드 프로스트가 이전 도움말에서 지적했듯이, 디자인 변경은 모바일 웹용으로 빌드할 때 고려해야 할 여러 사항 중 하나일 뿐입니다. 모바일 웹사이트를 빌드할 때 미디어 쿼리로 레이아웃을 맞춤설정하는 작업만 수행하는 경우 다음과 같은 상황이 발생합니다.

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

웹앱에는 미디어 쿼리 외의 항목이 필요합니다.

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

빌드하는 UI의 복잡성이 증가하고 싱글페이지 웹앱을 사용하게 되면 기기 유형별로 UI를 맞춤설정하는 작업을 더 많이 해야 합니다. 이 도움말에서는 최소한의 노력으로 이러한 맞춤설정을 실행하는 방법을 설명합니다. 일반적인 접근 방식은 방문자의 기기를 올바른 기기 클래스로 분류하고 버전 간에 코드 재사용을 극대화하면서 해당 기기에 적절한 버전을 제공하는 것입니다.

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

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

다양한 기기
다양한 기기 (source).

우수한 사용자 환경을 만들려면 사용자가 누구이고 어떤 기기를 사용하고 있는지 알아야 합니다. 마우스와 키보드를 사용하는 데스크톱 사용자를 위한 사용자 인터페이스를 빌드하여 스마트폰 사용자에게 제공하면 다른 화면 크기와 다른 입력 모달에 맞게 설계된 인터페이스가 사용자에게 불편을 줄 수 있습니다.

접근 방식에는 두 가지 극단이 있습니다.

  1. 모든 기기에서 작동하는 하나의 버전을 빌드합니다. 기기마다 설계 고려사항이 다르므로 UX가 저하됩니다.

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

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

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

가능한 해결 방법

기기를 카테고리로 분류하고 각 카테고리에 가장 적합한 환경을 설계하는 것이 좋습니다. 선택하는 카테고리는 제품과 타겟 사용자에 따라 다릅니다. 다음은 오늘날 존재하는 널리 사용되는 웹 지원 기기를 멋지게 분류한 샘플입니다.

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

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

폼 팩터별 웹 앱의 예

다양한 폼 팩터에 완전히 다른 버전을 제공하는 웹 속성의 예는 많습니다. Google 검색과 Facebook에서 이러한 작업을 실행합니다. 이때 고려해야 할 사항에는 성능 (애셋 가져오기, 페이지 렌더링)과 더 일반적인 사용자 환경이 모두 포함됩니다.

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

휴대전화 및 태블릿을 위한 상당한 UI 맞춤설정
휴대전화 및 태블릿의 상당한 UI 맞춤설정

접근 방식 1: 서버 측 감지

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

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

모바일 브라우저 감지 프로젝트와 같이 더 간단하고 무료인 대안도 있습니다. 물론 단점은 기기 감지가 덜 포괄적일 수밖에 없다는 점입니다. 또한 휴대기기와 휴대기기가 아닌 기기만 구분하며 임시 조정 세트를 통해서만 제한적인 태블릿 지원을 제공합니다.

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

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

작은 터치 기기와 큰 터치 기기를 구분하기 위해 어딘가에 기준을 두어야 합니다. 5인치 Galaxy Note와 같은 특이 사례는 어떻게 하나요? 다음 그래픽은 여러 인기 Android 및 iOS 기기를 겹쳐서 보여주며 해당 기기의 화면 해상도도 표시되어 있습니다. 별표는 기기가 이중 밀도로 제공되거나 제공될 수 있음을 나타냅니다. 픽셀 밀도가 두 배가 될 수 있지만 CSS는 여전히 동일한 크기를 보고합니다.

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

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

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

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

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

임곗값을 650px로 설정하면 iPhone, Galaxy Nexus는 smalltouch로, iPad, Galaxy Tab은 '태블릿'으로 분류됩니다. 이 경우 양성적 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는 특별한 서버 측 구성 없이 시맨틱 미디어 쿼리 기반 기기 감지를 시작하는 지점으로, 사용자 에이전트 문자열 파싱에 필요한 시간과 노력을 절약할 수 있습니다.

<head> 상단에 제공하려는 사이트 버전을 나타내는 검색엔진 친화적인 마크업 (link rel=alternate)을 제공하는 것입니다.

<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에 대해 잘 모르는 경우 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 감지로 전환합니다.