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。
本文首發於有贊技術博客。