Vant 是有赞开发的一套基于 Vue 2.0
的 Mobile
组件库,在开发的过程当中也踩了不少坑,今天咱们就来聊一聊开发一个移动端 Modal 组件(在有赞该组件被称为 Popup
)须要注意的一些坑
。css
在任何一个合格的UI组件库中,Modal
组件应该是必备的组件之一。它通常用于用户处理事物,但又不但愿跳转页面时,可使用 Modal
在当前页面中打开一个浮层,承载对应的操做。相比PC端,移动端的 Modal
组件坑会更多,好比滚动穿透问题就不像PC端在 body
上添加 overflow: hidden
那么简单。html
1、API定义
2、水平垂直居中的方案
3、可恶的滚动穿透
4、position: fixed
失效前端
任何一个组件开始编码前都须要首先将API先定义好,才好根据API来提供对应的功能。Modal
组件提供了如下API:css3
更具体的 Api 介绍能够访问该连接查看:Popupgit
垂直居中的方案网上谷歌一下就能找到不少种,主流的方案有:github
首先说一下咱们选择的是第二种:absolute(fixed) + transform
,它是以上方案中最简单最方便的方案,代码实现量也不多。实现代码以下:web
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
复制代码
可是 transform
会致使一个巨大的坑
,这个坑
的具体细节会在下面的章节中详细讲到。浏览器
说完了咱们选择的方案,再来讲说为啥不选择其余的方案呢?前端工程师
absolute(fixed) + 负边距wordpress
只能适合定高的场景,果断抛弃。若是要实现不定高度就要经过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>
复制代码
垂直居中方案 position: fixed + transform
的选择致使了 Modal
组件使用上的一个坑。当咱们在 Modal
组件里面嵌套了一个 Modal
时,内层的Modal
就是相对外层的 Modal
来定位,而不是浏览器的 viewport。这也限制了咱们 Modal
的使用场景,若是你想实现嵌套的 Modal
,就要选择其余的垂直居中方案了,有舍必有得嘛。
关于 position: fixed
失效的更多细节能够参考如下几篇博文:
开发组件库不易,开发移动端组件库更不易。移动端组件库相对PC端会有更多的奇葩的坑。当遇到坑,确定是要选择跨越它,而不是逃避它,所以也才有了咱们这篇文章,后续咱们也还会有一些介绍 Vant 组件库开发过程当中遇到的坑,或者一些优化相关的文章,敬请期待。
若是以为这篇文章讲的还不够,完整源代码实现请移步Github:popup。
本文首发于有赞技术博客。