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 相关文章推荐
javascript 模拟JQuery的Ready方法实现并出现的问题
Dec 06 Javascript
40个有创意的jQuery图片和内容滑动及弹出插件收藏集之三
Jan 03 Javascript
jquery访问ashx文件示例代码
Aug 11 Javascript
JavaScript中判断变量是数组、函数或是对象类型的方法
Feb 25 Javascript
JavaScript中的Math.atan2()方法使用详解
Jun 15 Javascript
JavaScript数据推送Comet技术详解
Apr 07 Javascript
jquery中实现时间戳与日期相互转换
Apr 12 Javascript
js Date()日期函数浏览器兼容问题解决方法
Sep 12 Javascript
详解RequireJs官方使用教程
Oct 31 Javascript
Angular5中提取公共组件之radio list的实例代码
Jul 10 Javascript
js屏蔽退格键(backspace或者叫后退键与F5)
Feb 10 Javascript
node.js使用stream模块实现自定义流示例
Feb 13 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
PHP概述.
2006/10/09 PHP
PHP 网络开发详解之远程文件包含漏洞
2010/04/25 PHP
解析PHP跳出循环的方法以及continue、break、exit的区别介绍
2013/07/01 PHP
ThinkPHP之foreach标签使用概述
2014/06/30 PHP
PHP操作MySQL事务实例
2014/11/05 PHP
PHP中使用array函数新建一个数组
2015/11/19 PHP
ThinkPHP中where()使用方法详解
2016/04/19 PHP
ThinkPHP连接Oracle数据库
2016/04/22 PHP
PHP二分查找算法示例【递归与非递归方法】
2016/09/29 PHP
PHP实现的回溯算法示例
2017/08/15 PHP
PHP获取访问设备信息的方法示例
2019/02/20 PHP
JS函数实现动态添加CSS样式表文件
2012/12/15 Javascript
通过JS来判断页面控件是否获取焦点
2014/01/03 Javascript
js 判断控件获得焦点的示例代码
2014/03/04 Javascript
Jquery中request和request.form和request.querystring的区别
2015/11/26 Javascript
基于JavaScript代码实现微信扫一扫下载APP
2015/12/30 Javascript
javascript弹性运动效果简单实现方法
2016/01/08 Javascript
实例讲解js验证表单项是否为空的方法
2016/01/09 Javascript
巧用数组制作图片切换js代码
2016/11/29 Javascript
Jquery EasyUI $.Parser
2017/06/02 jQuery
Vue.js实现一个todo-list的上移下移删除功能
2017/06/26 Javascript
Extjs 中的 Treepanel 实现菜单级联选中效果及实例代码
2017/08/22 Javascript
vue中配置mint-ui报css错误问题的解决方法
2017/10/11 Javascript
Python素数检测的方法
2015/05/11 Python
python监控进程脚本
2018/04/12 Python
利用Python实现学生信息管理系统的完整实例
2020/12/30 Python
python pyg2plot的原理知识点总结
2021/02/28 Python
HTML5新增元素如何兼容旧浏览器有哪些方法
2014/05/09 HTML / CSS
美国首屈一指的礼品篮供应商:GiftTree
2018/01/06 全球购物
奥林匹亚体育:Olympia Sports
2020/12/30 全球购物
党员培训思想汇报
2014/01/07 职场文书
小学一年级学生评语
2014/04/22 职场文书
教师演讲稿大全
2014/05/16 职场文书
2014年乡镇人大工作总结
2014/11/25 职场文书
团代会开幕词
2015/01/28 职场文书
一篇文章带你了解Python和Java的正则表达式对比
2021/09/15 Python