前端开发者学堂 - fedev.cn

可折叠Web可能会给我们带来的变化

发布于 大漠

可折叠Web的说法是可折叠设备带来的。可折叠设备形式多样,从笔记本电脑到手机,再到新奇的双屏幕混合设备。对于这类新发明并没有一个全面的定义,但大多数都可以归为两类。“可折叠”是指屏幕可以折叠的设备(比如华为Mate X,三星Galaxy Z Flip);而“双屏”设备的屏幕是分开的,但也可以以独特的方式一起工作,以灵活的形式提供生产力(比如,微软的Surface Neo和Surface Duo)。当涉及到Web设计时,这两种类型可能会遵循类似的规则。如果这项技术能大获成功,那么Web设计将面临十年来最大的变革。这对于我们Web开发者而言,也将会开启新的旅程。

听起来就很令人兴奋,但这到底意味着什么呢?可折叠Web将带来新的挑战新的机遇,而且很可能还会带来新的概念。互联网可能也会经历自智能手机以来最大的变革。你可能会认为这是一种炒作,但事实上这样的一天已经离我们不是太远。记得在去年这个时候,有些移动品牌商,比如华为,三星都推出可折叠的硬件设备,而微软,苹果等公司在幕后的努力也不逊色。可以说可折叠Web即将到来。我们真的应该去思考可折叠Web可能会给我带来什么变化?如果这一天真的到来,我们的技术是否能支持该设备。简单地说,我们开发的Web页面能不能在可折叠设备和双屏幕上完美的运行。

可折叠和双屏设备

可折叠设备采用的是一种柔性屏幕技术,该技术的研究始于20世纪70年代,但直到世纪之交才得到真正的发展。特别是在去年,得到了爆发式的发展——在市场上能看到一些可折叠设备终端。比如三星Galaxy Z Flip手机,模仿的是老式的翻盖手机:

比如三星的Galaxy Fold手机,在折叠状态是一个小屏幕,展开状态是一个宽屏幕:

比如华为Mate X,屏幕是包裹在手机外面的:

还有很多像电子书一样,当设备完全打开时,内部的两个显示屏会合二为一。通常在外面有一个独立的小屏幕,这样用户在使用的时候就不用打开它。

双屏幕设备是具有两个对称屏幕的便携式设备,以独特的方式一起工作,以灵活的形式提供生产力。比如像微软的Surface Neo和Surface Duo这样的双屏幕设备,人们可以比以往任何时间都更快地完成工作。如果你使用过Surface Neo这样的双屏幕设备,你可以在一个屏幕上做笔记,在传输过程中在另一个屏幕上查看完整的项目建议书:

尽管现在市场上有不同的折叠屏,双屏幕设备,而且还会有更多的类似设备出现,但我们相信为这些设备设计应用程序会有一个共同的方法。我们希望这将帮助你的应用程序能跑在更多的设备上,而不需要为不同的设备做特殊的设计和处理。

换句话说,在可折叠屏或双屏幕的设备上,用户可以做更多的事情,比如说让屏幕分屏,可以同时打开多个应用程序:

也可以打开同一个应用,在应用中分屏,打开不同的页面,比如:

像上图这样,应用程序在两个屏幕上显示,就是所谓的跨屏布局。默认情况下,该应用程序将表现得好像它是在一个更大的屏幕上显示。你也可以改变你现有的应用程序布局来适应两个屏幕之间的缝隙,或者你可以更进一步,允分的利用双屏幕设备而专门创建不同的布局控件。比如iPad版本的手淘,就可以分屏展示:

新的Web标准、新的体验和新的问题

可折叠和双屏幕设备正在出现。这已经不是概念,也不是炒作。这更不是重点。重点是:

可折叠和双屏幕这项技术将如何影响Web开发人员、用户体验设计师以及其他以提供高质量浏览体验为业务的人

首先可折叠和双屏幕会给视觉设计师在做设计的时候带来很大的变化。

整个可折叠的概念是基于你可以把你的手机变成平板电脑的想法。市场上大多数平板电脑的屏幕宽高比都是 4:3,所以手机和平板电脑的混合也应该是4:3。这使用折叠设备达到3:2。而这些比率都不能保证16:918:9的真实视频体验。其中4:3的宽高比更适合在各种文本和图形编辑器中执行任务和工作,比如iPad,但它需要更大的分辨率,这是可折叠设备无法做到的。这使得大多数可折叠手机处于一个模糊地带,给人用户的体验就会变得很糟糕,这也是因为在传统手机中是无法达到16:9的比例。

为了让可折叠手机成为一种有用的媒体设备,它必须转换成16:918:9的设备,然后再折叠成8:99:9 —— 基本上就是一个正方形。@Nathan Cunn通过计算得出结论,折叠手机的完美宽高比应该是其宽度的1.4倍。

这也将会是iPhone折叠屏将会采用的一种宽高比例:

除此之外,不管是可折叠设备还是双屏幕设备,最大的特征就是屏幕变大了,在展开的状态下和平板一样了。这样一来,可用的空间就变大了。

可折叠和多屏设备打破了移动设备(手机)可用空间小的束缚!

空间大了,能放置的内容就多了,如果设计还是按着以前的思路,直接拉伸平满全屏,就过于浪费了。面对这样的场景,除了响应式Web设计能帮助我们更好的利用可用空间之外,还可以参照淘宝设计提出的一个概念:

让你的内容像水一样的流动(“Content is like water”)

用一张图来描述:

有关于这方面更详细的介绍,可以关注淘宝设计微信公众号:

你可以搜索“可折叠”关键词,能找到一些这方面的文章,比如:

对于双屏幕给设计带来的变化,或者说设计应该要注意的细节,可以阅读微软官方有关于双屏幕的介绍:《Introduction to dual-screen devices》。

另外,折叠屏幕还会带来一些其他的设计和体验变化。

单(双)手移动设计

随着状态的改变(这里的状态是折叠和展开状态,单屏和多屏状态),整个体验本身也随之改变。目前大多数用户都习惯用一只手操作移动设备(手机),而展开状态,就像iPad了,这个时候需要两只手来操作设备(或应用)。

手机操作 vs 平板操作

屏幕连续性和间隔

可折叠设备和多屏幕设备有着明显的区别。

可折叠设备在展开状态时,它的屏幕是连续性的,我们在设计的体验应该无缝地转移到全屏上。

针对这样的情景,如果只是粗暴的放大到全屏,那么布局上不会有太多的变化,只不过对于Web中的部分对象会变得模糊,比如图像。当然,也可以考虑设计上做差异性的处理,比如说做分屏设计:

对于多屏幕设备,有可能屏幕不是连续性的,比如微软的Surface Neo和Surface Duo,屏幕之间就有一个间隙:

在设计和布局的时候,就需要避免Web对象处在两个屏幕的间隙之间,比如:

或者:

多窗口,多应用

前面提到过,折叠屏和多屏幕最大的特征之一,就让我们有更多的空间可利用。这样对于用户来说,对于处理多任务更友好,能同时做更多的事情。因此,我们应该始终考虑用户可能会同时运行多个App应用。比如说,一边查看日历,一边查看地图,一边看新闻:

除了同时打开多个App应用之外,还可以在同一个App中,分屏做不同的事情,比如,一边看直播,一边逛淘宝:

折叠屏和多屏幕除了给设计师带来新的挑战之外,给Web开发者也带来相应的挑战。在去年华为Mate X出来的时候,有幸参与在Mate X做一些Web端的适配处理。主要处理H5的应用完美适配折叠屏设备。

由于相关H5业务采用的都是视窗单位vw(我常称该方案为vw-layout),能较好的让相应的H5业务适配折叠屏展开状态,但也存在一些相应的问题,最为突出的是图像变得模糊:

当然也尝试着采用响应式Web设计来做不同的布局处理:

虽然该方式可以让我们的H5应用最大化的利用折叠屏可用空间,但也存在一定的缺陷。因此在《聊聊安卓折叠屏给交互设计和开发带来的变化》一文中提出相应的概念:

处理折叠屏时,我们应该像一些带有刘海设备(比如iPhone X)一样,具有独特的检测特性来检测折叠设备或多屏幕设备

值得庆幸的是这一年来,在这方面有了明显的变化,尤其是微软公司的团队对可折叠技术的探究和讨论非常的积极。在今年2月份,三位微软开发人员@Bogdan Brinza@Daniel Libby@Zouhir Chahoud发表了一篇文章,解释如何使用JavaScript API和CSS 媒体查询来处理可折叠和多屏幕设备的布局

该文档提出两个概念:用于双屏幕布局的CSS窗口段枚举的JavaScript API。它的宗旨是作为讨论折叠屏和多屏幕在Web开发中技术标准。即:以可折叠和双屏幕设备为目标的Web开发人员能够在跨多个显示区域的窗口中有效的对Web应用进行布局

未来处理可折叠或多屏幕的Web技术

@Bogdan Brinza@Daniel Libby@Zouhir Chahoud三位开发者在Github上发表的文档《Web Platform Primitives for Enlightened Experiences on Foldable Devices》来看。处理可折叠和多屏幕设备的Web布局主要由两部分特性组成,其一是CSS的媒体查询特性,其二是JavaScript API。

接下来,我们主要围绕着这两个部分来展开。

用于构建双屏幕布局的CSS特性

特别声明:用于双屏幕布局的CSS还处于W3C规范的草案当中,一切皆有可能会变

@Brinza、@Libby和@Chahoud提出了一个CSS特性,即媒体特性,它可以确定网站是否跨越两个相邻的显示区域,以及这两个相邻显示区域的配置。相应的还提出了另一个特性,即环境变量(指的是用户代理定义的环境变量),它将帮助Web开发人员以CSS像素计算每个屏幕区域的大小。

为什么要提出新的CSS特性

当浏览器窗口跨越设备折叠时,告诉Web开发者折叠方向和显示边界可以帮助他们开发出来具有较好体验的Web应用。这里快速概述可能会出现的模式。

将更大屏幕的UI模式带到更小的便携设备

由于屏幕尺寸的限制,传统的便携式触摸设备在很大程度上依赖于“叠加视图”,比如点击收件箱列表中的一封邮件,就会导致整个收件箱列表视图被选中的邮件内容视图所取代。这种行为通常会创建额外的操作步骤;而在更大屏幕的设备上将会有一个更自然的配置,邮件收件箱列表视图和邮件内容视图是并排的。

当然,这种重新创造的模式并不是唯一的解决方案,只不过可折叠屏和多屏幕设备相对于传统的便携触摸式移动设备来说多了一个显示器,相当于增加一个屏幕空间,这样一来可以为Web开发者和设计师提供独特的机会来创造新的体验。

轻松改进现有的Web应用和UI组件

Web开发人员可能不想为这类设备引入主要的UI更改,而只想简单地移动一些组件。在下面的示例中,对于模态对话框(Modal)避免设备折叠更有意义(无论可折叠设备是无缝的还是有间隙的),并允许Web开发人员逐步增强他们的站点,提供更好的体验。

也正因为这些原因,提出了相应的CSS特性和设计原则。

CSS媒体特性:spanning

提出一个新的媒体特性:spanning,该特性可用于检测浏览器窗口是否跨越多个显示区域。

spanning特性主要有三个值:

  • single-fold-vertical:屏幕是水平的,布局视图跨越单个折叠(两个屏幕)并且折叠姿势是垂直时(分左右两边),这个值是匹配的
  • single-fold-horizontal:屏幕是垂直的,布局视图跨越单个折叠(两个屏幕)并且折叠姿势是水平时(分上下),这个值是匹配的
  • none:描述浏览器窗口不处于跨越模式时的状态

识别折叠或多屏设备的CSS环境变量

这个有点类似于刘海设备一样,通过env()函数来识别环境变量,即识别安全区域:

  • env(safe-area-inset-top):在Viewport顶部的安全区域内设置量(CSS像素)
  • env(safe-area-inset-bottom):在Viewport底部的安全区域内设置量(CSS像素)
  • env(safe-area-inset-left):在Viewport左边的安全区域内设置量(CSS像素)
  • env(safe-area-inset-right):在Viewport右边的安全区域内设置量(CSS像素)

这份建议预定义了几个可识别折叠或多屏设备的CSS环境变量:fold-topfold-leftfold-widthfold-height

Web开发人员可以利用这些变量来计算横屏和竖屏的每个屏幕片段大小。

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

来看一个简单的示例:一个地图应用程序,在一个屏幕上显示地图,在另一个屏幕上显示搜索结果。如下图所示:

如果用CSS代码来实现的话,大致如下:

@media (spanning: single-fold-vertical) {	
    body {
        flex-direction: row;
    }

    .map {
        flex: 1 1 env(fold-left)
    }
    
    .locations-list {
        flex: 1;
    }
}

spanning的Polyfill

到目前为止,新增的CSS媒体特性spanning和相应的环境变量都还处于草案的讨论阶,但如果你要尝试着在折叠设备或多屏幕设备上使用该特性的话,可以使用@darktears 提供的一个Polyfill

这个Polyfill的使用很简单,你可以使用NPM将这个Polyfill加载到你的项目中

npm install --save spanning-css-polyfill

安装完之后,通过<script>将对应的spanning-css-polyfill.js引入到项目中:

<script type="module" src="/path/to/modules/spanning-css-polyfill.js"></script>

也可以使用import的方式引入:

import "/path/to/modules/spanning-css-polyfill/spanning-css-polyfill.js";

这样你就可以在CSS中使用spanning这个新媒体查询特性和CSS环境变量fold-topfold-leftfold-widthfold-height

当然,你还可以手动改变显示(display)相关的配置。比如,通过导入FoldablesFeature对象:

import { FoldablesFeature } from '/path/to/modules/spanning-css-polyfill/spanning-css-polyfill.js';

可以通过FoldablesFeature对象来更新像spanningfoldSizebrowserShellSize等值。你也可以订阅change事件,以便spanning媒体查询特性或环境变量发生变更时得到相应的通知。

import { FoldablesFeature } from '/path/to/modules/spanning-css-polyfill/spanning-css-polyfill.js';

const foldablesFeat = new FoldablesFeature;

// Add an event listener.
foldablesFeat.onchange = () => console.log("change");

// Add as many event listeners as you want.
foldablesFeat.addEventListener('change', () => console.log("change"));

// Change a single value; results in one update (one 'change' event firing).
foldablesFeat.foldSize = 20;

// Change multiple values by assignment; results in one update.
Object.assign(foldablesFeat, { foldSize: 50, spanning: "none"});

// Change multiple values in one scope; results in one update
(function() { foldablesFeat.foldSize = 100; foldablesFeat = "single-fold-horizontal" })();

有关于spanning更详细的使用,可以查看Github上的相关教程

枚举窗口片段的JavaScript API

特别声明,枚举窗口片段的JavaScript API已移到W3C第二屏幕社区组。相关的解释、问题和评论,可以参阅GitHub的webscreens/window-segments仓库。

这里提出了一个窗口片段的新概念,它表示位于单独(相邻)显示上的窗口区域(及其尺寸)。窗口片段尺寸以CSS像素表示,并允许Web开发者通过JavaScript API对窗口片段进行枚举,在相应的窗口片段上放置独立内容。

这个建议主要针对响应式场景,在这种情况下,Web应用程序希望利用跨越多个显示的优势,通过用户、窗口管理器将其置于该状态。它不适合将内容预先放置在各种可用显示的单独顶级浏览器上下文的情况(这属于Window Placement APIPresentation API)。请注意,使用枚举窗口片段的API和Web现有的特性,可以使用JavaScript来编写交叉显示和窗口的矩形,同时考虑devicePixelRatio来,计算横跨多个显示的窗口区域。不过,这个并不能正确的处理未来设备形式中存在的极端情况,因此,这个提议试图解决Web开发人员针对或考虑显示内容屏幕作为一个实际的起点。

简单地来说,Web开发者可以使用getWindowSegments()来获取一个DOMRects数组,然后根据每个窗口片段返回的数据,开发人员能够推断出可用链接(Hinge)的数量以及其方向。

用户可以在任何时候将浏览器窗口脱离跨越模式,并将其放在某个屏幕上,反之亦然,在这种情况下,将触发窗口resize事件,从而可以查询并获得可用屏幕段的数量。

比如我们上面提到的示例:

如果使用JavaScript来解决的话,可以像下面这样:

const screenSegments = window.getWindowSegments();

if( screenSegments.length > 1 ) {
    // 现在我们知道这个设备是可折叠(可多屏幕)的
    // 建议测试 screenSegments[0].width === screenSegments[1].width 
    // 可以更新CSS类,实现不同的布局效果

    document.body.classList.add('is-foldable');
    document.querySelector('.map').classList.add('flex-one-half');
    document.querySelector('.locations-list').classList.add('flex-one-half');
}

我们再来看另一个示例,当窗口的resize事件和spanning状态发现变化时处理方案。

先看CSS的解决方案:

@media (spanning: none) and (max-width: 728px) {	
    body {
        flex-direction: column;
    }

    .map {
        flex: 0 0 300px;
    }
    
    .locations-list {
        flex: 1;
    }
}

如果使用JavaScript可以这样:

window.onresize = function() {
    const segments = window.getWindowSegments();
    console.log(segments.length) // 1

    if( screenSegments.length > 1 ) {

        document.body.classList.add('is-foldable');
        document.querySelector('.map').classList.add('flex-one-half');
        document.querySelector('.locations-list').classList.add('flex-one-half');
    }
}

实战:多屏幕布局

最后我们来看实际案例。

你可能在手上没有折叠屏或多屏幕的设备,有可能无法看到实际效果。但可以使用一款基于Web的模拟器,这款模拟器可以模拟微软 Surface DuoSurface Neo两款分屏设备:

@kenchris 在GitHub上spanning-css-polyfill提供了几个Demo,我们来看最简单的basic中的index.html

HTMl部分很简单(这个Demo本身就很简单):

<div class="wrapper">
    <div class="col1"></div>
    <div class="col2"></div>
</div>

</body>前加载了一个.js文件,根据CSS Spanning Polyfill提供的一些方法,处理一些全局样式,甚至可以根据JavaScript的API来构建CSS方面的样式。

<script type="module" src="../../src/index.js"></script>

index.js对应的详细代码可以点击这里查阅

接着看CSS部分。在CSS中使用媒体查询特性:

/* 普通设备下对应的CSS */
.wrapper {
    display: flex;
    height: 100%;
    width: 100%;
}

.col1 {
    flex: 0 0 200px;
    background-color: lightgray;
    transition: background-color .2s linear;
}

.col2 {
    flex: 1;
    background-color: papayawhip;
    transition: background-color .2s linear;
}

.col1:before {
    content: "not spanning";
}
    
.col2:before {
    content: "not spanning";
}

不是分屏(或折叠)设备中,看到的效果像下面这样:

在水平方向分屏状态(即spanning取值为single-fold-vertical)下的CSS:

@media (spanning: single-fold-vertical) {
    .col1 {
        flex: 0 0 env(fold-left);
        margin-right: env(fold-width);
        background-color: steelblue;
    }

    .col2 {
        background-color: yellow;
    }

    .col1:before {
        content: "spanning single-fold-vertical";
    }
    .col2:before {
        content: "spanning single-fold-vertical";
    }
}

注意,示例中还使用了CSS的env()函数,调用了CSS环境变量fold-leftfold-width。这个时候看到的效果如下:

当你把浏览器模式(Browser Mode)切换为“Left”或“Right”时,看到的效果如下:

上图是Surface Duo模拟器,Browser Mode为Left状态效果

上图是Surface Duo模拟器,Browser Mode为Right状态效果

接下来再来设备垂直方向分屏(即spanning取值为single-fold-horizontal)的布局样式:

@media (spanning: single-fold-horizontal) {
    .wrapper {
        flex-direction: column;
    }

    .col1 {
        flex: 0 0 env(fold-top);
        margin-bottom: env(fold-height);
        background-color: pink;
    }

    .col2 {
        background-color: seagreen;
    }

    .col1:before {
        content: "spanning single-fold-horizontal";
    }
    .col2:before {
        content: "spanning single-fold-horizontal";
    }
}

将设备的方向切换到portrait状态下,浏览器在spanning状态下,看到的效果如下:

同样的,如果你把模拟器的浏览器模式切换到"Top"或"Bottom"状态下,看到的效果也会不一样:

最后,你还可以在spanning:none状态下设置相应的CSS:

@media (spanning: none) {
    .col1:before {
      content: "spanning:none";
    }
    .col2:before {
      content: "spanning:none";
    }
}

上面是一个较为简单的示例。那么接下来,我们在上面的基础上把页面做得稍微复杂一点:

<!-- HTML -->
<ul class="card__container">
    <li class="card">
        <div class="card__object"><img src="//source.unsplash.com/300x300?01" alt="Psychopomp"></div>
        <div class="card__body">
            <h4 class="card__title">Psychopomp</h4>
            <p>Japanese Breakfast</p>
        </div>
    </li>
    <!-- ... -->
</ul>

按照上面的示例,在媒体特性之外,给普通设备添加样式(也可以说是全局样式):

/* 布局关键样式 */
body {
    width: 100vw;
    min-height: 100vh;
    margin: 0;
    padding: 2vh;
}

.card__container {
    gap: 2vmin;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    grid-auto-flow: dense;
}

.card:nth-child(1) {
    grid-column: span 2;
}

.card:nth-child(6) {
    grid-column: span 3;
}

普通设备(非折叠和分屏)下效果如下:

特别声明,该示例来自《CSS Grid中的auto-fillauto-fit》一文。这个示例中使用CSS Grid做的响应式布局效果

拖动浏览器时改变视窗大小,看到的效果如下:

在不对spanning媒体特性下做任何样式处理时,在分屏状态下看到的效果如下:

同样的,先对spanning: single-fold-vertical做样式上的处理:

@media (spanning: single-fold-vertical) {
        
    .card__container {
        gap: env(fold-width);
        grid-template-columns: repeat(auto-fit, minmax(calc((env(fold-left) - env(fold-width) - 4vh) / 2), 1fr));
    }

    .card:nth-child(6) {
        grid-column: span 2;
    }
}

这个时候水平分屏状态下的效果如下:

接着来看垂直方向的分屏状态下的效果,先来看未处理时的效果:

接下来在spanning: single-fold-horizontal媒体特性下做一些布局上的处理:

@media (spanning: single-fold-horizontal) {

    .card__container {
        gap: 20px;
        grid-template-columns: 1fr;
        grid-template-rows: calc(env(fold-height) - 4vh);
    }

    .card__object {
        height: 250px;
    }

    .card:nth-child(1) {
        grid-column: span 1;
    }

    .card:nth-child(6) {
        grid-column: span 1;
    }
}

看到的效果如下:

实际操作的时候,应该根据自己的使用场景,然后对应的媒体查询特性中配合CSS本地环境使用,实现不同的效果。

有关于这方面的Demo,还可以查阅:

小结

可折叠Web的出现,让移动优先的设计变得更加复杂,但也更加令人兴奋。可折叠Web可能是第一次手持设备感到空间的扩展而不是限制。对于一些Web应用或Web页面来说,需要做一定的调整,而对于另一些Web应用来说,则意味着需要大规模的重新设计。这个范围取决于开发人员的创新。

参考资料