前端开发者学堂 - fedev.cn

折叠屏相关的Web API

发布于 大漠

告诉大家一个好消息,The Screen Fold API 已经进入 W3C规范的ED阶段 了。这个规范主要可以帮助我们以后为使用折叠屏设备用户提供一个更好的用户体验,即 为折叠屏设备在不同的折叠状态(“姿势”)提供相应的布局。这个规范主要由 三星(Samsung)@Diego González英特尔(Intel Corporation)@Kenneth Rohde Christiansen 一起起草的,虽然目前只是处于规范的 ED 阶段,但这为我们今后在折叠屏设备上实现Web布局有了一定的理论依据。也许在未来,这里面的一些属性或API还会有所变动,但这并不影响我们去探究或参与讨论。或许说不定明年,或者后年你就在实际业务中需要面对折叠屏幕的适配需求。如果你感兴趣的话请继续往下阅读。

简介

和以往我们熟悉的不同类型设备已经在市场上出现了,甚至你已经开始在使用了,比如我们在《可折叠Web可能会给我们带来的变化》和 《可用于双屏幕和折叠屏的Web API》提到的 双屏幕折叠屏幕 的设备。这些设备有着一个最相同的特征:

设备是双屏幕,可以折叠

这些设备中,有被称为 “无缝” 的设备(比如,三星Galaxy Fold,三星Galaxy Flip Z,华为Mate X,摩托罗拉Razr,联想ThinkPad X1 Fold),还有被称为 “有缝” 的设备(比如微软的Surface Duo,中兴Axon M,微软Surface Neo,戴尔Duet)。“无缝”和“有缝”最大的区别就是“有缝设备它有两个物理屏幕”:

甚至在未来会多于两个屏幕的设备出现在市场上。

还需要说明的是,“无缝”和“有缝”的设备可以有不同的尺寸,从手机、平板电脑到笔记本电脑大小不等。还应该注意的是,不同的设备会有不同的默认方向(纵向或横向),折叠可能以垂直或水平的方式发生。

因此,当设备折叠时,它们在物理上形成一个角度。我们去了解这个折叠角度的主要是可以在这些设备中实现响应式设计,有更多的机会给使用这些设备用户带来更好的体验。

也就是说,从通过避开折叠的区域来提高Web的可用性,到实现Web的创新用例,了解折叠角度可以帮助开发人员根据不同的设备来定制内容。它还可以实现检测设备可能处于不同的姿势。在这种情况下,开发者可能要根据折叠角度的状态为其提供不同的布局。此外,开发者还可以根据不同的“姿势”来调整内容,并有可能对其中的一些转换过程添加动效。

可折叠屏幕相关的API

由于可折叠设备相对来说是新型设备,面对这些新型设备时很多开发者并没有做好相应的知识储备,甚至是不知道从何入手。事实上呢?有些Web开发者已经开始在为我们制定这方面的API,除了文章开头提到的三星(Samsung)@Diego González英特尔(Intel Corporation)@Kenneth Rohde Christiansen 之外还有微软(Microsoft)@Bogdan Brinza@Daniel Libby@Zouhir Chahoud。只不过对于Web开发者来说,现在这些制定的规范(CSS相关的特性)和Web API(JavaScript API)还很新,不确定因素过多,甚至差异性也比较大。

到目前为止主要分为两个部分。其中一个部分是《可用于双屏幕和折叠屏的Web API》介绍的相关API,它是由微软(Microsoft)@Bogdan Brinza@Daniel Libby@Zouhir Chahoud一起制定的,更适用于“有缝”的折叠处设备;另一部分是目前处于W3C规范ED阶段的 屏幕折叠 API ,它更适用于“无缝”的折叠设备。

检测折叠屏的CSS媒体查询

@argyleink在Github上发起了一个使用CSS媒体特性来检测折叠屏的讨论。也就是说,Web开发者可以使用@media相关的特性来识别折叠屏,为折叠屏的类型(比如“有缝”和“无缝”)提供相应的媒体查询。

“有缝”折叠屏

使用screen-spanning这个特性可以用来帮助Web开发人员检测“根视图”是否跨越多个相邻显示区域,并提供有关这些相邻显示区域配置的详细信息。

CSS的screen-spanning可以被指定为一个值,该值可以描述设备具有的折叠(或铰链)数量及其姿势。如果该设备不是可折叠设备,则值为none。如果它是可折叠的,它可以接受以下两个值中的一个:

  • single-fold-vertical:屏幕是水平的,布局视图跨越单个折叠(两个屏幕)并且折叠姿势是垂直时(分左右两边),这个值是匹配的
  • single-fold-horizontal:屏幕是垂直的,布局视图跨越单个折叠(两个屏幕)并且折叠姿势是水平时(分上下),这个值是匹配的

比如:

@media (spanning: single-fold-vertical) {	
    .blue {
        height: 100px;
        width: 100px;
        position: absolute;
        left: calc(env(fold-left) - 100px);
        top: 0;
    }

    .yellow {
        height: 100px;
        width: calc(100vw - env(fold-right));
        position: absolute;
        left: env(fold-right);
        top: 0;
    }

    .pink {
        height: 100px;
        width: env(fold-left);
        position: absolute;
        left: 0;
        bottom: 0;
    }

    .green {
        height: 100px;
        width: 100px;
        position: absolute;
        left: env(fold-right);
        bottom: 0;
    }
}

这个媒体特性更适合于像微软的Surface Duo,中兴Axon M,微软Surface Neo,戴尔Duet等可折叠屏。

“无缝”折叠屏

折叠屏API 中新增了另外两个 CSS媒体特性,即 screen-fold-posturescreen-fold-angle

  • screen-fold-posture:用来检测可折叠设备的折叠姿势
  • screen-fold-angle:用来检测可折叠设备的折叠角度

比如:

@media (screen-fold-posture: laptop){
    body {
        display: flex;
        flex-flow: column nowrap;
    }
    .videocall-area, .videocall-controls {
        flex: 1 1 env(fold-bottom);
    }
}

有些场景需要针对不同的折叠角度来改变布局,比如,可能需要按用户划分独立的观看区域的体验(比如游戏),你会发现默认185°tent过于“开放”,无法保证一个用户不会看到另一个用户的屏幕。这个时候使用屏幕折叠角(screen-fold-angle)由开发者定义一个范围或阈值来改变布局。

html {
    background-color: blue;
}

@media (max-screen-fold-angle: 120deg) {
    html {
        background-color: red;
    }
}

为了满足可折叠设备的需求,未来可能还会有min-anglemax-angle媒体查询,它们的值可以从CSS数据类型中获取一个角度:

@media(min-angle: 110deg) { 
    /*...*/ 
}

@media(max-angle: 170deg) and (spanning: single-fold-vertical) { 
    /*...*/ 
}

折叠姿势

在折叠API中提供了六种折叠姿势:

  • no-fold:是指没有铰链(Hinge)的设备的姿势。这是对不折叠的设备的预期值
  • laptop:是指设备是作为传统的笔记本电脑来使用的,也就是说一块屏幕被放置在一个或多或少的水平面上,屏幕角度在180度到0度之间
  • flat:是指屏幕被放置在一个水平面上,屏幕角度在180度左右
  • tent:是指两块屏幕的边缘放置在一个水平面上,且屏幕角度大于180度,看上去有点像一顶帐篷
  • tablet:是指设备可以在铰链上一直旋转,让屏幕背靠背,屏幕角度被认为是360度左右
  • book:是指屏幕设置折叠的角度在50度到160度左右的时候,通常像我们手握看书的样子

上面这几个值都可以用于@mediascreen-fold-posture中。使用折叠姿势有利也有弊,先来看它的利:

  • 不需要暴露精确的折叠角度,这也可能会涉及到用户隐私的问题,开发者可以通过模糊的折叠角度来避开这个安全问题
  • 利于不需要根据精确折叠角度来调整布局的Web应用

不利点是:

  • 预先定义的折叠姿势的存在将实现绑定到特定的硬件
  • 并不是所有硬件都支持每种折叠姿势
  • 不能指定折叠角度的取值范围
  • 不适合在动画中使用,因为没有暴露折叠角度值
  • 不是面向未来的,因为它假设了设备将支持的折叠类型

折叠角度

折叠设备有一个最大的特点就是“具有一个折叠角度”,即使是平放的(比如像flat)还是折叠的(比如像tablet),都有相应的折叠角度值。虽然折叠姿势能帮助我们给用户提供更好的体验,但也正如上面提到的,他也有很多缺点,比如说有些姿势和硬件类型不匹配,使用姿势时获取不到折叠角度值等。而且用户在使用折叠设备的时候,在折叠状态中也有可能无法和上面提到的折叠姿势相匹配,甚至开发者希望拿到折叠角度的值,给元素添加动效,或者在折叠时给页面添加转场动效等。

庆幸的是,在折叠API中,可以使用screen-fold-anglemax-screen-fold-anglemin-screen-fold-anglemin-anglemax-angle媒体查询,它们的值可以从CSS数据类型中获取一个角度。

提供折叠角度的好处是:

  • 提供了一个360度角度值范围,让Web开发者可以在此范围内为用户定制不同的体验
  • Web开发者可以根据折叠角度值指定的范围来指定媒体查询,即,媒体查询可以根据折叠角度值提供不同的布局
  • 可以提供类似于原生应用的体验,比如说,Web开发者可以获取设备折叠的角度,根据角度值做一些事情(比如给元素添加动效)

同样的,使用折叠角度也有不利之处:

  • 使用折叠角度不足以检测出设备使用的折叠姿势
  • 把不同模式的值留给应用程序开发者,将意味着一个网站考虑一种模式,可能会被另一个网站考虑,这可能会让用户感到困惑

折叠姿势和折叠角度的映射关系

不管是折叠姿势还是折叠角度,他们两者都有各自的利弊,而且折叠姿势没有一个确切的折叠角度值(它的值都是一个近似值),甚至不同的设备在相同的折叠姿势之下有不同的折叠角度值。比如,一个设备在平放时可能不会产生180度的精确值,反而产生一个175度到185度之间的某一值。

另外,还有可能受物理条件限制或设备设计的约束,有些折叠设备具备的折叠姿势无法和上面提到六种折叠姿势完全匹配(或多或少的缺一两种折叠姿势),在这种情况之下,折叠设备必须确保所有折叠角度和折叠设备方向的组合可以和已定义的折叠姿势有一定的映射关系:

设备在水平折叠下,**折叠姿势(posture折叠角度(angle)**的对应关系:

折叠姿势 折叠角度 当前屏幕方向
laptop [0° - 140°] portrait-primary / portrait-secondary
flat [140° - 185°] 任何方向
tent [185° - 335°] 任何方向
tablet [335° - 360°] 任何方向
book [0° - 140°] landscape-primary / landscape-secondary

设备在垂直折叠下,**折叠姿势(posture折叠角度(angle)**的对应关系:

折叠姿势 折叠角度 当前屏幕方向
laptop [0° - 140°] landscape-primary / landscape-secondary
flat [140° - 185°] 任何方向
tent [185° - 335°] 任何方向
tablet [335° - 360°] 任何方向
book [0° - 140°] portrait-primary / portrait-secondary

水平折叠的设备是指在其主要外形尺寸中,折叠铰链发生在屏幕的两侧,有点类似于以前的上下翻盖设备:

垂直折叠的设备是指在其主要外尺寸中,折叠铰链发生在屏幕上下两侧,有点类似于书本打开,关闭的样子:

有关于landscape-primarylandscape-secondaryportrait-primaryportrait-secondary以及我们以前常说的portraitlandscape更详细的介绍可以阅读W3C的 The Screen Orientation APIScreenOrientation API

计算显示区域几何形状

在传统屏幕下,我们关注的只是Viewport的大小,一般情况之下,使用媒体查询就可以处理好。自苹果的iPhone X系列的设备(带有刘海的设备):

自此,给Web开发者或UI设计师带了另一个概念,即 安全区域

为了让Web开发者开发的Web应用能更好的适配这些带有安全区域的设备,最早由苹果公司提出了env()(最早称为constant())这样的CSS函数用来识别设备安全区域的大小。

W3C的 CSS Environment Variables Module Level 1 规范定义了env()函数的使用,以及到目前为止提供的安全变量:

安全区域变量名称 描述
safe-area-inset-top <length> 在Viewport顶部的安全区域内设置量(CSS像素)
safe-area-inset-right <length> 在Viewport右边的安全区域内设置量(CSS像素)
safe-area-inset-bottom <length> 在Viewport底部的安全区域内设置量(CSS像素)
safe-area-inset-left <length> 在Viewport左边的安全区域内设置量(CSS像素)

如果你对带有刘海设备的适配感兴趣的话,建议你花点时间阅读:

这里花了点时间向大家提CSS的env()函数是有目的性的。因为我们在折叠设备中也会涉及到env()函数,即 使用env()函数来计算折叠设备逻辑显示区域几何形状(也就是显示区域相关的值)。这样做的好处是,在折叠屏设备中,开发者可以很好的计算出每个逻辑显示区域的大涉,并确保他们知道有多少内容(如果有的话)在正确的区域显示。

在折叠屏设备下,我们可以像下图这样,使用env()函数来计算:

这些CSS环境变量的值是CSS像素,并且是相对于布局视图的(即在客户端坐标中,由CSSOM视图定义)。当不处于跨越状态时,这些值将被视为不存在,则会取env()函数的回退值:

有了这些环境变量之后,我们就可以使用CSS的Flexbox,Grid和calc()等实现类似下图这样的布局效果:

代码如下:

<!-- HTML -->
<body>
    <article class="article">
        ...
    </article>
    <figure class="figure">
        <img src="/sydney-opera-house.jpg"
            alt="Sydney Opera House">
    </figure>
</body>

/* CSS Flexbox */
body {
    height: 100vh;
    display: flex;
}

.article {
    flex: 0 0 env(fold-left);
    margin-inline-end: env(fold-width);
    overflow-y: scroll;
}

.figure {
    flex: 1;
    margin: 0;
    overflow: hidden;
}

.figure img {
    height: 100%;
}

/* CSS Grid*/
body {
    height: 100vh;
    display: grid;
    grid-template-columns: env(fold-left) 1fr;
    column-gap: env(fold-width)
}

.article {
    overflow-y: scroll;
}

.figure {
    overflow: hidden;
    margin: 0;
}

.figure img {
    height: 100%;
}

需要特别注意的是,这几个环境变量目前既没有被纳入到W3C的The Screen Fold API规范,也还没有纳入到W3C的CSS Environment Variables Module Level 1规范中,但在微软的双屏折叠设备中是得到了相应的支持,我想在未来,这几个环境变量应该也会纳入到CSS Environment Variables Module Level 1规范中

窗口段(Window Segments)枚举API

前面提到的都是更适合用于CSS样式上的媒体特性,但在具体的开发者我们需要在折叠屏上使用JavaScript相关的API,比如说在Canvas或WebGL这样的非DOM目标上。对于在JavaScript环境上识别折叠屏设备的API主要分为两个部分,其中一个部分更适合有两个逻辑显示区域的折叠设备,比如微软的Surface Duo、Surface Neo等设备;另外一个部分更适合于单屏幕可折叠设备,比如华为的Mate X。我们先来看第一部分,该部分主要由微软团队的开发者在维护,即 Window Segments Enumeration API,简称 窗口段枚举API

开发者可以使用窗口段(Window Segments)枚举API获得每个显示区域(双屏折叠设备每个逻辑显示区域)的几何图形。

window对象提供了一个getWindowSegments()方法,它将返回一个包含一个或多个DOMRects的数组,表示每个显示区域的几何形状和位置。比如:

const segments = window.getWindowSegments()

console.log(segments.length)

如果是单屏(或折叠屏折叠状态)的时segments.length返回的值为1,如果是可折叠屏展开状态时segments.length返回的值为2

使用该API可以获取相应的DOMRects相关的参数,比如:

同样也可以使用resizeorientationchange事件来检测浏览器是否被调整大小,或设备是否被旋转以及检索更新的显示区域。

let segments = window.getWindowSegments(); 

// 跨越两上逻辑显示区域和折叠边界,并且是垂直方向的 
console.log(segments.length); // » 2 

// 用户决定旋转设备,浏览器仍然是跨越的,但折叠现在是水平的 
// 在窗口调整中,当用户进入或离开跨越状态时,resize和orientationchange事件都会触发 
window.addEventListener('resize', () => { 
    // 当水平折叠时,我们最初检索的片段不再使用表示片段2的最新信息进行更新 
    segments = window.getWindowSegments(); 
});

到目前为止,并没有明确的方法来解析折叠是垂直的(single-fold-vertical)还是水平的(single-fold-horizontal),因为这些信息可以很容易地从返回的DOMRects中计算出来:

function isSingleFoldHorizontal() { 
    const segments = window.getWindowSegments(); 
    
    // 单折叠式是指设备有1折叠式和2个逻辑显示区域 
    if( segments.length !== 2 ) { 
        return false; 
    } 
    
    // 水平折叠single-fold-horizontal是指第一段顶部小于第二段顶部 
    if( segments[0].top < segments[1].top ) { 
        return true; 
    } 
    
    // 如果这个条件满足,那么折叠就是垂直的single-fold-vertical 
    return false; 
}

对于折叠宽度(fold-width)同样适用,Web开发人员可以使用getWindowSegments()提供的信息一了解窗口管理器是否屏蔽了在折叠后呈现的内容,以及折叠宽度(fold-width)是否大于0px

function foldWidth() { 
    const segments = window.getWindowSegments(); 
    
    // 如果有1段(segment),那么折叠宽度(fold-width)不适用,返回0 
    // 如果有超过2段(segment),那么我们不处理这种设备,但返回0 
    if( segments.length !== 2 ) { 
        return 0; 
    } 
    
    // 折叠是垂直的 (spanning: single-fold-horizontal) 
    // 设备看起来像这样的: [][] 
    if( segments[0].top === segments[1].top ) { 
        return segments[1].left - segments[0].right; 
    } 
    
    // 如果我们达到这一点,那么折叠是水平的(spanning: single-fold-vertical) 
    return segments[1].top - segments[0].bottom;
}

屏幕折叠(Screen Fold)API

The Screen Fold API中除了提供CSS的@media特性screen-fold-posture(检测折叠设备折叠姿势)和screen-fold-angle(检测折叠设备折叠角度)之外,也像 Window Segments Enumeration API类似,提供了一些JavaScript API来获取折叠设备的“折叠姿势”和“折叠角度值”。

简单地说,屏幕折叠API扩展了CSSOM视图(CSSOM View)的screen

在浏览器调试工具中输入中window.screen可以输出CSSOM视图相关参数的值:

对于折叠设备,我们在window.screen接口基础上扩展了一个screen.foldScreenFold接口,在这个接口中相应的提供了:

  • postrue获取折叠设备折叠姿势
  • angle获取折叠设备折叠角度值
  • onchange事件,当折叠设备的anglepostrue发生变化时就会触发该事件

也就是说,在折叠设备中可以使用screen.fold.posture可以获取折叠设备的折叠姿势,它输出的值可能会是no-foldlaptopflattenttabletbook其中之一;也可以使用screen.fold.angle获取折叠设备折叠时对应的折叠角度值。比如,我们可以像下面这样来获取相应的折叠姿势和折叠角度值:

screen.fold.addEventListener("change", () => {
    const { angle, posture } = screen.fold;
    console.log(`当前折叠角度的值是 ${angle} 度, 它对应的折叠姿势可能是 ${posture} !`);
})

我们也可以动态监听折叠设备的onchange事件,动态改变元素的旋转角度,从而给元素添加动画效果:

let fish = document.querySelector('.fish-circle');

ScreenFold.addEventListener('change', function(e) {
    //animation keyframes
    let rotationFish = [
        {transform: `rotate(${e.angle})`, easing: 'ease-out'}
    ];

    fish.animate(rotationFish, 100);
};

注意,目前折叠API扩展window.screenScreenFold(或screen.fold)以及相应的postureangleonchange等支持的程度需要使用真机来验证。如果手上有相应真机,可以尝试输出相应结果

相关Polyfill

到目前为止,不管是screen-spanning还是screen-fold-posturescreen-fold-angle媒体查询还是env()相应的环境变量,甚至包括相应的JavaScript API都还处理草案的讨论阶段,还没有得到主流设备的支持。如果你想验证上面提到的CSS特性和JavaScript相关的API,我们还需要借助相应的Polyfill:

有关于这两个Polyfill详细介绍这里就不做过多阐述了,如果感兴趣的话,请阅读对应Polyfill的文档。

使用场景

我们来看看折叠设备使用的几个可能出现的场景。

场景一:把折叠设备当做一本书来用

折叠设备在平放(对应的折叠姿势是flat,折叠角度约180度)时正常显示Web内容时按照媒体查询指定的方式呈现(布局)。当用户将设备折叠到特定的角度时,布局将会发生变化,以适应类似手握看书的体验,内容可以避开设备的折叠区域。一旦设备再次平放,内容又会根据可用的屏幕尺寸重新布局。

这种模式对于博客、在线杂志或想模仿开放式书籍布局的开发者来说,是非常有意思的。比如,微软Surface Due在很多场景下都可以像书一样使用:

场景二:把它放在桌子上

用户可以将折叠设备放置在桌子上,其中一个面平放在桌子上,另一个面在指定范围内折叠起来。有点像我们平时使用笔记本电脑那样:

使用的例子可以包括:

  • 电话会议(共享屏幕或视频)
  • 流媒体,如电视节目以及视频流
  • 游戏(比如,战舰类、棋牌类)
  • 应用程序

使用三星Galaxy Flip Z设备就可以体验到上面提到的场景,如下图所示:

场景三:像帐篷一样使用

使用可折叠设备,可以选择将内容传递的折叠的任何一侧,或者配合内容创建分屏游戏或类似的内容,这样做是有好处的。比如说,创建一个“你画我猜”这样的游戏,你可以在折叠设备的侧绘制,另外一位在屏幕的另一侧猜。

它在折叠的时候有一个明显的特征,就像我们生活中使用的帐篷一样:

比如像下图所示,使用微软Surface Duo观看视频也是非常好的体验(将微软Surface Duo折叠成像帐篷一样放在桌面上):

场景四:基于折叠角度值打开或关闭设备

开发者可以让Web应用根据折叠角度值做出相应的反应,比如说打开或关闭应用。比如在可折叠设备中打开一个Web网站,在整个过程中我们可以给他添加一个动画效果,比如一个球体会根据设备的打开程度(折叠角度值)改变球的大小和位置以及透明度等:

示例

先来看一个简单的示例,在body上设置一个背景颜色:

body {
    background: orange
}

在不同的终端上看到的效果如下(比如PC机显示器上,移动设备上和折叠设备):

使用@media可以在手机上设置一个green背景色:

@media (max-width: 540px) {
    body {
        background: green;
    }
}

这个时候,手机上的背景颜色变成了绿色:

如果把screen-spanning媒体查询加进来,可以给折叠设备设置另一个背景颜色:

@media (screen-spanning: single-fold-vertical) and (min-width: 541px) {
    body {
        background: yellow
    }
}

这个时候三种不同类型设备,body背景颜色将分别是orangegreenyellow

按类似的方式,我们可以使用CSS的Grid、env()screen-spanning构建一个更复杂的布局。比如:

<!-- HTML -->
<head>
    <script src="./sfold-polyfill.js"></script>
    <script src="./spanning-css-polyfill.js"></script>
</head>
<body>
    <div class="App">
    
        <div class="header">Header</div>

        <div class="stories">Story Data</div>

        <div class="content">Content</div>

        <div class="related">Related</div>

    </div>
    
</body>

添加相应的CSS:

<style>
    body {
        margin: 0;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
            "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
            sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
    }

    .App {
        display: grid;
        grid-template-columns: 10vw 67vw 20vw;
        grid-template-rows: 5vh 60vh 33vh;
        grid-template-areas:
            "header header header"
            "stories content related"
            "stories content .";
        column-gap: 1vw;
        row-gap: 1vh;
    }

    .header {
        grid-area: header;
        background-color: lime;
    }

    .stories {
        grid-area: stories;
        background-color: maroon;
    }

    .content {
        grid-area: content;
        background-color: mediumorchid;
        overflow-y: auto;
        min-height: 50vh;
    }

    .related {
        grid-area: related;
        background-color: mediumslateblue;
    }
</style>

在浏览器中打开这个页面,看到的效果会像下图这样:

在上面基础上使用@media给iPhone和iPad中改变布局:

/* phone layout portrait */
@media screen and (max-device-width: 480px) and (orientation: portrait) {
    .App {
        display: grid;
        grid-template-columns: 100vw;
        grid-template-rows: 10vh 10vh auto 10vh;
        grid-template-areas:
            "header"
            "stories"
            "content"
            "related";
        column-gap: 1vw;
        row-gap: 1vh;
    }
}

/* tablet layout portrait */
@media screen and (min-device-width: 480px) and (max-device-width: 1200px) and (orientation: portrait) {
    .App {
        display: grid;
        grid-template-columns: 25vw auto;
        grid-template-rows: 10vh 80vh 8vh;
        grid-template-areas:
            "header header"
            "stories content"
            "stories related";
        column-gap: 1vw;
        row-gap: 1vh;
        font-size: 2.4vh;
    }
}

效果如下:

使用screen-spanningenv()calc()给像微软Surface Due这样的折叠设备添加样式:

/* 横向双屏布局 */
@media (screen-spanning: single-fold-vertical) {
    .App {
        display: grid;
        grid-template-columns: calc(env(fold-left) - 1vw) env(fold-width) calc(
            100vw - env(fold-left) - env(fold-width) - 1vw
        );
        grid-template-rows: 5vh 60vh 33vh;
        grid-template-areas:
            "header header header"
            "stories . content"
            "related . content";
        column-gap: 1vw;
        row-gap: 1vh;
    }
}

/* 纵向双屏 */
@media (screen-spanning: single-fold-horizontal) {
    .App {
        display: grid;
        grid-template-columns: 60vw 39vw;
        grid-template-rows:
            9vh
            calc(env(fold-top) - 10vh - 2vh)
            env(fold-height)
            calc(99vh - env(fold-top) - env(fold-height) - 2vh);
        grid-template-areas:
            "header header"
            "stories related"
            ". ."
            "content content";
        column-gap: 1vw;
        row-gap: 1vh;
    }
}

你可以在模拟器上看到像下图这样的效果:

如果你感兴趣的话,可以使用折叠屏API中提供的screen-fold-posturescreen-fold-angle给像三星Galaxy Fold,三星Galaxy Flip Z折叠设备提供不同的布局效果。要是你有华为Mate x设备,也可以尝试着改写上面的Demo,查看效果。

最后向大家推荐一个React版本的Demo。这个Demo是@slace提供的,具体的代码这里不展示,可以在Github上直接下载也可以直接点击这里,在浏览器中查看效果。

如果你查看了其源码的话,你可以在App.css中看到像下面这样的CSS代码:

@media (screen-spanning: single-fold-horizontal),(screen-spanning: single-fold-vertical) {
    .non-foldable {
        display: none;
    }
    .App-header {
        min-height: auto;
    }

    .second {
        text-align: center;
    }
}

@media (screen-spanning: single-fold-horizontal) {
    .App-header,
    .second {
        max-height: env(fold-top);
        min-width: env(fold-width);
        max-width: env(fold-width);
    }
    .second {
        padding-top: env(fold-height);
    }
}

@media (screen-spanning: single-fold-vertical) {
    .App {
        float: left;
    }
    .App-header,
    .second {
        min-width: env(fold-left);
        max-width: env(fold-left);
        min-height: env(fold-height);
    }

    .second {
        float: left;
        padding-left: env(fold-width);
    }
}

上面我们演示的示例在微软的Surface Due这样的折叠设备上可以看到效果。

相关API文档

需要特别声明的是,上面这些链接以及整篇文章提到的相关特性还不是最终的版本,或许在未来会根据设备的需要做相应的调整。但这些API对于Web开发者来说是福音,为我们在折叠设备上构建不同的布局效果提供理论依据。开发者也可以根据这些特性为使用折叠设备用户提供不同的体验。如果你对这方面感兴趣,或者你在写相应的测试案例时发现有什么问题以及有更好的想法都可以在相应的Github上提供,或者直接参与相关的讨论。甚至还可以直接参与这方面规范的制定。

小结

这篇文章将可折叠设备的两种类型的API(一种是微软团队提供的草案,另一种是三星和英特尔团队提供的草案)整合在一起。正如文章中所说的,这两种类型草案给CSS的媒体特性(@media)新增了screen-spanningscreen-fold-posturescreen-fold-angle查询条件,另外给env()提供了另外四种环境变量fold-topfold-leftfold-widthfold-height,在未来还会新增fold-bottomfold-right两个环境变量。

除了CSS方面的特性之外,还提供了相应的JavaScript API,比如window.getWindowSegments()window.screen,并且还扩展了window.screen,可以使用screen.foldScreenFold来获取折叠设备的折叠姿势(postrue)和折叠角度值(angle),还可以使用onchange事件用来监听折叠设备姿势和角度值改变。

扩展阅读

如果你对折叠屏相关的内容感兴趣的话,还可以阅读下面相关文章: