公司移動端開發平臺進行了大變革,前端架構由DCloud大生態轉換爲VUE,因此移動端的UI組件庫從MUI改成使用MintUI,而後開始大刀闊斧的把MintUI組件改爲MUI組件的樣子,而後發現少了幾個較爲經常使用的,其中一個就是,嗯,側滑面板(也叫側滑菜單,也叫抽屜面板-andriod官方是這麼翻譯的,很形象)。可是,它就是一個佈局組件,具體裏邊菜單什麼的,那都是浮雲(嗯,就是愛用幾年前的流行詞彙,並且很喜歡在網上衝浪和踩別人的空間)css
(1)主面板滑動,側滑面板不動
(2)側滑面板動,主面板不動
(3)它倆一塊動,一塊兒--------前端
mintUI組件中一樣?️滑動操做的tabContainer,爲了知足需求5,我連函數名都抄了過來。vue
不說廢話,上代碼吧git
這個組件分爲兩部分,一部分爲側滑面板容器,另外一部分爲主面板容器,而後具體容器內部直接放了插槽,而後還須要一個主面板容器的遮罩,爲了側滑面板打開的時候顯現出來。上代碼了github
<div class="mint-drawer-layout"> <!--側滑欄--> <div ref="drawer" class=" mint-drawer-warp" @touchstart.stop="startDrag" @touchmove.stop="onDrag" @touchend.stop="endDrag" :style="drawerStyle"> <slot name="drawer"></slot> </div> <!--主容器--> <div ref="content" @touchstart.stop="startDrag" @touchmove.stop="onDrag" @touchend.stop="endDrag" class=" mint-content-warp" :style="contentStyle"> <!--主容器遮罩(側滑打開狀態下顯示)--> <div class="content-mask" v-tap="toggle" ref="contentMask"></div> <slot name="content"></slot> </div> </div>
這塊加了一些咱們公司的一些需求,可能各位哥哥姐姐門用不到裏邊的一些props的設計,僅供參考web
props: { // 側滑面板的寬度(單位px) 'drawerWidth': { type: Number, default: 200 }, // 是否可用 'enable': { type: Boolean, default: true }, // 側滑菜單是否在右邊,默認爲false,在左邊 'isRight': { type: Boolean, default: false }, // 側滑菜單滑動操做類型 // ['fixDrawer'——固定側滑面板,主面板滑動] // ['fixContent'——固定主面板,側滑面板滑動] // ['noFixed'——一塊兒滑動!] 'swipeType': { type: String, default: 'fixDrawer' }, // 點擊出現側滑菜單的按鈕的id ( @TODO 這裏如何處理異步渲染的問題 ) 'btnId': { type: String, default: '' }, // 狀態位,側滑面板是否爲打開狀態 // (由於咱們公司有這種一開始就把側滑菜單打開的shabee場景,因此這纔會有這麼個東西) //(若是這個不但願配置的話、能夠放在data裏邊) 'isDrawerOpened': { type: Boolean, default: false }, // 是否可滑動,若是不可滑動的話,就只能經過調用toogle方法打開側滑面板 // 這個也是公司的一個使用場景,就是你甭滑,找個按鈕觸發一下側滑面板打開的方法才能打開 //(若是這個不但願配置的話、也能夠放在data裏邊) 'swipeable': { type: Boolean, default: true } }
不得不認可,個人css寫的shitsegmentfault
對於整個的組件來講,它應該是默認充滿整個父容器的,並且這個組件,我以爲,通常都是用來放在最外層的一個佈局組件,因此,默認充滿窗口就好了
因此組件的最外層來一個絕對佈局,而後以下:架構
.mint-drawer-layout { position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow-x: hidden; }
而後側滑面板,只須要縱向充滿就能夠了,寬度是配置app
.mint-drawer-warp { position: absolute; top: 0; bottom: 0; }
而後是主面板,沒說的異步
.mint-content-warp { position: relative; width: 100%; height: 100%; }
終於來到了介紹到底該怎麼實現的了
首先第一步上來就要設置一個記錄滑動操做的狀態變量——dragging,設置爲false,方便屏蔽滑動時觸發的其餘操做的執行
——開始的時候記錄開始滑動的位置
——滑動中,dragging狀態記錄爲true,開始進行滑動位置和手指移動的聯動
——滑動結束,dragging狀態記錄爲false,計算當前的滑動位置,判斷是劃開側滑面板仍是關閉,並進行動畫處理
其中幾個細節小談一哈
(1)左右滑動操做觸發的判斷:我這邊是公司的規範,橫軸移動位移大於五,豎軸位移不大於橫軸的1.73倍就能夠
(2)最後結束時判斷側滑面板的打開和關閉:是這樣的,我這邊取的是三分之一的側滑面板的寬度,也就是從打開到關閉,那麼像關閉的方向滑動側滑面板寬度的三分之一就能夠了,若是是關閉到打開,往打開的方向滑動三分之一就能夠了
(3)左側和右側,還有三種不一樣的滑動方式:三種不一樣的滑動方式實際上就是控制到底哪一個面板隨着手指動,具體的動做過程和麪板的偏移量其實是同樣的。左右兩側就更簡單了,直接是對稱的操做就可
滑動結束的操做,參考的tabcontainer,也挺巧妙的,各位請上眼~
/** * 滑動結束的動畫 */ swipeLeaveTransition() { let g = this, currentMovingDoms = []; let {swipeType, drawerWidth} = g; switch (swipeType) { case 'fixDrawer': currentMovingDoms.push(g.content); break; case 'fixContent': currentMovingDoms.push(g.drawer); break; case 'noFixed': currentMovingDoms.push(g.drawer); currentMovingDoms.push(g.content); break; default: break; } currentMovingDoms.forEach((val) => { val.classList.add('swipe-transition'); }); setTimeout(() => { if (g.isDO) { this.swipeMove(drawerWidth); } else { this.swipeMove(0); g.contentMask.style.opacity = 0; g.contentMask.style.display = 'none'; } g.isToggle = false; currentMovingDoms.forEach((val) => { once(val, 'webkitTransitionEnd', _ => { val.classList.remove('swipe-transition'); g.swiping = false; }); }); }, 0); }, /** * 滑動操做 * @param offset 滑動位置 */ swipeMove(offset) { let g = this; let {swipeType, isRight} = g; g.contentMask.style.display = 'block'; g.contentMask.style.opacity = Math.abs(offset) / g.drawerWidth * 0.4; switch (swipeType) { case 'fixDrawer': g.content.style.webkitTransform = `translate3d(${(!isRight ? '' : '-') + offset}px, 0, 0)`; g.swiping = true; break; case 'fixContent': g.drawer.style.webkitTransform = `translate3d(${(!isRight ? '' : '-') + offset}px, 0, 0)`; g.swiping = true; break; case 'noFixed': g.content.style.webkitTransform = `translate3d(${(!isRight ? '' : '-') + offset}px, 0, 0)`; g.drawer.style.webkitTransform = `translate3d(${(!isRight ? '' : '-') + offset}px, 0, 0)`; g.swiping = true; break; default: break; } }, // 開始滑動 startDrag(evt) { let g = this; if (!g.enable || !g.swipeable) return false; evt = evt.changedTouches ? evt.changedTouches[0] : evt; g.start.x = evt.pageX; g.start.y = evt.pageY; }, // 滑動中 onDrag(evt) { let g = this, swiping; if (!g.enable || !g.swipeable) return false; g.dragging = true; const e = evt.changedTouches ? evt.changedTouches[0] : evt; const offsetTop = e.pageY - g.start.y; const offsetLeft = e.pageX - g.start.x; const y = Math.abs(offsetTop); const x = Math.abs(offsetLeft); swiping = !(x < 5 || (x >= 5 && y >= x * 1.73)); if (!swiping) return; evt.preventDefault(); let offset; if (g.isDO) { offset = g.isRight ? (g.drawerWidth - offsetLeft) : (g.drawerWidth - (-offsetLeft)); } else { offset = g.isRight ? -offsetLeft : offsetLeft; } if (offset < 0 || offset > g.drawerWidth) { g.swiping = false; return; } g.offset = offset; g.swipeMove(offset); }, // 結束滑動 endDrag() { let g = this; if (!g.enable || g.isToggle || !g.dragging) { return false; } const tempWidth = g.drawerWidth / 3; if (g.isDO && g.offset < tempWidth * 2) { g.isDO = false; } else if (!g.isDO && g.offset > tempWidth) { g.isDO = true; } g.dragging = false; g.swipeLeaveTransition(); }
好啦,到這應該就差很少了。。。
裏邊涉及到的v-tap指令是自定義的指令,爲了處理移動端的點擊操做,我還整理了一片陋文:https://segmentfault.com/a/11... (移動點擊長按滑動vue指令)
而後這個組件的源碼我放在了我fork出來的mintUI項目上
https://github.com/LylaYuKako...
謝謝各位品嚐,