在微信裏面瀏覽頁面的時候,有一個很管用的方法能夠區分這個頁面是原生的仍是H5形式的。隨便打開一個頁面,用力往下扯的時候,若是頁面上方出現了「黑底」,黑底上有一行諸如網頁由game.weixin.qq.com提供
的文字,就代表這個頁面是H5形式的。這帶來的問題是,若是一個頁面可滾動區域很小,隨便一拉,頁面下方出現了黑底,而後你又輕輕往上一拉,上面的黑底又出來了,我的表示很是難受啊!
因而乎,折騰了一番,寫了一個簡單的組件來實現禁止這種拉動頁面出現黑底的特性。php
首先須要說明的是,因爲Android和IOS的webview存在差別,這個組件對於IOS是比較友好的,安卓下並不能作到完美避免,下面一一分析。node
智能手機和平板電腦一類的移動設備一般會有一個電容式觸摸屏(capacitive touch-sensitive screen),以捕捉用戶的手指所作的交互。有三種在規範中列出並得到跨移動設備普遍實現的基本觸摸事件:git
touchstart :手指放在一個DOM元素上github
touchmove :手指拖曳一個DOM元素web
touchend :手指從一個DOM元素上移開chrome
其中每個觸摸事件都會包含三個觸摸列表:移動web開發
touches :當前位於屏幕上的全部手指的一個列表。瀏覽器
targetTouches :位於當前DOM元素上的手指的一個列表。微信
changedTouches :涉及當前事件的手指的一個列表。session
這些列表由包含了觸摸信息的對象組成:
identifier :一個數值,惟一標識觸摸會話(touch session)中的當前手指。
target :DOM元素,是動做所針對的目標。
客戶/頁面/屏幕座標 :動做在屏幕上發生的位置。
半徑座標和 rotationAngle :畫出大約至關於手指形狀的橢圓形。
在jsfiddle裏面寫一個簡單的小demo就一目瞭然了:
http://jsfiddle.net/yuanzm/ws9j4v1v/2/
在這個組件中,咱們只須要用到e.touches[0].clientY
屬性就夠了:在開始觸摸的時候,記錄觸摸點的起始位置,在手指移動過程當中,不斷獲取最新的clientY
,與起始位置的clientY
比較,就能獲知拉動頁面的方向。
scrollHeight: 是計量元素內容高度的只讀屬性,包括overflow樣式屬性致使的視圖中不可見內容。沒有垂直滾動條的狀況下,scrollHeight值與元素視圖填充全部內容所須要的最小值clientHeight相同。包括元素的padding,但不包括元素的margin.
offsetHeight:是一個只讀屬性,它返回該元素的像素高度,高度包含該元素的垂直內邊距和邊框,且是一個整數。
scrollTop:設置獲取讀取元素向上滾動了多少像素。對於可滾動的元素,這個值是可見區域頂部和不可見區域頂部的距離。若是元素不能滾動,這個值默認爲0。
這三個屬性是用來計算元素處於頁面的哪一個位置的,考慮下面兩種狀況:
元素的offsetHeight大於等於scrollHeight,無縱向滾動條出現,這個元素是不能滾動的。若是一個元素不能滾動了,就會嘗試外層的元素能不能滾動,一層一層往外冒泡。在webview裏面,最外面一層就是這個webview容器了,按照微信的設置,這一層的「滾動」就是露出下面的黑底。因此爲了不露出黑底,咱們要在當前元素不能滾動的時候及時禁止掉冒泡,這樣就不會觸發到上一層的滾動。
若是一個元素設置了高度,而且設置了overflow: scroll,當元素內的內容可滾動的時候,scrollHeight的值就會明顯大於offsetheight,那咱們怎麼判斷元素內的內容下拉到底部了呢?這就須要綜合offsetHeight和scrollTop的值了,若是offsetHeight的值加上srcollTop的值大於等於scrollHeight的值,就代表內容已經滑動底部了。和第一點同樣,當咱們知道了臨界條件後,及時阻止掉冒泡就ok了。
經過上面兩點,咱們已經知道要達到禁止出現黑底的效果,努力的方向是在知道滑動方向的條件下,在與height相關的屬性達到臨界值的時候及時阻止事件冒泡。只有三種簡單的狀況:
(內容)向下拉到底部,不能往下拉,可是能夠往上拉
(內容)向上拉到頂部,不能往上拉,可是能夠往下拉
(內容)既不能往下拉也不能往下拉
總結起來以下表(1爲容許,0爲禁止,高位表示向上方向,低位表示向下方向)
能夠拉的方向(height) | 拉的方向(touch) | 可否繼續拉 |
---|---|---|
00 | 10 | 0 |
00 | 01 | 0 |
01 | 10 | 0 |
01 | 01 | 1 |
10 | 10 | 1 |
10 | 01 | 0 |
從表中咱們能夠得出一個結論是,可否在該方向上繼續拉其實就是對兩種條件作一個&
運算!話很少說,上核心源碼
// 防止過度拉動 preventMove: function(e) { // 高位表示向上滾動, 底位表示向下滾動: 1允許 0禁止 var status = '11', e = e || window.event, // 使用 || 運算取得event對象 ele = this, currentY = e.touches[0].clientY, startY = startMoveYmap[ele.id], scrollTop = ele.scrollTop, offsetHeight = ele.offsetHeight, scrollHeight = ele.scrollHeight; if (scrollTop === 0) { // 若是內容小於容器則同時禁止上下滾動 status = offsetHeight >= scrollHeight ? '00' : '01'; } else if (scrollTop + offsetHeight >= scrollHeight) { // 已經滾到底部了只能向上滾動 status = '10'; } if (status != '11') { // 判斷當前的滾動方向 var direction = currentY - startY > 0 ? '10' : '01'; // console.log(direction); // 操做方向和當前容許狀態求與運算,運算結果爲0,就說明不容許該方向滾動,則禁止默認事件,阻止滾動 if (!(parseInt(status, 2) & parseInt(direction, 2))) { e.preventDefault(); e.stopPropagation(); return; } } },
開始的時候,我覺得上面的代碼就萬事大吉了,通過實踐和摸索,結論是:簡直是天真。
異步的概念之因此首先在Web2.0中火起來,是由於在瀏覽器中JavaScript在單線程上執行,並且它還與UI渲染共用一個UI線程。這意味着JavaScript在執行的時候UI渲染和響應是處於停滯狀態的。 ----《深刻淺出nodejs》
這意味這什麼呢?當咱們的UI線程在進行渲染的時候,JavaScript代碼也是處於停滯狀態的!不信的話能夠在一個能夠滑動的頁面上引入下面這段代碼:
var count = 0; setInterval(functiong() { console.log(++count); }, 100);
刷新頁面的時候,控制檯會一直打印不斷變大的數字,可是隻要你用手指開始拖動頁面,打印終止,等你把手放開的時候,打印繼續,並且數字會承接打印中止前那個數字。也就是UI在渲染的時候,js保存了狀態,在UI渲染中止的時候,js又能夠繼續運行。
這對咱們的組件帶來的影響是什麼呢?幾乎是毀滅性的,場景以下:
若是頁面內容不足一屏,按照組件的設定,既不能上拉也不能下拉,這種狀況不會受影響。
若是頁面內容多於一屏,按照組件的設定,這時候能夠往下拉不能往上拉,在嘗試上拉的時候,組件會阻止冒泡。但若是先下拉一點而後使勁往上拉,原本拉到頂以後組件會阻止事件冒泡,可是一旦下拉以後,線程就歸屬於UI了,上拉的過程當中組件的判斷徹底插不進手,仍是無情漏出了黑底!GG!
在尋求最終的解決方案以前,咱們先來討論一下overflow這個屬性。
傳統 pc 端中,子容器高度超出父容器高度,一般使用 overflow:auto 可出現滾動條拖動顯示溢出的內容,而移動web開發中,因爲瀏覽器廠商的系統不一樣、版本不一樣,致使有部分機型不支持對彈性滾動,從而在開發中製造了所謂的 BUG。
從本人這兩個月移動Web實踐的經驗來看,微信的webview裏面overflow: scroll
和overflow: auto
的滑動效果不管是在安卓仍是IOS下的體驗都很通常,有明顯的卡頓現象,在安卓下面還會出現滑動過快的時候在頁面停下來以後滾動條才閃到相應位置的現象。
在IOS5以後,出現了一個新的屬性: -webkit-overflow-scrolling
,用來控制元素在移動設備上是否使用滾動回彈效果。它的取值有兩個:
auto:使用普通滾動, 當手指從觸摸屏上移開,滾動會當即中止。
touch:使用具備回彈效果的滾動, 當手指從觸摸屏上移開,內容會繼續保持一段時間的滾動效果。繼續滾動的速度和持續的時間和滾動手勢的強烈程度成正比。同時也會建立一個新的堆棧上下文。
實驗代表,在IOS下,對一個元素設置了overflow:scroll
的基礎上再添加-webkit-overflow-scrolling: touch;
會讓滑動又如絲般順滑。
這個屬性和咱們解決以前的問題有什麼聯繫呢?祕密就在這彈性滾動效果。
頁面中body
元素的內容超過一屏,頁面能夠往下滑動(手指往上拉)。按照咱們組件的設定,手指開始的時候是不能往下拉的,可是若是手指的方向是先往上拉一小段,在手指不離開屏幕的基礎上再往下拉,當頁面拉到頂部的時候,會相繼出現黑底,由於UI在渲染,js無法去阻止事件冒泡。
如今咱們把組件的做用元素設定爲body內最外圍的div元素,而且給這個元素添加兩個CSS屬性overflow:scroll
和-webkit-overflow-scrolling: touch;
,那麼上面的場景就會變成:
頁面中body內最外圍的div標籤內容超過一屏,其內容能夠往下滑動(手指往上拉)。按照咱們組件的設定,手指開始的時候是不能往下拉的。和以前同樣,手指先往上拉一小段,在手指不離開該元素的基礎上再往下拉,當元素內容到頂以後,由於UI在渲染,js本插不上手,可是該元素內部的內容設置了彈性滾動,要實現彈性滾動,基本要求就是這個div容器是不動的,能夠理解成由於彈性滾動,自動就禁止掉了事件冒泡,也就不會出現黑底了。
確定有人要問了,既然自動禁止了事件冒泡,那還要這個組件何用?固然有用,會禁止掉事件冒泡的前提是內容在滾動。依照上面的場景,若是一開始手指直接往下拉,沒有組件的限制,仍是會露出黑底,於是,要實現比較好的效果,是須要這兩個屬性和組件配合的。
至於安卓嘛,由於沒有這個屬性,暫時只能一邊涼快去吧。
多說無用,看源碼吧:
https://github.com/yuanzm/preventoverscrolljs