适配这件事被重新提出,不是因为层叠样式表又添了新语法,而是因为”同一档宽度对应同一种布局”这个前提没了。窄长高屏把可用高度压到很紧,阔屏与小屏同时存在,折叠形态和平板横屏让窗口比例在一个连续区间里变动。于是同一份页面在两台都算”窄屏”的设备上表现完全不同,而差别来源根本不是宽度那一栏。

我们后来把验收标准的写法换掉了:不按机型写,按维度写。维度是逻辑宽度、宽高比、可用高度、系统字号档位、深浅色模式、字重、安全区与开孔这几条。每条维度给出取值区间,测试矩阵就是它们的组合。组合不需要全部跑完,但必须能说清楚漏掉了哪一格、那一格的风险归谁。

按维度组织的适配矩阵:宽度、宽高比、字号档位与屏幕开孔共同决定布局分支

一、决定布局分支的是宽高比,不是宽度

最容易被忽略的一条是可用高度。同一个逻辑宽度下,偏窄长的屏幕与接近方形的屏幕,纵向能放的内容条目差得很远。首屏能看到几条列表、吸底操作栏会不会把最后一条盖住、长表单是否需要滚动才能到提交按钮,这三件事都由高度决定,而我们的样式表里几乎只按宽度写分支。

处理方式是让高度成为一级变量。媒体查询里 aspect-ratio 与 orientation 可以按比值分支,比只按宽度分支更接近真实;更稳的做法是别让布局依赖”屏高多少”,而是依赖”这块区域能长多高”——用弹性轨道把可变部分交给内容,首屏条目数由容器高度决定而不是固定数值。

移动浏览器还有一层麻烦:工具栏会随滚动收放,100vh 不等于用户实际看到的可视高度。吸底栏用 100vh 定位时,常被工具栏压住一条边,或在两者之间留出一条穿帮的缝。相对可用的写法是动态视口单位配合安全区内边距,需要精确时读 visualViewport 的高度与偏移,写进自定义属性再由样式消费。软键盘弹出后底部内容被遮挡,不同浏览器对”键盘是否压缩可视区域”的默认行为不一致,凡是底部有输入框的流程都要实测,不能当作已统一的能力。

二、设备白名单换成布局约束

按机型或厂商做白名单,问题不在于判断不准,而在于它描述的是”设备是什么”,不是”容器有多大”。新形态出现的那天,白名单就得回归一次;同一台设备分屏、半屏、悬浮窗状态下,白名单还会给出错误答案。

替代手段是把判断权交给约束。

  • 容器查询让组件按自己所在容器的行内尺寸分支。卡片放进主栏还是侧栏,由容器宽度决定,同一份组件代码不用为页面位置各写一套断点。
  • clamp() 与 min()/max() 把字号、间距、区域高度写成随视口连续变化的函数,控制增长斜率而不是跳档。斜率两端要用最小值和最大值收住,否则极窄时文字小到不可读,极宽时段落行长失控。
  • 栅格轨道用自动填充加最小轨道宽度,列数由可用宽度算出来。这样列数永远不为零,也不需要为某个具体比例补一条分支。
  • 换行与间距交给弹性布局和 gap,靠 flex-wrap 让内容自己找行,比手写”低于某个宽度时改为竖排”少一条判断。

断点本身要当成成本记账。每加一个断点,就多一条需要维护的分支、多一套截图基线、多一处只在特定宽度复现的缺陷。只在信息密度或操作位置真的改变时才加分支,其余情况让它连续变化。判断逻辑尽量留在样式层,确实需要测量时用观察器回调,别把宽度数值烧进脚本常量。

三、单手可达区与底部操作栏

窄长屏带来的不是”屏幕变大”,而是拇指可达范围相对屏幕变小。拇指自然张开时的活动范围是一个弧,弧的右下部分是热区,屏幕左上角是冷区。把主操作放在顶部或屏幕角落,在窄长屏上就是让用户体验一次够不着。

阔屏的反向问题更隐蔽。为了利用宽度,常规做法是从单列改成左右分栏,结果主行动按钮被推到左栏顶部或右栏边角,正好落进拇指死角;更糟的是左右手用户的可达区是镜像的,固定放在任何一侧都有一半用户不方便。分栏本身不是问题,把工具栏做成一整条通栏、所有行动入口都在上方,才是问题。

对应的做法:主操作收进吸底操作条,并在条内加上安全区内边距,避免被底部手势区吃掉一半高度;每个分栏保留自己的底部入口,而不是共用顶部通栏;次级功能放顶部图标,接受它们需要另一只手;滑动与长按操作避开屏幕左右边缘,让出系统手势的起始区域;命中区域按下限设计而不是按图形大小,相邻按钮之间留可分辨的间隔;只在真正支持悬停的设备上依赖悬停提示,触屏上这类信息永远不出现。

四、字号缩放、粗体、深色模式三个变量

这三个变量都不改变宽度断点,却能单独撑破一套调好的布局,而它们大多是用户在系统设置里主动开的,不是异常状态。

字号:系统字号调大后会同时影响原生与网页文本。行高随之变长,写死高度的按钮和标签开始裁字;标题从两行变三行,把整块内容顶出可视区;带行数上限的截断在高档位把关键信息吃干净。对策是把高度还给内容——用最小高度而不是固定高度,文本行高用相对单位,字号基准放在根元素上以便整体缩放,截断处保留展开入口。

字重:系统的加粗文本设置会把整站字重上调,行长增加几个百分点,恰好触发换行或溢出。薄弱点集中在单行标题、表格列头、按钮文案和带图标的标签。表格里给列设最小宽度并允许横向滚动,比强迫单行显示更稳;按钮文案要允许两行或按内容伸缩。

深浅色:除了跟随偏好切换配色,还要声明表单控件的颜色方案,否则原生输入框、滚动条和下拉列表仍保持亮色底。带白底的截图与扫描件在深色界面上刺眼,需要容器化的降亮策略而不是直接显示。半透明描边与浅灰分割线在深色下几乎不可见,层级关系不能只靠阴影表达,深色要靠描边或背景明度差。

测试办法是固定组合而不是随机点测:三个字号档位、两种颜色模式、两档字重,乘上两到三个宽高比,只挑五到八个信息密度最高的页面跑截图比对。表单页、单证列表页、详情页通常就是最脆弱的那几个。

五、开孔、圆角、手势区与媒体裁切

屏幕开孔、圆角与系统手势是三类遮挡,共同点是它们不出现在可视尺寸里,只能靠运行时环境变量取到。

顶部一行内容在开孔区域被切掉是最常见的现象,尤其当开孔偏在一侧时,居中的标题会被啃掉半个字。圆角会把贴角的点击目标裁掉一角,右上角的关闭按钮看起来还在,但命中区域缺了外侧那块。系统侧滑返回占用屏幕左右边缘的一条窄带,页面里自绘的左滑删除、抽屉和返回按钮会和它抢同一片区域。

对策并不复杂:顶部固定条带按安全区补内边距,并且不拿整行高亮当点击区;贴角控件离角至少一个安全区的距离;左边缘的滑动操作把起始判定从边缘向内让开;不要在开孔区域放任何不可丢失的信息,包括倒计时和状态徽标。

媒体在比例差异下的策略要写成产品规则,而不是让每个页面各自决定。给图片容器声明宽高比,既占位又避免内容回来后重排;object-fit 的覆盖模式是裁切而非缩放,主体在画面里的位置要靠定位属性显式约定,构图敏感的图(含人脸、含表格左上角、含印章)更适合等比容纳或按比例出多套图。需要服务端出图时,按设备像素比与目标比例生成,前端只负责声明需求,不要靠拉伸补尺寸。同一张横图在竖屏卡片里保留哪一块,这是内容规则,写进组件文档,比在样式表里散落十来个不同的定位值好维护。

六、按维度写验收而不是按机型:洹拓视角

真机数量永远不够,所以顺序要反过来:先用便宜的虚拟组合覆盖面,再用少量真机覆盖极值,最后把人工走查留给截图比不出来的项。

形态维度 应对手段 出问题时用户看到什么
逻辑宽度 自动填充栅格、容器查询 列数挤爆或只剩一列,横向出现滚动条
宽高比与可用高度 按比值分支、弹性轨道、动态视口单位 首屏只有一条内容,提交按钮看不见
工具栏收放 动态视口单位、可视区域 API 吸底栏被压住一条边或露出缝隙
系统字号档位 最小高度、相对行高、可展开截断 按钮里的字被切半,标题吃掉整屏
系统加粗 列最小宽度、按钮按内容伸缩 文案换行撑破行、表头错位
深浅色模式 颜色方案声明、描边分层、降亮容器 输入框仍是亮色底,分割线消失
安全区与开孔 环境变量内边距、贴角避让 标题被啃半个字,关闭按钮点不中
边缘手势 让出起始区、改长按或右侧滑动 侧滑返回与滑动删除互相打架
媒体比例 宽高比占位、按 DPR 与比例出图 图被裁掉主体或出现拉伸变形

组织顺序上,我们固定六步。

  1. 把维度与取值区间写进测试计划。宽度取几档、比例取几档、字号取几档、深浅与加粗各两档,这张表评审时就是风险清单。
  2. 先跑全虚拟组合。浏览器与模拟器改视口很快,截图比对覆盖全部宽度与比例交叉,这一步不需要真机。
  3. 真机只买形态代表,各代表一个维度极值:一台窄长的、一台偏阔或可折叠半屏的、一台性能与字重基线偏低的低端设备。数量不足要写明它们各自代表哪条维度的边界。
  4. 关键流程人工走查,只查截图查不出的四项:单手可达性、键盘弹起遮挡、深色下的对比度、截断后的可读性。
  5. 缺陷归因回写维度表。每条问题标注它落在哪一格,长期反复出问题的格子,说明那里还锁在媒体查询里,应该改成容器或流式写法。
  6. 交付文档写维度声明,不写机型清单。写”在上述区间内保证首屏条目数与操作可达”,机型清单会随一次上新作废,维度不会。

洹拓视角:企业应用与活动页交付里,返工最多的场景是甲方把一台设备当成全部验收条件,换一台就重开一轮。把验收条款按维度写,两边都省事——需求方拿到的是可判定的边界,交付方拿到的是不必反复自证的清单。形态碎片化不会收敛,能收敛的只有我们描述它的方式。