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 相关文章推荐
js类中的公有变量和私有变量
Jul 24 Javascript
JavaScript Event学习第二章 Event浏览器兼容性
Feb 07 Javascript
JS声明变量背后的编译原理剖析
Dec 28 Javascript
JS获取浏览器版本及名称实现函数
Apr 02 Javascript
JS去除右边逗号的简单方法
Jul 03 Javascript
JS如何生成一个不重复的ID的函数
Dec 25 Javascript
实现一个简单的vue无限加载指令方法
Jan 10 Javascript
vue使用watch 观察路由变化,重新获取内容
Mar 08 Javascript
jQuery实现简单的下拉菜单导航功能示例
Dec 07 jQuery
基于vue开发的在线付费课程应用过程
Jan 25 Javascript
vue 不使用select实现下拉框功能(推荐)
May 17 Javascript
傻瓜式vuex语法糖kiss-vuex整理
Dec 21 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
实现“上一页”和“下一页按钮
2006/10/09 PHP
php 日期时间处理函数小结
2009/12/18 PHP
thinkPHP5.0框架API优化后的友好性分析
2017/03/17 PHP
用CSS+JS实现的进度条效果效果
2007/06/05 Javascript
运用jquery实现table单双行不同显示并能单行选中
2009/07/25 Javascript
Javascript页面添加到收藏夹的简单方法
2013/08/07 Javascript
JavaScript中Math对象方法使用概述
2014/01/02 Javascript
js实现点击图片改变页面背景图的方法
2015/02/28 Javascript
javascript文本模板用法实例
2015/07/31 Javascript
JQuery实现定时刷新功能代码
2017/05/09 jQuery
vue组件生命周期详解
2017/11/07 Javascript
微信小程序实现红包功能(后端PHP实现逻辑)
2018/07/11 Javascript
layer.js open 隐藏滚动条的例子
2019/09/05 Javascript
微信小程序wxml列表渲染原理解析
2019/11/27 Javascript
Javascript实现鼠标移入方向感知
2020/06/24 Javascript
mustache.js实现首页元件动态渲染的示例代码
2020/12/28 Javascript
Python优化技巧之利用ctypes提高执行速度
2016/09/11 Python
Python cookbook(数据结构与算法)字典相关计算问题示例
2018/02/18 Python
用tensorflow搭建CNN的方法
2018/03/05 Python
python下解压缩zip文件并删除文件的实例
2018/04/24 Python
用python建立两个Y轴的XY曲线图方法
2019/07/08 Python
python dict乱码如何解决
2020/06/07 Python
PyCharm 2020.2 安装详细教程
2020/09/25 Python
Python使用urlretrieve实现直接远程下载图片的示例代码
2020/08/17 Python
如何用Python 实现全连接神经网络(Multi-layer Perceptron)
2020/10/15 Python
如何用canvas实现在线签名的示例代码
2018/07/10 HTML / CSS
《临死前的严监生》教学反思
2014/02/13 职场文书
致接力运动员广播稿
2014/02/17 职场文书
大学三年计划书范文
2014/04/30 职场文书
团结演讲稿范文
2014/05/23 职场文书
机关驾驶员违规检讨书
2014/09/13 职场文书
先进党组织事迹材料
2014/12/26 职场文书
武侯祠导游词
2015/02/04 职场文书
工作经历证明范本
2015/06/15 职场文书
学校运动会通讯稿
2015/07/18 职场文书
JavaScript流程控制(循环)
2021/12/06 Javascript