摘要:網(wǎng)上谷歌一下滾動穿透關(guān)鍵字其實可以發(fā)現(xiàn)很多種解決方案,每個方案也各有優(yōu)缺點,但我們選擇的解決方案是團(tuán)隊的一姐一篇移動端體驗優(yōu)化的博文中得到的啟示博文地址花式提升移動端交互體驗。
Vant 是有贊開發(fā)的一套基于 Vue 2.0 的 Mobile 組件庫,在開發(fā)的過程中也踩了很多坑,今天我們就來聊一聊開發(fā)一個移動端 Modal 組件(在有贊該組件被稱為 Popup )需要注意的一些坑。
在任何一個合格的UI組件庫中,Modal 組件應(yīng)該是必備的組件之一。它一般用于用戶處理事物,但又不希望跳轉(zhuǎn)頁面時,可以使用 Modal 在當(dāng)前頁面中打開一個浮層,承載對應(yīng)的操作。相比PC端,移動端的 Modal 組件坑會更多,比如滾動穿透問題就不像PC端在 body 上添加 overflow: hidden 那么簡單。
目錄一、API定義
二、水平垂直居中的方案
三、可惡的滾動穿透
四、position: fixed 失效
任何一個組件開始編碼前都需要首先將API先定義好,才好根據(jù)API來提供對應(yīng)的功能。Modal 組件提供了以下API:
更具體的 Api 介紹可以訪問該鏈接查看:Popup
二、水平垂直居中方案垂直居中的方案網(wǎng)上谷歌一下就能找到很多種,主流的方案有:
absolute(fixed) + 負(fù)邊距
absolute(fixed) + transform
flex
table + vertical-align
首先說一下我們選擇的是第二種:absolute(fixed) + transform,它是以上方案中最簡單最方便的方案,代碼實現(xiàn)量也很少。實現(xiàn)代碼如下:
.modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); }
但是 transform 會導(dǎo)致一個巨大的坑,這個坑的具體細(xì)節(jié)會在下面的章節(jié)中詳細(xì)講到。
說完了我們選擇的方案,再來說說為啥不選擇其他的方案呢?
absolute(fixed) + 負(fù)邊距
只能適合定高的場景,果斷拋棄。如果要實現(xiàn)不定高度就要通過JS來計算了,增加了實現(xiàn)的復(fù)雜度。
flex
flex 布局一是在某些老版本的安卓瀏覽器上還不是很兼容,還有就是需要包裹一個父級才能水平垂直居中。
table + vertical-middle
在 CSS2 時代用這個方案來實現(xiàn)垂直居中是比較常見的方案,不足的地方就是代碼實現(xiàn)量相對較大。
三、可惡的滾動穿透開發(fā)過移動端UI組件的都知道,在移動端有個可惡的滾動穿透問題。這個問題可以描述為:在彈窗上滑動會導(dǎo)致下層的頁面跟著滾動。
網(wǎng)上谷歌一下滾動穿透關(guān)鍵字其實可以發(fā)現(xiàn)很多種解決方案,每個方案也各有優(yōu)缺點,但我們選擇的解決方案是團(tuán)隊的一姐一篇移動端體驗優(yōu)化的博文中得到的啟示(博文地址:花式提升移動端交互體驗 | TinySymphony)。
具體的思路是:當(dāng)容器可以滑動時,若已經(jīng)在頂部,禁止下滑;若在底部,禁止上滑;容器無法滾動時,禁止上下滑。實現(xiàn)的方式就是在 document 上監(jiān)聽 touchstart 和 touchmove 事件,如滑動時,祖先元素并沒有可滑動元素,直接阻止冒泡即可;否則判斷手指滑動的方向,若向下滑動,判斷是否滑動到了滑動元素的底部,若已經(jīng)到達(dá)底部,阻止冒泡,向上滑動也類似。具體的代碼實現(xiàn)可以看下面的代碼:
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 一直是相對瀏覽器視口來定位的。有一天,你在固定定位元素的父元素上應(yīng)用了 transform 屬性,當(dāng)你刷新瀏覽器想看看最新的頁面效果時,你竟然發(fā)現(xiàn)固定定位的元素竟然相對于父元素來定位了。是不是感覺人生觀都崩塌了。
這個問題,目前只在Chrome瀏覽器/FireFox瀏覽器下有。也有人給 Chrome 提bug:Fixed-position element uses transformed ancestor as the container,但至今尚未解決。
例如下面的代碼:
垂直居中方案 position: fixed + transform 的選擇導(dǎo)致了 Modal 組件使用上的一個坑。當(dāng)我們在 Modal 組件里面嵌套了一個 Modal 時,內(nèi)層的Modal 就是相對外層的 Modal 來定位,而不是瀏覽器的 viewport。這也限制了我們 Modal 的使用場景,如果你想實現(xiàn)嵌套的 Modal,就要選擇其他的垂直居中方案了,有舍必有得嘛。
關(guān)于 position: fixed 失效的更多細(xì)節(jié)可以參考以下幾篇博文:
Eric’s Archived Thoughts: Un-fixing Fixed Elements with CSS Transforms
CSS3 transform對普通元素的N多渲染影響
總結(jié)開發(fā)組件庫不易,開發(fā)移動端組件庫更不易。移動端組件庫相對PC端會有更多的奇葩的坑。當(dāng)遇到坑,肯定是要選擇跨越它,而不是逃避它,因此也才有了我們這篇文章,后續(xù)我們也還會有一些介紹 Vant 組件庫開發(fā)過程中遇到的坑,或者一些優(yōu)化相關(guān)的文章,敬請期待。
如果覺得這篇文章講的還不夠,完整源代碼實現(xiàn)請移步Github:popup。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/92268.html
摘要:前文上篇中篇地址現(xiàn)在只剩下把東西展示出來了頁面這里有四種頁面其實是四個組件文章,雜談,收藏,具體的文章或雜談前三個雖然布局一樣,但功能有細(xì)微差別,同時考慮到以后可能要針對不同種類做不同的布局方法我還是定義了三個組件以及具體的那個可以看到它們 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......
摘要:前文上篇中篇地址現(xiàn)在只剩下把東西展示出來了頁面這里有四種頁面其實是四個組件文章,雜談,收藏,具體的文章或雜談前三個雖然布局一樣,但功能有細(xì)微差別,同時考慮到以后可能要針對不同種類做不同的布局方法我還是定義了三個組件以及具體的那個可以看到它們 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......
摘要:前文上篇中篇地址現(xiàn)在只剩下把東西展示出來了頁面這里有四種頁面其實是四個組件文章,雜談,收藏,具體的文章或雜談前三個雖然布局一樣,但功能有細(xì)微差別,同時考慮到以后可能要針對不同種類做不同的布局方法我還是定義了三個組件以及具體的那個可以看到它們 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......
摘要:前文上篇中篇地址現(xiàn)在只剩下把東西展示出來了頁面這里有四種頁面其實是四個組件文章,雜談,收藏,具體的文章或雜談前三個雖然布局一樣,但功能有細(xì)微差別,同時考慮到以后可能要針對不同種類做不同的布局方法我還是定義了三個組件以及具體的那個可以看到它們 前文 上篇:https://segmentfault.com/a/11...中篇:https://segmentfault.com/a/11......
摘要:取消確認(rèn)現(xiàn)在彈窗組件的結(jié)構(gòu)已經(jīng)搭建出來了。另外還有兩個方法,分別是點擊取消和確認(rèn)的回調(diào)函數(shù),它們的作用是觸發(fā)對應(yīng)的事件。到這里,一個簡單的彈窗組件已經(jīng)完成了樣式后面再說。一個彈窗組件的拖拽一般通過三個事件來控制,分別是。 更多文章 一個彈窗組件通常包含兩個部分,分別是遮罩層和內(nèi)容層。 遮罩層是背景層,一般是半透明或不透明的黑色。 內(nèi)容層是放我們要展示的內(nèi)容的容器。 ...
閱讀 1446·2021-11-24 09:39
閱讀 3626·2021-09-29 09:47
閱讀 1571·2021-09-29 09:34
閱讀 3067·2021-09-10 10:51
閱讀 2536·2019-08-30 15:54
閱讀 3216·2019-08-30 15:54
閱讀 869·2019-08-30 11:07
閱讀 1004·2019-08-29 18:36