以下這段都是廢話,請跳過
公司移動端開發平臺進行了大變革,前端架構由DCloud大生態轉換為VUE,所以移動端的UI元件庫從MUI改為使用MintUI,然後開始大刀闊斧的把MintUI元件改成MUI元件的樣子,然後發現少了幾個較為常用的,其中一個就是,嗯,側滑皮膚(也叫側滑選單,也叫抽屜皮膚-andriod官方是這麼翻譯的,很形象)。但是,它就是一個佈局元件,具體裡邊選單什麼的,那都是浮雲(嗯,就是愛用幾年前的流行詞彙,而且很喜歡在網上衝浪和踩別人的空間)
以上這段都是廢話,感謝閱讀
需求
- 開發一個側滑皮膚(類似QQ、網易郵箱等app的)
- 可以在左邊,也可以在右邊
- 側滑皮膚內容隨意定製
- 側滑皮膚相對的就有主皮膚,那麼衍生出不同的體位和姿勢
(1)主皮膚滑動,側滑皮膚不動
(2)側滑皮膚動,主皮膚不動
(3)它倆一塊動,一起-------- - 程式碼風格儘量和MintUI的其他元件風格類似(這個挺重要的)
參考
mintUI元件中同樣?️滑動操作的tabContainer,為了滿足需求5,我連函式名都抄了過來。
不說廢話,上程式碼吧
The Waaaaaaaay
1. 設計元件結構
這個元件分為兩部分,一部分為側滑皮膚容器,另一部分為主皮膚容器,然後具體容器內部直接放了插槽,然後還需要一個主皮膚容器的遮罩,為了側滑皮膚開啟的時候顯現出來。上程式碼了
<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>
2. 配置設計
這塊加了一些我們公司的一些需求,可能各位哥哥姐姐門用不到裡邊的一些props的設計,僅供參考
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
}
}
2. 樣式設計
不得不承認,我的css寫的shit
對於整個的元件來說,它應該是預設充滿整個父容器的,而且這個元件,我覺得,一般都是用來放在最外層的一個佈局元件,所以,預設充滿視窗就行了
所以元件的最外層來一個絕對佈局,然後如下:
.mint-drawer-layout {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow-x: hidden;
}
然後側滑皮膚,只需要縱向充滿就可以了,寬度是配置
.mint-drawer-warp {
position: absolute;
top: 0;
bottom: 0;
}
然後是主皮膚,沒說的
.mint-content-warp {
position: relative;
width: 100%;
height: 100%;
}
4. 關鍵實現
終於來到了介紹到底該怎麼實現的了
滑動處理三步走,start、drag、end
首先第一步上來就要設定一個記錄滑動操作的狀態變數——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...
謝謝各位品嚐,