Vue側(cè)滑菜單組件——DrawerLayout
本文介紹一個簡單的DrawerLayout(類似Android的DrawerLayout)布局組件的實現(xiàn),基于Vue.js。介紹的內(nèi)容已經(jīng)制作成 vue-drawer-layout 組件。
前言
大家有興趣先用手機掃一掃這個二維碼,或者點我
然后點擊頁面中左上角的頭像打開drawer或者向右向左拖拽,就可以看到下面gif的效果,打開自己的手機QQ,是不是很像:)
谷歌官方把這種布局叫做DrawerLayout(抽屜式導(dǎo)航欄)。那么我們要如何實現(xiàn)呢,好了正片開始!
HTML結(jié)構(gòu)
頁面結(jié)構(gòu)很簡單,一個抽屜,一個主容器,內(nèi)容可以利用slot支持外部自行定制。
<div class="drawer-layout"> <!--抽屜--> <div class="drawer-wrap"> <slot name="drawer"></slot> </div> <!--主容器--> <div class="content-wrap"> <!--遮罩--> <div class="drawer-mask"></div> <slot name="content"></slot> </div> </div>
抽屜一開始是隱藏在左側(cè)屏幕外的,故設(shè)置 left:-100% 使其整個都藏在外部
使用Touch
首先,判斷瀏覽器是否支持 touchEvent
let isTouch = 'ontouchstart' in window; let mouseEvents = isTouch ? { down: 'touchstart', move: 'touchmove', up: 'touchend', over: 'touchstart', out: 'touchend' } : { down: 'mousedown', move: 'mousemove', up: 'mouseup', over: 'mouseover', out: 'mouseout' };
綁定 touchdown 事件
document.addEventListener(mouseEvents.down, initDrag, false);
先定義一些變量,手指按下的x坐標記為 startX ,滑動中手指的位置x坐標記為 nowX ,drawer的x坐標偏移量記為 startPos
let startX, nowX, startPos;
觸發(fā) touchstart 時,記錄起始位置并綁定 touchmove ,注意:如果是 mouseEvent ,通過 e.clientX 來獲取當(dāng)前的x坐標,如果是 touchEvent ,要通過 e.changedTouches[0].clientX 來獲取x坐標
const initDrag = function (e) { startX = e.clientX || e.changedTouches[0].clientX; //記錄手指按下的位置 startPos = this.pos; //記錄drawer的上次位置 document.addEventListener(mouseEvents.move, drag, false); document.addEventListener(mouseEvents.up, removeDrag, false); }.bind(this); const drag = function (e) { nowX = e.clientX || e.changedTouches[0].clientX; //滑動中手指的位置x坐標 let pos = startPos + nowX - startX; pos = Math.min(width, pos); //不能超過滑動最大值 pos = Math.max(0, pos); //不能小于0 this.pos = pos; //設(shè)置滾動距離為拖動的距離 }.bind(this);
那么,手指滑動的距離就是 nowX - startX ,當(dāng)前drawer的位置為 startPos + nowX - startX ,這樣抽屜已經(jīng)跟隨手指向右移動了,并且不會超過我們設(shè)置的拖動最大值。
區(qū)分垂直滑動和水平滑動
接下來你會發(fā)現(xiàn)一個問題,當(dāng)手指垂直滾動主內(nèi)容時,向右滑動手指也會拖出抽屜,這時應(yīng)該做一件事:區(qū)分垂直滑動和水平滑動
當(dāng)然,辦法有很多,這里先介紹一種利用三角函數(shù)來判定的方法
假設(shè),上圖中的每個箭頭是手指滑動的方向,綠色箭頭代表可以拖出抽屜,紅色箭頭代表不可以拖出(注意,紅色箭頭也是有x坐標的偏移量的)。即當(dāng)不可以拖出抽屜時,應(yīng)觸發(fā)默認事件,比如垂直方向的滾動等等。
當(dāng)手指按下觸發(fā) touchstart 時,記錄初始位置P 0 ;當(dāng)滑動手指時,觸發(fā)的第一次 touchmove 時,記錄位置P 1 ,我們將P 0 到P 1 的矢量記為S(原諒我這個靈魂畫手)
這時候很容易看出,∠θ大于某個值時,比如30度,就可能是垂直方向的滾動操作而不是拖動抽屜。所以,可以根據(jù) y/x>tan30°
得到判斷條件:
if (isVerticle === undefined) isVerticle = Math.abs(nowY - startY) / Math.abs(nowX - startX) > (Math.sqrt(3) / 3);
當(dāng) isVerticle 為 true 時,不執(zhí)行drawer的拖動
讓Drawer動起來
我們使用css3的 transition 屬性使drawer具有過渡動畫效果,這里寫一個 moving 類
.moving transition transform .3s ease
別忘了加上class綁定,拖動時是不需要過渡動畫的(要跟隨手指),而松開手指時才需要過渡動畫。
<div class="drawer-wrap" :class="{'moving':moving,'will-change':willChange}" :style="{width:`${width}px`,left:`-${width)}px`,transform:`translate3d(${pos}px,0,0)`}"> <slot name="drawer"></slot> </div>
所以綁定 touchend 事件的方法時要做這些步驟
const removeDrag = function (e) { if (isVerticle !== undefined) { if (!isVerticle) {//當(dāng)判定為抽屜拖動才進入 let pos = this.pos; this.visible = pos > width * 3 / 5 //當(dāng)前位置如果大于總寬度的3/5就判定為全部展開抽屜,否則將抽屜彈回隱藏 if (this.pos > 0 && this.pos < width) this.moving = true;//如果位置已經(jīng)處于最小值或最大值處,不需要有動畫效果了 } this.pos = this.visible ? width : 0; } if (!this.moving) { this.willChange = false; //留個懸念 } isVerticle = undefined; //取消touchmove和touchend事件綁定 document.removeEventListener(mouseEvents.move, drag, false); document.removeEventListener(mouseEvents.up, removeDrag, false); }.bind(this);
上面你可能發(fā)現(xiàn)代碼里有個 this.willChange = false ,它是干啥的捏?下面我們請出css的 will-change 大法
.will-change
will-change transform
CSS 屬性 will-change 為web開發(fā)者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器可以在元素屬性真正發(fā)生變化之前提前做好對應(yīng)的優(yōu)化準備工作。 這種優(yōu)化可以將一部分復(fù)雜的計算工作提前準備好,使頁面的反應(yīng)更為快速靈敏。
其實是我們在 touchstart 可以預(yù)先告知瀏覽器抽屜可能要發(fā)生位移
const initDrag = function (e) { //... this.willChange = true; }.bind(this);
當(dāng)然最后別忘了在 transitionend 事件后把 transition 和 will-change 去掉,讓瀏覽器歇一會兒~
還有什么可以優(yōu)化的?
上面說的已經(jīng)基本上把主要功能實現(xiàn)了,但是這其中還有沒有哪里可以優(yōu)化的?
咦? passive
是什么鬼?
網(wǎng)站使用被動事件偵聽器以提升滾動性能,在您的觸摸和滾輪事件偵聽器上設(shè)置 passive 選項可提升滾動性能具體看這里
原來這是現(xiàn)代瀏覽器的一個新特性,我們需要以新的方式來綁定我們的touch事件,當(dāng)然首先先檢測一下是否支持 passive
const supportsPassive = (() => { let supportsPassive = false; try { const opts = Object.defineProperty({}, 'passive', { get: function () { supportsPassive = true; } }); window.addEventListener("test", null, opts); } catch (e) { } return supportsPassive; })();
于是我們的綁定事件代碼變成這樣
document.addEventListener(mouseEvents.move, drag, supportsPassive ? {passive: true} : false);
寫在最后
本文介紹了實現(xiàn)抽屜式導(dǎo)航欄的主要過程,詳細代碼已封裝成 vue-drawer-layout 組件,支持更豐富的定制和使用方式,具體文檔可以訪問我的github 或者npm官網(wǎng)檢索。
相關(guān)文章
vue-router路由跳轉(zhuǎn)問題 replace
這篇文章主要介紹了vue-router路由跳轉(zhuǎn)問題 replace,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09vue-cli項目如何使用vue-resource獲取本地的json數(shù)據(jù)(模擬服務(wù)端返回數(shù)據(jù))
這篇文章主要介紹了vue-cli項目如何使用vue-resource獲取本地的json數(shù)據(jù)(模擬服務(wù)端返回數(shù)據(jù)),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08Vue?+?element-ui?背景圖片設(shè)置方式
這篇文章主要介紹了Vue?+?element-ui?背景圖片設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08