본문 바로가기

웹 프론트엔드

[번역] 듀얼 스크린과 폴더블 디바이스를 위한 웹 API 소개

이 글은 Microsoft의 Introducing Web APIs for Dual Screen and Foldable Devices 포스트 내용을 번역, 요약하였습니다.


9월 10일 출시된 Surface Duo를 포함하여, 요즘엔 다양하고 새로운 듀얼 스크린, 폴더블 디바이스가 시장에 나오고있습니다. 이에 따라서 사이트를 이러한 규격을 어떻게 수용할 지 생각해볼 좋은 시기입니다.

그래서 마이크로소프트는 웹 개발자들이 브라우저 창이 여러 디스플레이 영역에 걸쳐있는 경우에 콘텐츠를 효과적으로 배치하고, 해당 디바이스에 자연스럽게 반응형 웹 사이트를 만들 수 있도록 하는 두 가지 실험 기능을 발표했습니다.

  1. CSS 스크린 확장 미디어 기능(screen-spaning media feature)과 접힌 형상을 설명하는 환경 변수들.
  2. JavaScript 윈도우 세그먼트 나열 API(window segments enumeration API). Canvas2d 와 WebGL과 같은 비 DOM 대상으로 작업 할 때 유용한 API.

폴더블 디바이스의 종류

일반적으로 접을 수 있는 장치에는 두 가지 형태가 있습니다.

  • 폴더블 듀얼 스크린 디바이스: 물리적 스크린이 2개. 논리적 디스플레이 영역이 2개. 접히는 영역이 1개.
  • 폴더블 싱글 스크린 디바이스: 물리적 스크린이 1개. 논리적 디스플레이 영역이 2개. 접히는 영역이 1개.

두 장치 모두 회전, 접기가 가능하여 여러 자세를 가지고 있는 디바이스입니다.

이런 기기에서 애플리케이션은 한쪽에만 있거나, 두 디스플레이 영역에 걸쳐 있을 수 있습니다. 걸쳐진 상태에 대응하려는 웹 사이트는 콘텐츠를 논리적으로 잘 나누면서도 시맨틱하게 개발해야 합니다.

기존의 연속된 스크린에서 듀얼 스크린/폴더블로 전환

기존 웹 사이트도 기본적으로 계속 작동합니다. 하지만 폴더블이라는 특성을 사이트가 인식한다면 사용자 경험을 크게 향상시킬 수 있습니다.

새로 만들어진 브라우저 기능이 어떻게 동작하는지 보여주기 위해, 이메일 클라이언트 레이아웃 개선 예제를 보여드리겠습니다.

영역이 넓을 때, 받은 편지함의 리스트뷰(왼쪽)와 이메일 내용(오른쪽)을 좌우로 나란히 표시하는 것은 일반적인 패턴입니다.

만약 듀얼 스크린 디바이스에서 브라우저 창이 두 디스플레이 영역에 걸쳐있다면, 전체 뷰포트 너비는 기존의 가로모드 태블릿과 비슷할 겁니다.

별도의 수정이 없는 이메일 클라이언트는 평소와 동일하게 작동합니다. 하지만 받은 편지함과 이메일 내용을 접힌 부분에 맞추어서, 받은 편지함과 이메일 내용을 각 디스플레이 영역 테두리 내에 두면, 사용자 경험이 크게 개선될 수 있을 것입니다. 이렇게 하면 힌지가 있는 디바이스에는 두 컨텐츠 영역 모두 힌지에 가려지지 않고, 디스플레이가 유연한 디바이스의 경우에는 디스플레이의 접히는 부분에 렌더링 되지 않습니다.

이러한 레이아웃을 만들기 위해, 웹 개발자가 폴더블 디바이스를 다른 반응형 웹 디자인 처럼 취급할 수 있도록 새로운 스크린 확장 미디어 기능과 미리 정의된 환경 변수 세트를 도입했습니다. 이제 개발자는 특정 하드웨어 패러미터에 의존하지 않고 모든 디바이스 종류에 적합한 레이아웃을 만들 수 있습니다. 이러한 점은 각각 새 디바이스 타입에 대해 중복 작업이 필요하지 않으므로 확장성이 향상됩니다.

디스플레이 영역 감지

CSS의 screen-spanning 미디어 기능은 루트 뷰포트가 여러 인접한 디스플레이에 걸쳐있는지를 테스트하는데 도움을 줍니다. 그리고 인접한 디스플레이 영역에 대한 세부 정보를 제공합니다.(예: 위 아래로 쌓여있는지 혹은 좌우로 나란히 있는지)

문법

screen-spanning 미디어 기능은 디바이스가 가진 접힌 부분(혹은 힌지)의 수와 자세를 설명하는 값으로 지정됩니다. 만약 폴더블 디바이스가 아니라면 값이 none 이 됩니다. 폴더블인 경우, 다음 두 값 중 하나를 가질 수 있습니다:

  • Single-fold-vertical: 한 번 접을 수 있는 상태(디스플레이 영역이 두 개)을 가진 장치이며 접힌 부분의 자세는 수직(vertical).
  • Single-fold-horizontal: 한 번 접을 수 있는 상태(디스플레이 영역이 두 개)을 가진 장치이며 접힌 부분의 자세는 수평(horizontal).

디스플레이 영역의 형상 계산

screen-spanning 상태에 있을 때, 접힌 부분이 항상 정확히 뷰포트의 반을 나눈다고 가정하는 것은 위험합니다. 또, 어떤 창 관리자는 접힌 부분에 있는 웹 콘텐츠를 가리도록 할 수 있습니다. 각 디스플레이 영역의 크기를 계산하고, 가려지는 것을 피하려면 콘텐츠에 패딩을 얼마나 추가해야하는지 알 수 있도록, 미리 정의된 CSS 환경 변수를 4개 추가했습니다.

  • env(fold-top)
  • env(fold-left)
  • env(fold-width)
  • env(fold-height)

이 변수 값은 CSS 픽셀로 표현되고, 레이아웃 뷰포트에 상대적입니다.(즉 CSSOM 뷰에 정의한 클라이언트 좌표에 있다는 뜻). 확장된 상태 중 하나가 아니라고 확인되면, 이러한 환경 변수 값은 존재하지 않는 것처럼 처리되고 브라우저는 env()함수에 전달된 폴백값을 사용합니다.

듀얼 스크린 및 폴더블에 대한 경험을 향상시키도록 이메일 앱 개선

CSS screen-spaning 미디어 기능과 접힌 형상을 나타내는 환경 변수를 실제로 적용해서 이메일 클라이언트의 뷰를 개선해보도록 하겠습니다

@media screen and (min-width: 799px) {
    /* 태블릿보다 넓은 화면에만 적용되는 규칙 */
}

@media screen and (min-width: 799px) and (screen-spanning: single-fold-vertical) {
    /* main은 위의 그림에서 강조된 3개의 flex 아이템을 감싸는 엘리먼트 */
    main {
        display: flex;
        flex-direction: row;
    }
    .navigation {
        /* 
        ** flex direction은 row이므로, flex-basis는 flex 아이템의 너비와 같은 역할을 한다
        ** 디자인에 따르면, 폴더블/듀얼 스크린에서 원하는 너비는 60px 이다
        */
        flex-basis: 60px;

        flex-grow: 0;
        flex-shrink: 0;
    }

    .inbox {
        /* 
        ** 받은 편지함 의 너비는 첫번째 디스플레이 영역의 전체 너비를 사용합니다. 예:
        ** 받은 편지함 너비 = display-region-1-width - 60px (네비게이션 열 너비)
        */
        flex-basis: calc( env(fold-left) - 60px );

        /* 
        ** 일부 디바이스에는 가려지는 부분이 있으므로, 이 열 다음에 간격이나 여백을 추가할 필요가 있습니다
        ** env(fold-width) = 28 (서피스 듀오의 CSS 픽셀)
        ** env(fold-width) = 0 (컨텐츠를 가리지 않는 디바이스의 CSS-픽셀)
        */
        margin-inline-end: env(fold-width);

        flex-grow: 0;
        flex-shrink: 0;
    }

    .email-content {
        /* 
        ** 이메일의 내용이 있는 열이 나머지 공간을 채우도록 "grow"되어야 합니다
        ** 하지만 두 번째 디스플레이 영역의 너비를 계산하는 방법을 설명하기 위해
        ** 너비를 수동으로 설정하겠습니다
        */
        flex-basis: calc( 100vw - (env(fold-left) + env(fold-width)) );

        flex-grow: 0;
        flex-shrink: 0;
    }

}

JavaScript에서 윈도우 세그멘트 나열하기

Canvas2d나 WebGL 같은, 비 DOM을 대상으로 작업할 때, 새로운 윈도우 세그멘트 나열 API 를 사용하여 각 디스플레이 영역의 형상을 가져올 수 있습니다.

getWindowSegments() 는 각 디스플레이 영역의 형상와 위치을 나타내는 1개 이상의 DOMRect 배열을 리턴하는 window 객체의 메소드입니다.

리턴된 배열은 메서드가 호출 된 시점에 디스플레이 영역 상태에 대한 변경 불가능한(immutable) 스냅샷입니다. 사용자가 확장된 상태에서 확장 되지 않은 상태로 전환하거나, 디바이스를 회전하면, 이전에 검색한 윈도우 세그먼트가 유효하지 않습니다.

const segments = window.getWindowSegments();

// 케이스 1: 데스크탑, 기존 터치 스크린 디바이스, 접을 수 있는 디바이스가 확장되지 않음
console.log(segments.length) // 1

// 케이스 2: 듀얼스크린이고 접을 수 있음
console.log(segments.length) // 2

페이지는 window resize 이벤트나 orientationchange 이벤트를 수신해서 브라우저의 크기가 조정되었는지 혹은 디바이스가 회전되었는지 감지하고 업데이트된 디스플레이 영역을 검색해야 합니다.

let segments = window.getWindowSegments();

// 상태 1: 브라우저는 2개의 디스플레이에 걸쳐있고, 접힌 부분은 수직입니다.
console.log(segments.length); // 2

// 상태 2: 유저가 기기를 회전하기로 결정했고, 브라우저는 여전히 2개의 디스플레이에 걸쳐있지만 접힌 부분은 *수평*입니다
// 창 크기가 변하면 resize 와 orientationchange 이벤트 둘다 발생합니다
// 사용자가 확장된 상태에 들어가거나 나올때에도 resize 이벤트가 발생합니다
window.addEventListener('resize', () => {
    // 처음에 검색한 세그먼트는 최신 정보(접힌 부분이 수평일 때의 2개의 세그먼트)로 
    // 더 이상 업데이트 되지 않습니다. 
    segments = window.getWindowSegments();
});

접힌 부분의 자세가 수평인지 수직인지 아는 명확한 방법은 없지만, 반환된 DOMRects로 쉽게 계산할 수 있습니다.

function isSingleFoldHorizontal() {
    const segments = window.getWindowSegments();

    // 한번 접는 것은(single fold) 디바이스가 2개의 디스플레이 영역과 한번의 접는 영역을 가지고 있다는 뜻입니다
    if( segments.length !== 2 ) {
        return false;
    }

    // 수평 접기는 첫번째 세그먼트의 top이 두번째 세그먼트의 top보다 작음을 의미합니다
    if( segments[0].top < segments[1].top ) {
        return true;
    }

    // 이 지점에 도달하면 접힌 부분이 수직입니다
    return false;

접힌 부분의 너비에도 동일하게 적용되며, 웹 개발자는 getWindowSegments() 에서 제공하는 정보를 사용하여 창 관리자가 접힌 부분 뒤의 렌더링 된 정보를 가리는지, 접힌 부분의 너비가 0보다 큰지 아닌지 여부를 알 수 있습니다.

function foldWidth() {
    const segments = window.getWindowSegments();

    // 세그먼트가 1개면, 접힌 부분 숨기기(fold mask)를 적용할 수 없음. 0 반환
    // 세그먼트가 2개 이상이면, 아직 이러한 종류의 디바이스를 처리하지 않으므로, 0 반환.
    if( segments.length !== 2 ) {
        return 0;
    }

    // 접힌 부분이 수직
    // 디바이스가 다음과 같이 보여진다: [][]
    if( segments[0].top === segments[1].top ) {
        return segments[1].left - segments[0].right;
    }

    // 이 지점에 도달하면, 접힌 부분이 수평이다
    return segments[1].top - segments[0].bottom;
}

미래 대비하기

개발자들은 오늘 개발할 때 미래를 계획하는 경향이 있습니다. 따라서 앞으로 발생할 가능성이 있는 시나리오를 해결하기 위한 최소한의 리팩토링이 필요합니다.

예를 들면 2개의 접힌 부분과 3개의 화면이 있는 가상의 디바이스입니다.

CSS 와 달리 JavaScript는 윈도우 세그먼트 나열 API와 N 개의 디스플레이 영역을 직접적으로 매핑할 수 있는 배열, 루프, 조건의 개념이 있습니다. 위와 같은 가상의 디바이스에 브라우저가 3개의 디스플레이 영역에 모두 걸쳐있다고 가정합시다. 이 때 getWindowSegments() 메소드를 호출하면, 3개의 DOMRect 배열이 리턴되고 루프나 내장된 Array 메소드와 같이 간단하게 디스플레이 영역이 어떻게 설정됐는지에 대한 정보(예: 모든 화면의 너비가 같은지 등)에 대해 자세히 알 수 있습니다.

CSS은 screen-spanning 미디어 기능에 스크린 연결 방식을 나타내는 새로운 값을 추가할 계획입니다.

'지금' 폴더블 환경에 맞춰 웹 사이트 개선하기

CSS screen-spanning 미디어 기능 및 윈도우 세그먼트 나열 API는 실험용 플래그를 설정하면 사용할 수 있습니다. edge://flags/#enable-experimental-web-platform-features 에서 활성화 할 수 있습니다.

Microsoft Edge 86부터는 윈도우, 맥 데스크탑 플랫폼에서 Microsoft Edge DevTools를 사용하여 듀얼 스크린 & 폴더블 디바이스를 에뮬레이션 할 수 있습니다. 또는 새로운 Surface Duo 에뮬레이터 프리뷰 (버전 2020.806.1 이상)를 다운로드하고 설치하여 실험용 플랫폼 기능을 사용 설정한 후 내장된 Edge 브라우저를 사용하여 테스트하고 디버깅 할 수 있습니다.

두 기능은 현재 Origin Trial에서 제공되며, API에 대한 피드백을 제공하는 대신, 여기에서 토큰을 얻고 프로덕션 환경에서 새로운 기능을 안전하게 실험해 볼 수 있습니다. API 테스트에 관심 있다면 Origin Trial에 가입하세요!

앞으로의 길

이 기능들은 Chromium 프로젝트, Google, Intel, W3C의 CSSWG, Second-screen WG와 그 외 다수의 공동 작업으로 많은 반복 작업과 개선 작업을 거친 후에 사용할 수 있게 되었습니다.

마이크로소프트는 데스크탑 플랫폼용 CSS 및 JavaScript 기능 모두 크롬 오픈소스 프로젝트에 기여했습니다. 이제 DevTools의 폴더블/듀얼 스크린 디바이스 에뮬레이션을 Edge 뿐만 아니라 Chrome 과 Chromium 기반 브라우저에서 사용할 수 있습니다. 마이크로소프트는 현재 Android OS 내의 모든 Chromium 기반 브라우저에도 작업을 진행중입니다.