前言:移动端有非常多的坑,布局首当其冲。
背景
移动端应用有各种复杂的页面需求,不仅要解决单屏、多屏、固定头部或底部等多个场景,还要兼容ios和Android内核,在经历了项目实战(手机模式打开)过后,总结出了一些经验,在这里和大家分享一下。
这篇文章是基于 →
Next轻量级框架与主流工具的整合
最新的代码在这里 →
next-mobile-complete-demo
心路历程
首先,需要实现的首页类似于app应用。
-
第一站:统一flex布局的坑
→ flex教程
首页如上面的图片所示,然后偷了个懒,直接用了antd-mobile的tab标签栏,它需要指定容器高度。于是,在项目最开始的时候直接通过js将容器设置为浏览器html窗口的高度:
// html高度 = body高度 = 主容器高度
doc.body.style.height = docEl.clientHeight + 'px';
这样做也有许多好处:纵向布局十分方便,不管是tab标签栏、tab标签页、头部固定元素或者底部固定元素实现起来都很简单;也能很简单就能实现元素居中对齐、两端对齐、自动分配空间等;同时也避免了Android输入框引起传统布局的问题。
不过这里有个唯一的不好的地方就是兼容不了ios的前进返回的操作栏和上下滚动回弹:
- 不能在滚动时隐藏前进返回的操作栏、
- 在上下滚动时容易触发页面的回弹效果,造成滚动卡顿。
这里补充一下:在ios上如果以正常流布局,内容超过一定高度时,向下滚动会隐藏底部的前进返回栏,向上滚动的时候再显示出来。当滚动到底部或者顶部时,再继续拉动页面,会有一个回弹的效果。
这两个问题极大的降低了ios的用户体验,又恰逢项目间隔期,有大把的时间,于是被推着到了布局优化上面。
第二站:寻找良好用户体验的布局案例
这里我们在寻找的过程中发现两个具有代表性的移动端应用:
-
bilibili的m站、腾讯新闻
它用的是传统式流布局,头部和底部fixed固定,所有的内容全部向下平铺。
-
花瓣网H5
它采用的是设置主容器的position属性为absolute来脱离文档流的形式。
这两种布局方式在ios上都用户体验极好。但同时也发现他们都存在的一些问题,比如:登录页面明明不足一屏,却依然存在滚动;没有兼容iPhone X的安全域;弹窗滚动穿透等等。
如果能将这两种布局和flex进行整合一下,聚合各自的优点,基本上能打造一个用户体验和兼容性都令人满意的移动端应用了。
那么,如何基于现有的flex布局去整合这两种布局又成了一个问题。
第三站:基于flex调整布局结构
在调整之前,我的预期是这样的:去掉外层高度限制,去掉纵向flex布局(除极少数单屏页面),将顶部、底部固定和弹窗设置为fixed。
考虑到少数单屏页面需要继承html窗口的高度,于是采用了主容器脱离文档流的方式。
调整过后dom结构是这样的:
html > body > div#root > div.main-content(position: absolute)
只需要给main-content加上height: 100%
,就可以满足单屏页面的需求。
好事多磨!
按照上面的想法进行改造过后又发现了新的问题:脱离文档流过后,切换页面,html会保持最后滚动的位置。
第四站:滚动条位置、滚动穿透、兼容iPhone X
-
有可能出现滚动条位置被记录的问题
找了一下发现history
有个scrollRestoration
的属性,它有两个值,一个是默认值auto
还有一个是manual
。如果设置为manual
就可以手动设置滚动的位置。if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; }
然后在切换路由过后设置滚动的位置为起始位置:
Router.events.on('routeChangeComplete', () => { document.scrollingElement.scrollTop = 0; });
这样每次切换页面都是从初始位置开始。
开发遇见的问题,这个demo中没有出现。这里有点鸡肋,前进后退也不会记录滚动位置。如果需要前进后退记录滚动的位置,就不能用这种脱离文档流的形式,需要用body滚动的形式,也就是。
-
解决滚动穿透
这个问题,需要针对有滚动的弹窗和无滚动的弹窗单独处理。- 针对无滚动的弹窗,在弹窗弹出的时候禁用touchmove事件,隐藏的时候移除事件监听。
- 有滚动的弹窗,需要找到页面的滚动的容器设置
overflow:hidden
-
兼容iPhone X
在meta标签后增加
viewport-fit=cover
<meta name="viewport" content="initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover"/>
然后留出安全域距离
padding-bottom: 0.49rem; // 底部button的高度 padding-bottom: calc(0.49rem + constant(safe-area-inset-bottom)); padding-bottom: calc(0.49rem + env(safe-area-inset-bottom));
这里需要注意些
padding-bottom
的时候需要写三个,第一个是为了兼容Android。
总结
总的来说flex布局还是带了十分大的便利,尤其是能根治居中、适配等一些老大难的问题。
每种布局的方式都有一定的缺陷,到目前为止还没有一种万能的方案来解决移动端各种复杂的场景。还是需要根据自己的需求还选择使用哪种实现方式。
*前面这三种方案各自的缺陷:
纵向flex布局(不建议使用) | body流式平铺(传统) | absolute脱离文档流(正在使用) |
---|---|---|
ios前进后退的操作栏无法隐藏、回弹效果引起卡顿 | 内部容器无法继承html窗口高度 | 路由跳转可能出现滚动条被记录 |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。