干货--手把手撸vue移动UI框架: 滑动删除
前言
前几天因为项目需要,用jquery写了一个swiperOut组件,然后我就随便把这个组件翻译成基于Vue的了,有兴趣的朋友可以看下。Github源码(不麻烦的话帮忙start,请各位大爷赏个星星) demo展示
效果展示
老规矩,先上效果,效果不是很好,大家如果有什么生成gif的好用的软件可以推荐下:
开始制作
DOM结构
分析效果途中的结构,我们可以得出每一项可滑动删除的节点的结构包含如下两部分:
- 正文部分,显示咱们的主内容
- 滑动出来的部分(如:删除按钮)
<li class="r-swiper-out-item">
<div class="r-swiper-out-item-content"></div>
<div class="r-swiper-out-item-btns"></div>
</li>
因为使用swiperOut的情景一般都是列表,所以,这里我们用li标签;在实际使用情况中,咱们的content和btns两个容器中的内容是经常会自定义的,所以在这里咱们使用两个插槽slot接受用户的自定义:
<li class="r-swiper-out-item">
<div class="r-swiper-out-item-content">
<slot></slot>
</div>
<div class="r-swiper-out-item-btns">
<slot name="btns">
<div class="r-swiper-out-item-btn" @click="delItem">删除</div>
</slot>
</div>
</li>
这个是咱们每一项的DOM结构,接下来,咱们还需要把这个列表放到一个UL容器中去,我们把UL父容器叫做swiperOut,子列表项li叫做swiperOutItem。swiperOut的DOM结构如下:
<ul class="r-swiper-out" ref="swiperOut">
<slot></slot>
</ul>
css样式
swiperOutItem:
<style lang="scss">
.r-swiper-out-item{
position: relative;
&-btns{
display: inline-block;
position: absolute;
right: 0;
top:0;
height: 100%;
transform: translateX(100%);
}
&-btn{
background-color: red;
color: #fff;
width: 100px;
text-align: center;
}
}
</style>
我们这里让正文content占据视图的100%,然后按钮容器btns靠右绝对定位,然后再把btns向右移动100%,这样btns就刚好衔接在content后面。当向左滑动的时候,item向左移动,btns显示出来。父容器swiperOut的样式如下:
<style lang="scss">
.r-swiper-out{
position: relative;
width: 100%;
overflow: hidden;
}
</style>
javascript
该交互的具体逻辑如下:
- 自容器向左滑动,容器跟着移动
- 如果有已经处于打开状态的子项,需要把这个已经打开的子项关闭
- 当手指释放的时候,判断移动距离是否超过阀值
- 如果超过阀值,则运用动画,把btns完全显示出来
- 如果没有,则运用动画返回初始状态
首先当子项初始化的时候我们应该获取btns的宽度,因为这个宽度决定了我们向左最多能滑动多少
<script>
export default{
data () {
return {
btnsWidth: 0
}
},
mounted () {
this.$nextTick(() => {
this.btnsWidth = this.$refs.btns.offsetWidth
})
}
}
</script>
接下来,咱们应该给item绑定一个样式对象,用来动态控制,子项滑动的距离:
<script>
export default{
data () {
return {
btnsWidth: 0
startX: 0,
translateX: 0,
}
},
computed: {
itemStyle () {
return {
transform: `translate3d(${this.translateX}px, 0, 0)`,
transition: `all ${this.speed}ms`
}
}
},
/*...省略之前代码...*
}
</script>
接下来给content绑定滑动事件:
<script>
export default{
/*...省略之前代码...*
methods: {
touchstart (e) {},
touchmove (e) {},
touchend (e) {}
}
/*...省略之前代码...*
}
</script>
接下来完善,touchstart函数:
- 记录手指开始滑动的坐标
- 将动画执行事件设置成零
- 记录当前item的X轴坐标
<script>
export default{
data () {
return {
speed: 300,
startX: 0,
translateX: 0,
oldPoint: null,
btnsWidth: 0
}
},
methods: {
touchstart (e) {
this.oldPoint = e.touches[0]
this.speed = 0
this.startX = this.translateX
}
}
/*...省略之前代码...*
}
</script>
完善我们的核心函数touchmove函数,逻辑如下:
- 获取手指横向移动距离moveX以及纵向距离moveY
- 判断手指是横向滑动还是纵向滑动,如果是横向滑动才移动容器,否则假设用户在滚动列表
- 判定用户在横向滑动,计算出item当前在X轴应该滑动的距离
<script>
export default{
/*...省略之前代码...*
methods: {
touchmove (e) {
let moveX = e.touches[0].pageX - this.oldPoint.pageX
let moveY = e.touches[0].pageY - this.oldPoint.pageY
if (Math.abs(moveX) < Math.abs(moveY) || Math.abs(moveX) < 20 || Math.abs(moveY) > 30) return
e.preventDefault()
moveX = this.startX * 1 + moveX * 1
if (moveX < -this.btnsWidth) {
moveX = -this.btnsWidth
} else if (moveX > 0) {
moveX = 0
}
this.translateX = moveX
}
}
/*...省略之前代码...*
}
</script>
当手指离开屏幕,触发touchend的时候:
- 判定当前滑动总距离是否超过阀值
- 超过阀值,显示btns,否则重置item的移动
- 给item一个动画,关闭或者打开btns
<script>
export default{
/*...省略之前代码...*
methods: {
touchend (e) {
let moveX = -this.translateX > 30 ? -this.btnsWidth : 0
this.speed = 300
this.translateX = moveX
}
}
/*...省略之前代码...*
}
</script>
到目前为止,咱们完成了item本身的交互逻辑,但是下面这个功能咱们目前还没有实现:
- 如果有已经处于打开状态的子项,需要把这个已经打开的子项关闭
那么我们应该怎么实现这个功能呢,我的初步设想是:
当用户触发滑动的时候,咱们触发父组件中的一个事件,把自组件本身传递给父组件;父组件记录当前活动的组件,如果之前活动的组件和当前活动的组件不是同一个子项,那么调用子组件自己的重置函数,关闭上一个活动子项,然后根据之前的逻辑移动现在咱们手指触摸的这个组件。代码如下:
swiperOutItem:
<script>
export default{
/*...省略之前代码...*
methods: {
touchmove (e) {
let moveX = e.touches[0].pageX - this.oldPoint.pageX
let moveY = e.touches[0].pageY - this.oldPoint.pageY
if (Math.abs(moveX) < Math.abs(moveY) || Math.abs(moveX) < 20 || Math.abs(moveY) > 30) return
e.preventDefault()
this.$parent.$emit('changeActiveItem', this)
moveX = this.startX * 1 + moveX * 1
if (moveX < -this.btnsWidth) {
moveX = -this.btnsWidth
} else if (moveX > 0) {
moveX = 0
}
this.translateX = moveX
}
}
/*...省略之前代码...*
}
</script>
请注意,在touchmove中咱们增加了一句代码:this.$parent.$emit('changeActiveItem', this)
swiperOut:
<script>
export default{
data () {
return {
activeItem: null
}
},
methods: {
changeActiveItem (item) {
if (this.activeItem === item) return
if (this.activeItem && this.activeItem.close) {
this.activeItem.close()
}
this.activeItem = item
}
},
created () {
this.$on('changeActiveItem', this.changeActiveItem)
}
}
</script>
到此为止,咱们这个组件基本上已经完成了,但是还有一个问题,就是咱们应该怎么去删除我们的子项呢?这里咱们还是和上面一样,点击子项的删除按钮,触发父组件,然后在父组件中调用removeChild方法删除子项,具体实现如下:
swiperOut:
<script>
export default{
/*...省略之前代码...*
methods: {
childRemove (childNode) {
this.$refs.swiperOut.removeChild(childNode)
}
},
created () {
this.$on('childRemove', this.childRemove)
}
/*...省略之前代码...*
}
</script>
swiperOutItem:
<script>
export default{
/*...省略之前代码...*
methods: {
delItem () {
this.$parent.$emit('childRemove', this.$el)
}
}
/*...省略之前代码...*
}
</script>
咱们在这一小节中只写了各个事件的处理函数,但是在DOM中绑定事件的代码没有写出来大家在跟着写代码的时候,千万不要忘了在DOM中绑定事件哦!!,整理最终代码如下:
swiperOut:
<template>
<ul class="r-swiper-out" ref="swiperOut">
<slot></slot>
</ul>
</template>
<script>
export default{
data () {
return {
activeItem: null
}
},
methods: {
changeActiveItem (item) {
if (this.activeItem === item) return
if (this.activeItem && this.activeItem.close) {
this.activeItem.close()
}
this.activeItem = item
},
childRemove (childNode) {
this.$refs.swiperOut.removeChild(childNode)
}
},
created () {
this.$on('changeActiveItem', this.changeActiveItem)
this.$on('childRemove', this.childRemove)
}
}
</script>
<style lang="scss">
.r-swiper-out{
position: relative;
width: 100%;
overflow: hidden;
}
</style>
swiperOutItem:
<template>
<li class="r-swiper-out-item" :style="itemStyle">
<div class="r-swiper-out-item-content" ref="content"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend">
<slot></slot>
</div>
<div class="r-swiper-out-item-btns" ref="btns">
<slot name="btns">
<div class="r-swiper-out-item-btn" @click="delItem">删除</div>
</slot>
</div>
</li>
</template>
<script>
export default{
data () {
return {
speed: 300,
startX: 0,
translateX: 0,
oldPoint: null,
btnsWidth: 0
}
},
computed: {
itemStyle () {
return {
transform: `translate3d(${this.translateX}px, 0, 0)`,
transition: `all ${this.speed}ms`
}
}
},
methods: {
touchstart (e) {
this.oldPoint = e.touches[0]
this.speed = 0
this.startX = this.translateX
},
touchmove (e) {
let moveX = e.touches[0].pageX - this.oldPoint.pageX
let moveY = e.touches[0].pageY - this.oldPoint.pageY
if (Math.abs(moveX) < Math.abs(moveY) || Math.abs(moveX) < 20 || Math.abs(moveY) > 30) return
e.preventDefault()
this.$parent.$emit('changeActiveItem', this)
moveX = this.startX * 1 + moveX * 1
if (moveX < -this.btnsWidth) {
moveX = -this.btnsWidth
} else if (moveX > 0) {
moveX = 0
}
this.translateX = moveX
},
touchend (e) {
let moveX = -this.translateX > 30 ? -this.btnsWidth : 0
this.speed = 300
this.translateX = moveX
},
close () {
this.translateX = 0
},
delItem () {
this.$parent.$emit('childRemove', this.$el)
}
},
mounted () {
this.$nextTick(() => {
this.btnsWidth = this.$refs.btns.offsetWidth
})
}
}
</script>
<style lang="scss">
.r-swiper-out-item{
position: relative;
&-btns{
display: inline-block;
position: absolute;
right: 0;
top:0;
height: 100%;
transform: translateX(100%);
}
&-btn{
background-color: red;
color: #fff;
width: 100px;
text-align: center;
}
}
</style>
写在最后
最近好像懒劲犯了,好久都没有更新博客了,谨记谨记!!!!对了,还有就是如果大家对我写的文章有不懂的,或者说希望我怎么写的更通俗易懂的,可以在评论里面评论我!争取以后的写的更能让大家懂我实现的思路。
干货--手把手撸vue移动UI框架: 滑动删除的更多相关文章
- 基于Vue的Ui框架
基于Vue的Ui框架 饿了么公司基于vue开的的vue的Ui组件库 Element Ui 基于vue pc端的UI框架 http://element.eleme.io/ MintUi 基于vue 移动 ...
- element-ui iview-admin 都是基于vue的ui框架
element-ui iview-admin 都是基于vue的ui框架
- 5款vue前端UI框架
Vue.js是一套构建用户界面的 渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计. 实用的 Vue.js组件库可以帮助我们快速搭建页面,下面介绍小编认为比较受欢迎的五个vue ...
- 基于vue的UI框架集锦
前端框架百花齐放.争奇斗艳,令人眼花缭乱.大神们一言不合就整一个框架出来,另小白们无所适从.下面罗列了一些比较优秀的UI框架,Star多的大都是老牌劲旅,Star少的许多是后起之秀. (1)Eleme ...
- 很受欢迎的vue前端UI框架
最近在逛各大网站,论坛,SegmentFault等编程问答社区,发现Vue.js异常火爆,重复性的提问和内容也很多,小编自己也趁着这个大前端的热潮,着手学习了一段时间的Vue.js,目前用它正在做自己 ...
- 2018年九个很受欢迎的vue前端UI框架
最近在逛各大网站,论坛,SegmentFault等编程问答社区,发现Vue.js异常火爆,重复性的提问和内容也很多,小编自己也趁着这个大前端的热潮,着手学习了一段时间的Vue.js,目前用它正在做自己 ...
- 一起学Vue:UI框架(element-ui)
目标 使用Vue+ElementUI构建一个非常简单CRUD应用程序,以便您更好地了解它的工作方式. 效果页面 比如我们要实现这样列表.新增.编辑三个页面: 列表页面 新增页面 编辑页面 安装elem ...
- [干货分享]AXURE整套高保真UI框架和元件组(白色风格)
写在前面 强烈建议开始之前阅读以下第一篇高保真UI框架的前面部分,以了解设计思想,这篇文章不再重复介绍: AXURE-整套可复用的高保真元件和框架之暗黑风格 本次共享模板的UI规范 注:由于篇幅问 ...
- [干货分享] AXURE-整套高保真UI框架和元件组(暗黑风格)
写在前面 在我们的开发团队里,一般在产品通过策划和需求评审后,在还没开始设计之前,产品经理和美工会一起定一套UI规范. 一方面用于规范整体界面,防止界面开发过程中出现UI不一致性的情况(有时候标准 ...
随机推荐
- [JS]實作LinkedList鏈結串列
由於自身資料結構的基礎薄弱,買了一本JavaScript資料結構與演算法實作的書來看,重新把LinkedList鏈結串列學習了一遍,並用JS實作出來. LinkedList鏈結串列 要存放多個元素,最 ...
- 31)PHP,对象的遍历
对象的遍历: 对象也可以可以使用foreach语句进行便利,有两点注意: 1,只能便利属性.(所以,这个就解决了,为啥之前的数据库类,我只是看到了一些属性名字,而没有得到我的属性值) 2,只能便利“看 ...
- sql 坐标距离排序计算距离(转)
如果两个坐标的列是(x1,y1).(x2,y2),那么他们之间的距离:SQRT((X1-X2)*(X1-X2)+(Y1-Y2)*(Y1-Y2)) sql排序 SELECT * FROM m_store ...
- ubantu中的mysql命令
查看mysql的安装目录:which mysql 进入mysql的运行状态:mysql -uroot -p 56..a_
- Django学习之路由层
Django请求生命周期 - wsgi, 他就是socket服务端,用于接收用户请求并将请求进行初次封装,然后将请求交给web框架(Flask.Django) - 中间件,帮助我们对请求进行校验或在请 ...
- 未来科技城 x 奇点云打造「企业数据大脑」,助力1.3万家企业服务
“当前,政府数字化和数字政府建设已成为一种趋势.一种必然,并且有了一条水到渠成式的实现路径.” 上升为国家战略的数字中国建设加速了”智慧政务“的生动实践,杭州未来科技城的「企业数据大脑」就是一个典型. ...
- asp.net 获取日期
//获取日期+时间 DateTime.Now.ToString(); // 2008-9-4 20:02:10 DateTime.Now.ToLocalTime().ToString(); // 20 ...
- Fire-Fighting Hero(多源最短路和单源最短路)
题:https://nanti.jisuanke.com/t/41349 分析:对于hero来说,走单源最短路,然后遍历dis数组中的最大值即可找到,对于消防员来说,走多源最短路,只需要建个超级起点连 ...
- EMCCD
EMCCD 即电子倍增CCD,是探测领域内灵敏度极高的一种高端光电探测产品. 在光子探测领域的应用发展对探测器灵敏度的要求不断提高,EMCCD (Electron-Multiplying CCD)技术 ...
- 使用Cron表达式创建定时任务
CronTriggerCronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用.CroTr ...