移动端 Modal 组件开发杂谈
Vant 是有赞开发的一套基于 Vue 2.0 的 Mobile 组件库,在开发的过程中也踩了很多坑,今天我们就来聊一聊开发一个移动端 Modal 组件(在有赞该组件被称为 Popup )需要注意的一些坑。
在任何一个合格的UI组件库中,Modal 组件应该是必备的组件之一。它一般用于用户处理事物,但又不希望跳转页面时,可以使用 Modal 在当前页面中打开一个浮层,承载对应的操作。相比PC端,移动端的 Modal 组件坑会更多,比如滚动穿透问题就不像PC端在 body 上添加 overflow: hidden 那么简单。
目录
一、API定义
二、水平垂直居中的方案
三、可恶的滚动穿透
四、position: fixed 失效
一、API定义
任何一个组件开始编码前都需要首先将API先定义好,才好根据API来提供对应的功能。Modal 组件提供了以下API:

更具体的 Api 介绍可以访问该链接查看:Popup
二、水平垂直居中方案
垂直居中的方案网上谷歌一下就能找到很多种,主流的方案有:
- absolute(fixed) + 负边距
- absolute(fixed) + transform
- flex
- table + vertical-align
首先说一下我们选择的是第二种:absolute(fixed) + transform,它是以上方案中最简单最方便的方案,代码实现量也很少。实现代码如下:
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
但是 transform 会导致一个巨大的坑,这个坑的具体细节会在下面的章节中详细讲到。
说完了我们选择的方案,再来说说为啥不选择其他的方案呢?
absolute(fixed) + 负边距
只能适合定高的场景,果断抛弃。如果要实现不定高度就要通过JS来计算了,增加了实现的复杂度。
flex
flex 布局一是在某些老版本的安卓浏览器上还不是很兼容,还有就是需要包裹一个父级才能水平垂直居中。
table + vertical-middle
在 CSS2 时代用这个方案来实现垂直居中是比较常见的方案,不足的地方就是代码实现量相对较大。
三、可恶的滚动穿透
开发过移动端UI组件的都知道,在移动端有个可恶的滚动穿透问题。这个问题可以描述为:在弹窗上滑动会导致下层的页面跟着滚动。
网上谷歌一下滚动穿透关键字其实可以发现很多种解决方案,每个方案也各有优缺点,但我们选择的解决方案是团队的一姐一篇移动端体验优化的博文中得到的启示(博文地址:花式提升移动端交互体验 | TinySymphony)。
具体的思路是:当容器可以滑动时,若已经在顶部,禁止下滑;若在底部,禁止上滑;容器无法滚动时,禁止上下滑。实现的方式就是在 document 上监听 touchstart 和 touchmove 事件,如滑动时,祖先元素并没有可滑动元素,直接阻止冒泡即可;否则判断手指滑动的方向,若向下滑动,判断是否滑动到了滑动元素的底部,若已经到达底部,阻止冒泡,向上滑动也类似。具体的代码实现可以看下面的代码:
const _ = require('src/util')
export default function (option) {
const scrollSelector = option.scroll || '.scroller'
const pos = {
x: 0,
y: 0
}
function stopEvent (e) {
e.preventDefault()
e.stopPropagation()
}
function recordPosition (e) {
pos.x = e.touches[0].clientX
pos.y = e.touches[0].clientY
}
function watchTouchMove (e) {
const target = e.target
const parents = _.parents(target, scrollSelector)
let el = null
if (target.classList.contains(scrollSelector)) el = target
else if (parents.length) el = parents[0]
else return stopEvent(e)
const dx = e.touches[0].clientX - pos.x
const dy = e.touches[0].clientY - pos.y
const direction = dy > 0 ? '10' : '01'
const scrollTop = el.scrollTop
const scrollHeight = el.scrollHeight
const offsetHeight = el.offsetHeight
const isVertical = Math.abs(dx) < Math.abs(dy)
let status = '11'
if (scrollTop === 0) {
status = offsetHeight >= scrollHeight ? '00' : '01'
} else if (scrollTop + offsetHeight >= scrollHeight) {
status = '10'
}
if (status !== '11' && isVertical && !(parseInt(status, 2) & parseInt(direction, 2))) return stopEvent(e)
}
document.addEventListener('touchstart', recordPosition, false)
document.addEventListener('touchmove', watchTouchMove, false)
}
四、position: fixed 失效
在前端工程师的世界观里,position: fixed 一直是相对浏览器视口来定位的。有一天,你在固定定位元素的父元素上应用了 transform 属性,当你刷新浏览器想看看最新的页面效果时,你竟然发现固定定位的元素竟然相对于父元素来定位了。是不是感觉人生观都崩塌了。
这个问题,目前只在Chrome浏览器/FireFox浏览器下有。也有人给 Chrome 提bug:Fixed-position element uses transformed ancestor as the container,但至今尚未解决。
例如下面的代码:
```<style>
body {
padding: 50px;
}
.demo {
background: #ccc;
height: 100px;
transform: scale(1);
}
.fixed-box {
position: fixed;
top: 0;
left: 0;
width: 100px;
height: 100px;
background: red;
}
</style>
<div class="demo">
<div class="fixed-box"></div>
</div>
<p>垂直居中方案 <code>position: fixed + transform</code> 的选择导致了 <code>Modal</code> 组件使用上的一个坑。当我们在 <code>Modal</code> 组件里面嵌套了一个 <code>Modal</code> 时,内层的<code>Modal</code> 就是相对外层的 <code>Modal</code> 来定位,而不是浏览器的 viewport。这也限制了我们 <code>Modal</code> 的使用场景,如果你想实现嵌套的 <code>Modal</code>,就要选择其他的垂直居中方案了,有舍必有得嘛。</p>
<p>关于 <code>position: fixed</code> 失效的更多细节可以参考以下几篇博文:</p>
<ul>
<li><a href="http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/" rel="nofollow noreferrer">Eric’s Archived Thoughts: Un-fixing Fixed Elements with CSS Transforms</a></li>
<li><a href="http://www.zhangxinxu.com/wordpress/2015/05/css3-transform-affect/" rel="nofollow noreferrer">CSS3 transform对普通元素的N多渲染影响</a></li>
</ul>
<h2>总结</h2>
<p>开发组件库不易,开发移动端组件库更不易。移动端组件库相对PC端会有更多的奇葩的坑。当遇到坑,肯定是要选择跨越它,而不是逃避它,因此也才有了我们这篇文章,后续我们也还会有一些介绍 <a href="https://www.youzanyun.com/zanui/vant" rel="nofollow noreferrer">Vant</a> 组件库开发过程中遇到的坑,或者一些优化相关的文章,敬请期待。</p>
<p>如果觉得这篇文章讲的还不够,完整源代码实现请移步Github:<a href="https://github.com/youzan/vant/tree/dev/packages/popup" rel="nofollow noreferrer">popup</a>。</p>
原文地址:
移动端 Modal 组件开发杂谈的更多相关文章
- React-Native 组件开发方法
前言 React Native的开发思路是通过组合各种组件来组织整个App,在大部分情况下通过组合View.Image等几个基础的组件,可以非常方便的实现各种复杂的跨平台组件,不过在需要原生功能支持. ...
- js组件开发-移动端地区选择控件mobile-select-area
移动端地区选择控件mobile-select-area 由于之前的[js开源组件开发]js手机联动选择地区仿ios 开源git 很受欢迎,于是我又对其进行了一些优化,包括可选的范围变大了,添加了默认空 ...
- [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发
通过一个小组件,熟悉 Blazor 服务端组件开发.github 一.环境搭建 vs2019 16.4, asp.net core 3.1 新建 Blazor 应用,选择 asp.net core 3 ...
- KVM&Libvirt基本概念及开发杂谈
导读 大家好,本次肖力分享的主题是KVM&Libvirt基本概念及开发杂谈,内容有些凌乱松散,主要基于自己早期整理的笔记内容和实践感悟,有些内容难免有失偏颇,望见谅.前面先介绍下需要了解的基本 ...
- 移动端报表JS开发示例
最近对移动端的报表开发颇有研究,细磨精算了好久,虽然到现在还是“囊中羞涩”,但决定还是先抛砖引玉,拿点小干货出来和大家分享. 研究的工具是比较有代表性的FineReport. 1. 移动端哪些地方支 ...
- 饿了么基于Vue2.0的通用组件开发之路(分享会记录)
Element:一套通用组件库的开发之路 Element 是由饿了么UED设计.饿了么大前端开发的一套基于 Vue 2.0 的桌面端组件库.今天我们要分享的就是开发 Element 的一些心得. 官网 ...
- 利用bootstrap的modal组件自定义alert,confirm和modal对话框
由于浏览器提供的alert和confirm框体验不好,而且浏览器没有提供一个标准的以对话框的形式显示自定义HTML的弹框函数,所以很多项目都会自定义对话框组件.本篇文章介绍自己在项目中基于bootst ...
- 【2015上半年总结】js开源组件开发系列索引
js开源组件开发系列一索引 2015.8 by 田想兵 个人网站 从3月份进入新公司以来,时经五个月,我以平均每周1个小组件的速度,已经完成的js组件有22个之余了,已基本上全部用到实际项目中,这些小 ...
- Agile.Net 组件式开发平台 - 组件开发示例
所谓组件式开发平台,它所有的功能模块都是以组件的形式扩展的,下面我来演示一个简单的组件开发例程. Agile.Net开发管理平台项目,已经托管在开源中国码云平台(http://git.oschina. ...
随机推荐
- sql-查看执行计划的方法
sql执行计划:把SQL语句拆分为每个的操作步骤组合,按照一定的顺序执行得出结果,查看并看懂执行计划是调优的关键步骤 查看执行计划的方法 DBMS_XPLAN包 sql*plus AUTO trace ...
- Django(part4)
一个简单的form表单: #polls/templates/polls/detail.html<h1>{{ question.question_text }}</h1> {% ...
- 你不知道的JavaScript(三)字符串
JavaScript字符串很容易被认为本质就是字符数组,ECMAScript规范中字符串作为一种单独的string类型,它的底层实现可能是数组,也可能是其他数据结构,因不同的JavaScript引擎而 ...
- (转载)安卓6.0之前的系统 判断app是否有录音权限
卓6.0之前的系统 判断app是否有录音权限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ...
- 【学习】JMS通信模式
1.关于JMS的点对点模式 JMS的点对点模式下,多个消费者可以注册到同一个队列上,但是生产者的某个消息只能被一个消费者接收,在多个消费者间,生产者的消息被多个消费者循环接收,如当前有6个消息在队列中 ...
- 路飞学城Python-Day13
[2.常用模块-模块的种类和导入方法] 1.什么是模块? 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长.越来越不容易维护. 为了编写可维护的代码,我们把很多函数分组,分 ...
- 服务器搭建域控与SQL Server的AlwaysOn环境过程(二) 搭建客户端节点 服务器
1. 修改客户端服务器的计算机名,重启后,如果服务器属于克隆服务器,需要修改服务器SID,如果没有则调过这一步. 命令行方式:启动Windows2008进入系统后,打开“CMD窗口”并进入到" ...
- 页面加载完成触发input[type="file"]控件问题
由于浏览器厂家的限制,不同的浏览器不开放页面加载完成就允许触发input[type="file"]控件 测试 Chrome .火狐 .IE .微信客户端QQ =>桌面端: C ...
- [NOI2014]动物园(KMP)
题意 题解 因为,一直用j=nxt[j]来遍历,可以遍历前i个字符所有相等的前后缀长度,所以有一个暴力的想法,就是对于每一个长度,开始遍历,记录长度小于i/2的相等的前后缀数量,最后累加即可. 但显然 ...
- 经典C语言编程注意点
C/C++程序员应聘试题剖析 分中的2分.读者可从本文看到strcpy函数从2分到10分解答的例子,看看自己属于什么样的层次.此外,还有一些面试题考查面试者敏捷的思维能力. 分析这些面试题,本身包含很 ...