python 移动ui框架_干货--手把手撸vue移动UI框架: 滑动删除

本文详细介绍了如何使用Vue实现一个移动UI框架中的滑动删除功能,包括DOM结构设计、CSS样式设定和JavaScript交互逻辑。通过监听触摸事件,动态调整元素位置,实现了滑动显示删除按钮并进行删除操作。同时,文章还讨论了如何处理多个活动子项的状态切换,以及如何在父组件中删除子项。
摘要由CSDN通过智能技术生成

前言

前几天因为项目需要,用jquery写了一个swiperOut组件,然后我就随便把这个组件翻译成基于Vue的了,有兴趣的朋友可以看下。Github源码(不麻烦的话帮忙start,请各位大爷赏个星星) demo展示

效果展示

老规矩,先上效果,效果不是很好,大家如果有什么生成gif的好用的软件可以推荐下:

3ba5818cf5061ef1dfb46959fe7482d7.gif

开始制作

DOM结构

分析效果途中的结构,我们可以得出每一项可滑动删除的节点的结构包含如下两部分:

正文部分,显示咱们的主内容

滑动出来的部分(如:删除按钮)

因为使用swiperOut的情景一般都是列表,所以,这里我们用li标签;在实际使用情况中,咱们的content和btns两个容器中的内容是经常会自定义的,所以在这里咱们使用两个插槽slot接受用户的自定义:

删除

这个是咱们每一项的DOM结构,接下来,咱们还需要把这个列表放到一个UL容器中去,我们把UL父容器叫做swiperOut,子列表项li叫做swiperOutItem。swiperOut的DOM结构如下:

css样式

swiperOutItem:

.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;

}

}

我们这里让正文content占据视图的100%,然后按钮容器btns靠右绝对定位,然后再把btns向右移动100%,这样btns就刚好衔接在content后面。当向左滑动的时候,item向左移动,btns显示出来。父容器swiperOut的样式如下:

.r-swiper-out{

position: relative;

width: 100%;

overflow: hidden;

}

javascript

该交互的具体逻辑如下:

自容器向左滑动,容器跟着移动

如果有已经处于打开状态的子项,需要把这个已经打开的子项关闭

当手指释放的时候,判断移动距离是否超过阀值

如果超过阀值,则运用动画,把btns完全显示出来

如果没有,则运用动画返回初始状态

首先当子项初始化的时候我们应该获取btns的宽度,因为这个宽度决定了我们向左最多能滑动多少

export default{

data () {

return {

btnsWidth: 0

}

},

mounted () {

this.$nextTick(() => {

this.btnsWidth = this.$refs.btns.offsetWidth

})

}

}

接下来,咱们应该给item绑定一个样式对象,用来动态控制,子项滑动的距离:

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`

}

}

},

/*...省略之前代码...*

}

接下来给content绑定滑动事件:

export default{

/*...省略之前代码...*

methods: {

touchstart (e) {},

touchmove (e) {},

touchend (e) {}

}

/*...省略之前代码...*

}

接下来完善,touchstart函数:

记录手指开始滑动的坐标

将动画执行事件设置成零

记录当前item的X轴坐标

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

}

}

/*...省略之前代码...*

}

完善我们的核心函数touchmove函数,逻辑如下:

获取手指横向移动距离moveX以及纵向距离moveY

判断手指是横向滑动还是纵向滑动,如果是横向滑动才移动容器,否则假设用户在滚动列表

判定用户在横向滑动,计算出item当前在X轴应该滑动的距离

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

}

}

/*...省略之前代码...*

}

当手指离开屏幕,触发touchend的时候:

判定当前滑动总距离是否超过阀值

超过阀值,显示btns,否则重置item的移动

给item一个动画,关闭或者打开btns

export default{

/*...省略之前代码...*

methods: {

touchend (e) {

let moveX = -this.translateX > 30 ? -this.btnsWidth : 0

this.speed = 300

this.translateX = moveX

}

}

/*...省略之前代码...*

}

到目前为止,咱们完成了item本身的交互逻辑,但是下面这个功能咱们目前还没有实现:

如果有已经处于打开状态的子项,需要把这个已经打开的子项关闭

那么我们应该怎么实现这个功能呢,我的初步设想是:

当用户触发滑动的时候,咱们触发父组件中的一个事件,把自组件本身传递给父组件;父组件记录当前活动的组件,如果之前活动的组件和当前活动的组件不是同一个子项,那么调用子组件自己的重置函数,关闭上一个活动子项,然后根据之前的逻辑移动现在咱们手指触摸的这个组件。代码如下:

swiperOutItem:

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

}

}

/*...省略之前代码...*

}

请注意,在touchmove中咱们增加了一句代码:this.$parent.$emit('changeActiveItem', this)

swiperOut:

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)

}

}

到此为止,咱们这个组件基本上已经完成了,但是还有一个问题,就是咱们应该怎么去删除我们的子项呢?这里咱们还是和上面一样,点击子项的删除按钮,触发父组件,然后在父组件中调用removeChild方法删除子项,具体实现如下:

swiperOut:

export default{

/*...省略之前代码...*

methods: {

childRemove (childNode) {

this.$refs.swiperOut.removeChild(childNode)

}

},

created () {

this.$on('childRemove', this.childRemove)

}

/*...省略之前代码...*

}

swiperOutItem:

export default{

/*...省略之前代码...*

methods: {

delItem () {

this.$parent.$emit('childRemove', this.$el)

}

}

/*...省略之前代码...*

}

咱们在这一小节中只写了各个事件的处理函数,但是在DOM中绑定事件的代码没有写出来大家在跟着写代码的时候,千万不要忘了在DOM中绑定事件哦!!,整理最终代码如下:

swiperOut:

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)

}

}

.r-swiper-out{

position: relative;

width: 100%;

overflow: hidden;

}

swiperOutItem:

@touchstart="touchstart"

@touchmove="touchmove"

@touchend="touchend">

删除

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

})

}

}

.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;

}

}

写在最后

最近好像懒劲犯了,好久都没有更新博客了,谨记谨记!!!!对了,还有就是如果大家对我写的文章有不懂的,或者说希望我怎么写的更通俗易懂的,可以在评论里面评论我!争取以后的写的更能让大家懂我实现的思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值