vue移动端UI框架实现QQ侧边菜单组件


Posted in Javascript onMarch 09, 2018

最近面试发现很多前端程序员都从来没有写过插件的经验,基本上都是网上百度。所以打算写一系列文章,手把手的教一些没有写过组件的兄弟们如何去写插件。本系列文章都基于VUE,核心内容都一样,会了之后大家可以快速的改写成react、angular或者是小程序等组件。这篇文章是第一篇,写的是一个类似QQ的侧边菜单组件。

效果展示

先让大家看个效果展示,知道咱们要做的东西是个怎么样的样子,图片有点模糊,大家先将就点:

vue移动端UI框架实现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

现在开始正式的代码编写了,首先我们理清下交互逻辑:

  • 手指左右滑动的时候主体容器和菜单容器都跟着手指运动运动
  • 当手指移动的距离超过菜单容器宽度的时候页面不能继续向右滑动
  • 当手指向左移动使得菜单和页面的移动距离归零的时候页面不能继续向左移动
  • 当手指释放离开屏幕的时候,页面滑动如果超过一定的距离(整个菜单宽度的比例)则打开整个菜单,如果小于一定距离则关闭菜单

所以现在咱们需要在使用组件的时候能够入参定制菜单宽度以及触发菜单收起关闭的临界值和菜单宽度的比例,同时需要给主体容器添加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事件处理函数,事件的逻辑如下:

  1. 手指按下瞬间,记录下当前手指所触摸的点,以及当前主容器的位置
  2. 手指移动的时候,获取到移动的点的位置
  3. 计算当前手指所在点移动的X、Y轴距离,如果X移动的距离大于Y移动的距离则判定为横向运动,否则为竖向运动
  4. 如果横向运动则判断当前移动的距离是在合理的移动区间(0到菜单宽度)移动,如果是则改变两个容器的位置(移动过程中阻止页面中其他的事件触发)
  5. 手指离开屏幕:如果累计移动距离超过临界值则运用动画打开菜单,否则关闭菜单
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侧边菜单组件,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
DHTML 中的绝对定位
Nov 26 Javascript
解读JavaScript代码 var ie = !-[1,] 最短的IE判定代码
May 28 Javascript
node.js中使用socket.io的方法
Dec 15 Javascript
javascript十六进制及二进制转化的方法
May 06 Javascript
浅析javascript的return语句
Dec 15 Javascript
JavaScript中匿名函数的用法及优缺点详解
Jun 01 Javascript
利用n工具轻松管理Node.js的版本
Apr 21 Javascript
vue中的非父子间的通讯问题简单的实例代码
Jul 19 Javascript
JavaScript 中Date对象的格式化代码方法汇总
Sep 06 Javascript
TypeScript中的方法重载详解
Apr 12 Javascript
js贪心算法 钱币找零问题代码实例
Sep 11 Javascript
Vue双向绑定实现原理与方法详解
May 07 Javascript
vue的安装及element组件的安装方法
Mar 09 #Javascript
11行JS代码制作二维码生成功能
Mar 09 #Javascript
浅谈vue.js导入css库(elementUi)的方法
Mar 09 #Javascript
使用use注册Vue全局组件和全局指令的方法
Mar 08 #Javascript
vue注册组件的几种方式总结
Mar 08 #Javascript
Vue.js自定义事件的表单输入组件方法
Mar 08 #Javascript
layui之select的option叠加问题的解决方法
Mar 08 #Javascript
You might like
跟我学Laravel之请求(Request)的生命周期
2014/10/15 PHP
php检测apache mod_rewrite模块是否安装的方法
2015/03/14 PHP
php实现随机显示图片方法汇总
2015/05/21 PHP
Yii2分页的使用及其扩展方法详解
2016/05/23 PHP
php中序列化与反序列化详解
2017/02/13 PHP
PHP count()函数讲解
2019/02/03 PHP
雄兵连第三季海报曝光,艾妮熙德成主角,蔷薇新造型
2021/03/09 国漫
兼容IE、FireFox、Chrome等浏览器的xml处理函数js代码
2011/11/30 Javascript
jQuery 数据缓存模块进化史详细介绍
2012/11/19 Javascript
Extjs中的GridPanel隐藏列会显示在menuDisabled中解决方法
2013/01/27 Javascript
用jQuery实现一些导航条切换,显示隐藏的实例代码
2013/06/08 Javascript
js清理Word格式示例代码
2014/02/13 Javascript
JS实现固定在右下角可展开收缩DIV层的方法
2015/02/13 Javascript
在JavaScript的AngularJS库中进行单元测试的方法
2015/06/23 Javascript
jquery移动点击的项目到列表最顶端的方法
2015/06/24 Javascript
jQuery过滤HTML标签并高亮显示关键字的方法
2015/08/07 Javascript
require.js的用法详解
2015/10/20 Javascript
JS Html转义和反转义(html编码和解码)的实现与使用方法总结
2020/03/10 Javascript
Python version 2.7 required, which was not found in the registry
2014/08/26 Python
python求pi的方法
2014/10/08 Python
在Linux下调试Python代码的各种方法
2015/04/17 Python
python中while和for的区别总结
2019/06/28 Python
Pytorch 中retain_graph的用法详解
2020/01/07 Python
巧用CSS3的calc()宽度计算做响应模式布局的方法
2018/03/22 HTML / CSS
HTML5 本地存储和内容按需加载的思路和方法
2011/04/07 HTML / CSS
微软香港官网及网上商店:Microsoft HK
2016/09/01 全球购物
JD Sports德国官网:英国领先的运动鞋和运动服饰零售商
2018/02/26 全球购物
Radley英国官网:英国莱德利小狗包
2019/03/21 全球购物
Sandro法国官网:法国成衣品牌
2019/08/28 全球购物
Cocopanda波兰:购买化妆品、护肤品、护发和香水
2020/05/25 全球购物
华为的Java面试题
2014/03/07 面试题
函授本科毕业生自我鉴定
2013/10/16 职场文书
诚信考试标语
2014/06/24 职场文书
有关浪费资源的建议书
2015/09/14 职场文书
使用pandas或numpy处理数据中的空值(np.isnan()/pd.isnull())
2021/05/14 Python
「月刊Action」2022年5月号封面公开
2022/03/21 日漫