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

最近面试发现很多前端程序员都从来没有写过插件的经验,基本上都是网上百度。所以打算写一系列文章,手把手的教一些没有写过组件的兄弟们如何去写插件。本系列文章都基于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

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

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

所以现在咱们需要在使用组件的时候能够入参定制菜单宽度以及触发菜单收起关闭的临界值和菜单宽度的比例,同时需要给主体容器添加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侧边菜单组件,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

您可能感兴趣的文章:

  • 基于VUE.JS的移动端框架Mint UI的使用
  • 浅谈基于Vue.js的移动组件库cube-ui
  • Mint UI 基于 Vue.js 移动端组件库
(0)

相关推荐

  • 浅谈基于Vue.js的移动组件库cube-ui

    cube-ui 是滴滴公司的技术团队基于 Vue.js 实现的精致移动端组件库.很赞,虽然组件还不是很多,但是基本场景是够用了,感谢开源! 首先创建一个vue项目 vue init webpack my-project cd my-project npm install 安装cube-ui npm install cube-ui -S 官方推荐使用 babel-plugin-transform-modules 插件,可以更优雅引入组件模块以及对应的样式. npm install babel-pl

  • 基于VUE.JS的移动端框架Mint UI的使用

    Mint UI GitHub:github.com/ElemeFE/mint 项目主页:mint-ui.github.io/# Demo:elemefe.github.io/mint- 文档:mint-ui.github.io/docs/# 由饿了么前端团队推出的 Mint UI 是一个基于 Vue.js 的移动端组件库.自 6 月初开源以来,根据社区和团队内部的反馈,修复了一些 bug 并新增了部分组件,于本周发布了 0.2.0 版本.本文介绍如何从零开始构建一个使用 Mint UI 的 Vu

  • Mint UI 基于 Vue.js 移动端组件库

    官网地址 http://mint-ui.github.io/ Mint UI 包含丰富的 CSS 和 JS 组件,能够满足日常的移动端开发需要.通过它,可以快速构建出风格统一的页面,提升开发效率. 真正意义上的按需加载组件.可以只加载声明过的组件及其样式文件,无需再纠结文件体积过大. 考虑到移动端的性能门槛,Mint UI 采用 CSS3 处理各种动效,避免浏览器进行不必要的重绘和重排,从而使用户获得流畅顺滑的体验. 依托 Vue.js 高效的组件化方案,Mint UI 做到了轻量化.即使全部引

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

    最近面试发现很多前端程序员都从来没有写过插件的经验,基本上都是网上百度.所以打算写一系列文章,手把手的教一些没有写过组件的兄弟们如何去写插件.本系列文章都基于VUE,核心内容都一样,会了之后大家可以快速的改写成react.angular或者是小程序等组件.这篇文章是第一篇,写的是一个类似QQ的侧边菜单组件. 效果展示 先让大家看个效果展示,知道咱们要做的东西是个怎么样的样子,图片有点模糊,大家先将就点: 开始制作 DOM结构 整体结构中应该存在两个容器:1. 菜单容器 2. 主页面容器:因此当前

  • 浅谈Vue服务端渲染框架Nuxt的那些事

    Vue因其简单易懂的API.高效的数据绑定和灵活的组件系统,受到很多前端开发人员的青睐.国内很多公司都在使用vue进行项目开发,我们正在使用的简书,便是基于Vue来构建的. 我们知道,SPA前端渲染存在两大痛点:(1)SEO.搜索引擎爬虫难以抓取客户端渲染的页面meta信息和其他SEO相关信息,使网站无法在搜索引擎中被用户搜索到.(2)用户体验.大型webApp打包之后的js会很庞大,于是就有了按模块加载,像require.js一样,异步请求.webpack盛行,就变成了代码分割.即便如此,受制

  • 从零开始搭建vue移动端项目到上线的步骤

    初始化项目 1.在安装了node.js的前提下,使用以下命令 npm install --g vue-cli 2.在将要构建项目的目录下 vue init webpack myproject(项目目录名称) 一路回车如下 中间会让选择ESLint进行项目代码风格检查,为了美观和效率,可以开起来,vue-router用起来,红框框中的两个测试,不要也罢,后面是问要使用哪个进行install依赖包,默认npm好了:然后回车,等待下载依赖:慢的话可以用镜像. 下载完成之后会看到如下提示: 按照步骤往下

  • 基于 Vue.js 之 iView UI 框架非工程化实践记录(推荐)

    像我们平日里做惯了 Java 或者 .NET 这种后端程序员,对于前端的认识还常常停留在 jQuery 时代,包括其插件在需要时就引用一下,不需要就删除.故观念使然,尽管 Nuget 和 Maven 用得顺溜,但对 NPM 仍不带感,兴许是周边无人带动的稀薄气氛,也或者是没参加过类似的大型活动,于是在自发性上差了许多.再者,我不用 MVVM 模式,领导也不会扣绩效. 为了快速体验 MVVM 模式,我选择了非工程化方式来起步,并选择使用 Vue.js,以及基于它构建的 iView UI 框架. V

  • 在vue+element ui框架里实现lodash的debounce防抖

    事情起因在:我使用element ui框架里的远程搜索框,在单选时,组件内部已经做了防抖,query是在一段时间内的字符串.但是在多选时,并没有做防抖,而是每输入一个字符都要向后台发一次请求,所以必须防抖,官方推荐使用lodash的debounce 在解决这个问题时,我遇到的坎儿主要有以下: 我首先在项目里用npm安装lodash,先全局安装,然后安装到项目 npm install -g lodash npm install --save lodash 安装后,我就在我要用防抖的组件里,引入lo

  • 详解基于vue的服务端渲染框架NUXT

    Nuxt 随着现在vue和react的流行,许多网站都做成了SPA,确实提升了用户体验,但SPA也有两个弱点,就是SEO和首屏渲染速度.为了解决单页应用的痛点,基于vue和react的服务端渲染应运而生.由于公司的框架采用的是vue,所以就简单的研究了一下基于vue的服务端渲染框架--NUXT.在vue的官网有关于服务端渲染的详细介绍,而NUXT集成了利用Vue开发服务端渲染的应用所需要的各种配置,也集成了Vue2.vue-router.vuex.vux-meta(管理页面meta信息的),利用

  • 浅谈Vue CLI 3结合Lerna进行UI框架设计

    当前大部分UI框架设计的Webpack配置都相对复杂,例如 Element. Ant Design Vue和Muse-UI等Vue组件库.例如Element,为了实现业务层面的两种引入形式( 完整引入 和 按需引入 ),以及抛出一些可供业务层面通用的 utils . i18n 等,Webpack配置变得非常复杂.为了简化UI框架的设计难度,这里介绍一种简单的UI框架设计,在此之前先简单介绍一下 Element 的构建流程,以便对比新的UI框架设计. 一般组件库的设计者将引入形式设计成 完整引入

  • vue移动端时弹出侧边抽屉菜单效果

    效果图: aside.vue: <template> <div class="aside"> <div style=" height: 60px; background-color: #2e6baa; line-height: 60px; display: flex; align-items: center; justify-content: center; " > <img src="https://lj-com

  • vue+web端仿微信网页版聊天室功能

    一.项目介绍 基于Vue2.5.6+Vuex+vue-cli+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术混合架构开发的仿微信web端聊天室--vueWebChat,实现了发送消息.表情(动图),图片.视频预览,右键菜单.截屏.截图可直接粘贴至文本框进行发送. 二.技术框架 •MVVM框架:Vue2.5.6 •状态管理:Vuex •页面路由:Vue-router •iconfont图标:阿里巴巴字体图标库 •自定义滚动条:vue-gemi

  • vue移动端下拉刷新和上拉加载的实现代码

    由于自身的项目比较简单,只有几个H5页面,用来嵌入app中,所有没有引入移动端的UI框架,但是介于能让用户在浏览H5页面时有下拉刷新和上拉加载,有更好的用户体验,自己写组件实现. 1.下拉刷新DropDownRefresh.vue <template lang="html"> <div class="refreshMoudle" @touchstart="touchStart($event)" @touchmove="

随机推荐