vue移動端UI框架實現QQ側邊菜單組件
最近面試發(fā)現很多前端程序員都從來沒有寫過插件的經驗,基本上都是網上百度。所以打算寫一系列文章,手把手的教一些沒有寫過組件的兄弟們如何去寫插件。本系列文章都基于VUE,核心內容都一樣,會了之后大家可以快速的改寫成react、angular或者是小程序等組件。這篇文章是第一篇,寫的是一個類似QQ的側邊菜單組件。
效果展示
先讓大家看個效果展示,知道咱們要做的東西是個怎么樣的樣子,圖片有點模糊,大家先將就點:
開始制作
DOM結構
整體結構中應該存在兩個容器:1. 菜單容器 2. 主頁面容器;因此當前DOM結構如下:
<template> <div class="r-slide-menu"> <div class="r-slide-menu-wrap"></div> <div class="r-slide-menu-content"></div> </div> </template>
為了使得菜單內容和主題內容能夠定制,我們再給兩個容器中加入兩個slot插槽:默認插槽中放置主體內容、菜單放置到menu插槽內:
<template> <div class="r-slide-menu"> <div class="r-slide-menu-wrap"> <slot name="menu"></slot> </div> <div class="r-slide-menu-content"> <slot></slot> </div> </div> </template>
css樣式
我項目中使用了scss,代碼如下:
<style lang="scss"> @mixin one-screen { position: absolute; left:0; top:0; width:100%; height:100%; overflow: hidden; } .r-slide-menu{ @include one-screen; &-wrap, &-content{ @include one-screen; } &-transition{ -webkit-transition: transform .3s; transition: transform .3s; } } </style>
此時我們就得到了兩個絕對定位的容器
javascript
現在開始正式的代碼編寫了,首先我們理清下交互邏輯:
- 手指左右滑動的時候主體容器和菜單容器都跟著手指運動運動
- 當手指移動的距離超過菜單容器寬度的時候頁面不能繼續(xù)向右滑動
- 當手指向左移動使得菜單和頁面的移動距離歸零的時候頁面不能繼續(xù)向左移動
- 當手指釋放離開屏幕的時候,頁面滑動如果超過一定的距離(整個菜單寬度的比例)則打開整個菜單,如果小于一定距離則關閉菜單
所以現在咱們需要在使用組件的時候能夠入參定制菜單寬度以及觸發(fā)菜單收起關閉的臨界值和菜單寬度的比例,同時需要給主體容器添加touch事件,最后我們給菜單容器和主體容器添加各自添加一個控制他們運動的style,通過控制這個style來控制容器的移動
<template> <div class="r-slide-menu"> <div class="r-slide-menu-wrap" :style="wrapStyle"> <slot name="menu"></slot> </div> <div class="r-slide-menu-content" :style="contentStyle" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"> <slot></slot> </div> </div> </template> <script> export default { props: { width: { type: String, default: '250' }, ratio: { type: Number, default: 2 } }, data () { return { isMoving: false, transitionClass: '', startPoint: { X: 0, y: 0 }, oldPoint: { x: 0, y: 0 }, move: { x: 0, y: 0 } } }, computed: { wrapStyle () { let style = { width: `${this.width}px`, left: `-${this.width / this.ratio}px`, transform: `translate3d(${this.move.x / this.ratio}px, 0px, 0px)` } return style }, contentStyle () { let style = { transform: `translate3d(${this.move.x}px, 0px, 0px)` } return style } }, methods: { touchstart (e) {}, touchmove (e) {}, touchend (e) {} } }
接下來,我們來實現我們最核心的touch事件處理函數,事件的邏輯如下:
- 手指按下瞬間,記錄下當前手指所觸摸的點,以及當前主容器的位置
- 手指移動的時候,獲取到移動的點的位置
- 計算當前手指所在點移動的X、Y軸距離,如果X移動的距離大于Y移動的距離則判定為橫向運動,否則為豎向運動
- 如果橫向運動則判斷當前移動的距離是在合理的移動區(qū)間(0到菜單寬度)移動,如果是則改變兩個容器的位置(移動過程中阻止頁面中其他的事件觸發(fā))
- 手指離開屏幕:如果累計移動距離超過臨界值則運用動畫打開菜單,否則關閉菜單
touchstart (e) { this.oldPoint.x = e.touches[0].pageX this.oldPoint.y = e.touches[0].pageY this.startPoint.x = this.move.x this.startPoint.y = this.move.y this.setTransition() }, touchmove (e) { let newPoint = { x: e.touches[0].pageX, y: e.touches[0].pageY } let moveX = newPoint.x - this.oldPoint.x let moveY = newPoint.y - this.oldPoint.y if (Math.abs(moveX) < Math.abs(moveY)) return false e.preventDefault() this.isMoving = true moveX = this.startPoint.x * 1 + moveX * 1 moveY = this.startPoint.y * 1 + moveY * 1 if (moveX >= this.width) { this.move.x = this.width } else if (moveX <= 0) { this.move.x = 0 } else { this.move.x = moveX } }, touchend (e) { this.setTransition(true) this.isMoving = false this.move.x = (this.move.x > this.width / this.ratio) ? this.width : 0 }, setTransition (isTransition = false) { this.transitionClass = isTransition ? 'r-slide-menu-transition' : '' }
上面,這段核心代碼中有一個setTransition 函數,這個函數的作用是在手指離開的時候給容器元素添加transition屬性,讓容器有一個過渡動畫,完成關閉或者打開動畫;所以在手指按下去的瞬間需要把容器上的這個transition屬性去除,避免滑動過程中出現容器和手指滑動延遲的不良體驗。 最后提醒下,代碼中使用translate3d而非translate的原因是為了啟動移動端手機的動畫3D加速,提升動畫流暢度。最終代碼如下:
<template> <div class="r-slide-menu"> <div class="r-slide-menu-wrap" :class="transitionClass" :style="wrapStyle"> <slot name="menu"></slot> </div> <div class="r-slide-menu-content" :class="transitionClass" :style="contentStyle" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"> <slot></slot> </div> </div> </template> <script> export default { props: { width: { type: String, default: '250' }, ratio: { type: Number, default: 2 } }, data () { return { isMoving: false, transitionClass: '', startPoint: { X: 0, y: 0 }, oldPoint: { x: 0, y: 0 }, move: { x: 0, y: 0 } } }, computed: { wrapStyle () { let style = { width: `${this.width}px`, left: `-${this.width / this.ratio}px`, transform: `translate3d(${this.move.x / this.ratio}px, 0px, 0px)` } return style }, contentStyle () { let style = { transform: `translate3d(${this.move.x}px, 0px, 0px)` } return style } }, methods: { touchstart (e) { this.oldPoint.x = e.touches[0].pageX this.oldPoint.y = e.touches[0].pageY this.startPoint.x = this.move.x this.startPoint.y = this.move.y this.setTransition() }, touchmove (e) { let newPoint = { x: e.touches[0].pageX, y: e.touches[0].pageY } let moveX = newPoint.x - this.oldPoint.x let moveY = newPoint.y - this.oldPoint.y if (Math.abs(moveX) < Math.abs(moveY)) return false e.preventDefault() this.isMoving = true moveX = this.startPoint.x * 1 + moveX * 1 moveY = this.startPoint.y * 1 + moveY * 1 if (moveX >= this.width) { this.move.x = this.width } else if (moveX <= 0) { this.move.x = 0 } else { this.move.x = moveX } }, touchend (e) { this.setTransition(true) this.isMoving = false this.move.x = (this.move.x > this.width / this.ratio) ? this.width : 0 }, // 點擊切換 switch () { this.setTransition(true) this.move.x = (this.move.x === 0) ? this.width : 0 }, setTransition (isTransition = false) { this.transitionClass = isTransition ? 'r-slide-menu-transition' : '' } } } </script> <style lang="scss"> @mixin one-screen { position: absolute; left:0; top:0; width:100%; height:100%; overflow: hidden; } .r-slide-menu{ @include one-screen; &-wrap, &-content{ @include one-screen; } &-transition{ -webkit-transition: transform .3s; transition: transform .3s; } } </style>
總結
以上所述是小編給大家介紹的vue移動端UI框架實現QQ側邊菜單組件,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
相關文章
vue-i18n的9以上版本中@被用作特殊字符處理,直接用會報錯問題
這篇文章主要介紹了vue-i18n的9以上版本中@被用作特殊字符處理,直接用會報錯問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08