论如何微信小程序如何优雅的左滑删除列表的卡片

553 阅读4分钟

效果展示

动画.gif

组件地址

组件已上传至码云,需要可自行下载码云地址

解析需求

  • WXML结构由一个大盒子包裹两个小盒子 分别为承载数据的展示盒子以及删除盒子
  • WXSS样式由大盒子负责整体定位居中 给一个相对定位,两个子盒子用绝对定位相对于父盒子布局
  • JS需要给展示盒子绑定三个事件 以达到监听所有的触摸滑动操作做出响应,分别为bindtouchstart,bindtouchmove,bindtouchend,通过滑动的x轴的像素点决定调用创建动画方法将盒子位移到目标位置(实现动画的方式由两种 一种是用css3的动画,还有就是就是js创建动画,这里选用js的方式创建动画并绑定到dom中的方式实现的原因是css3对于动态数据进行动画方面不如js,如果用css3,那么将会大量的使用setDate刷新dom,在js上只需要重新创建动画即可,性能更佳,同时过渡顺滑)非常优雅~

开始撸代码

这个项目我封装成了一个组件 ,一方面是方便复用,二是抽离解耦内聚方便维护

这里使用一个block遍历 list 以index为key循环渲染卡片,item盒子作为父盒子包裹 box以及delete盒子作为结构,box盒子与item盒子等大,这样修改层级将可以让item盒子覆盖delete盒子

WXML代码部分

<block wx:for="{{list}}" wx:key="index">
    <view class="item">
        <view class="box" data-index="{{index}}" bindtouchstart='touchstart' bindtouchmove='touchmove' bindtouchend='touchend' animation="{{item.aniMation}}">
            <image class="img" mode="aspectFit" src="{{item.src}}"></image>
            <view class="msgBox">
                <view class="name">
                    {{item.name}}<text class="dutyName">-{{item.dutyName}}</text>
                </view>
                <view>{{item.phone}}</view>
            </view>
        </view>
        <view class="delete" data-index="{{index}}" bindtap="delete">删除</view>
    </view>
</block>

WXSS代码部分

page给个背景色让卡片由对比 这里统一使用rpx布局 可以适应不同分辨率的设备,主要采用margin+子绝父相的形式布局,注意的是用z-index控制层级 box层级需要大于delete盒子

page {
    background-color: #F5F5F7;
}

.box {
    width: 702rpx;
    height: 180rpx;
    display: flex;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    align-items: center;
    padding: 0 24rpx;
    box-sizing: border-box;
    border-radius: 20rpx;
    background-color: #fff;
    border: 1rpx solid #fff;
}

.delete {
    position: absolute;
    top: 0rpx;
    right: 0rpx;
    z-index: 1;
    width: 180rpx;
    height: 180rpx;
    border-radius: 0 20rpx 20rpx 0;
    background-color: #cd4525;
    color: #fff;
    text-align: center;
    line-height: 180rpx;
}

.img {
    width: 120rpx;
    height: 120rpx;
    border-radius: 8rpx;
    margin-right: 20rpx;

}

.msgBox {
    display: flex;
    flex-direction: column;
    height: 120rpx;
    justify-content: center;
    color: #333333;
    font-size: 28rpx;
}

.name {
    margin-bottom: 10rpx;
}

.dutyName {
    font-size: 24rpx;
    color: #666666;
}

.item {
    position: relative;
    width: 702rpx;
    height: 180rpx;
    margin: 0 auto 20rpx auto;
    display: flex;
    flex-direction: row;
}

JSON代码部分

开启微信的组件功能

{
    "component": true,
    "usingComponents": {}
}

JS代码部分

该组件接受一个list用于循环渲染卡片列表,当点击删除的时候会返回一个delete方法并将删除的数据传递回去供父页面做操作,同时监听了box盒子的三个事件 (坐标是基于左上角为0 ,0 原点开始的)

  • bindtouchstart -> 触摸开始事件 在这个时候记录可能会滑动的卡片index值以及开始触摸点x轴的坐标
  • bindtouchmove -> 触摸滑动事件 当手指按住滑动的时候 会触发这个事件返回最新的x轴坐标 我们要将事件返回的x轴坐标存储起来同时我们判断滑动的值如果大于0那么我们让卡片做一个跟手的动画 将值传递给创造动画的方法中,这里获取到的是px值所以传递createAnimationPX方法让box盒子向左滑动
  • touchend -> 触摸结束事件 当手指离开屏幕的时候会返回离开屏幕的时候最后的坐标 根据坐标计算出移动的距离,通过移动的距离来判断是左移还是复位
// shipment/components/staffBox/staffBox.js
Component({
    /**
     * 组件的属性列表
     */
    properties: {
        list: {
            type: Array,
            value: [{
                    src: 'https://img2.baidu.com/it/u=2783448435,3957437405&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
                    name: '老张',
                    dutyName: '调度',
                    phone: '13197372226',
                    id: 1
                },
                {
                    src: 'https://img2.baidu.com/it/u=2783448435,3957437405&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
                    name: '老王',
                    dutyName: '调度',
                    phone: '13197372226',
                    id: 2
                },
                {
                    src: 'https://img2.baidu.com/it/u=2783448435,3957437405&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
                    name: '老李',
                    dutyName: '调度',
                    phone: '13197372226',
                    id: 3
                },
            ]
        },
    },

    /**
     * 组件的初始数据
     */
    data: {
        index: -1,
        Xstart: 0,
        isAnimation: false,
        aniMation: {},
        id: -1
    },

    /**
     * 组件的方法列表
     */
    methods: {
        getPx(rpx) {
            let px
            wx.getSystemInfo({
                success(res) {
                    px = rpx * (res.windowWidth / 750)
                }
            })
            return Number(px)
        },
        touchstart(e) {
            if (this.data.isAnimation) {
                this.createAnimationPX(0)
            }
            this.setData({
                index: e.currentTarget.dataset.index,
                Xstart: e.changedTouches[0].pageX
            });

        },
        touchmove(e) {
            //  从右往左的移动距离 = 触摸初始位置x坐标 - 当前位置的x坐标
            let move = this.data.Xstart - e.changedTouches[0].pageX;
            // 当从右往左位移大于0px的时候 调用方法创建动画
            if (move > 0) {
                this.createAnimationPX(-move)
            } else {
                // 当从左往右滑动的时候将让卡片复位
                this.createAnimationPX(0)
            }
        },
        touchend(e) {
            //  从右往左的移动距离 = 触摸初始位置x坐标 - 当前位置的x坐标
            let move = this.data.Xstart - e.changedTouches[0].pageX;
            // 当位移大于50px的时候认为展示删除按钮
            if (move > 50) {
                this.createAnimation(-160)
            } else {
                // 复位
                this.createAnimation(0)
            }
        },
        // 创建一个左滑动画基于 rpx单位
        createAnimation(changeX) {
            let list = this.properties.list;
            if (list.length != 0) {
                let ani = wx.createAnimation({
                    delay: 0,
                    timingFunction: 'ease',
                }).translateX(this.getPx(changeX)).step().export()
                list[this.data.index].aniMation = ani
                this.setData({
                    list,
                    isAnimation: true
                })
            }
        },
        // 创建一个左滑的动画 基于px单位
        createAnimationPX(changeX) {
            let list = this.properties.list;
            if (list.length != 0) {
                let ani = wx.createAnimation({
                    delay: 0,
                    timingFunction: 'linear',
                    duration: 45
                }).translateX(changeX).step().export()
                list[this.data.index].aniMation = ani
                this.setData({
                    list,
                    isAnimation: true
                })
            }
        },
        delete(e) {
            let itemIndex = e.currentTarget.dataset.index
            let arr = []
            let list = this.properties.list
            list.map((item, index) => {
                if (index != itemIndex) {
                    arr.push(item)
                }
            })
            this.setData({
                list: arr,
                isAnimation: true
            })
            wx.showToast({
                title: '已成功删除' + list[itemIndex].name,
            })
            this.createAnimation(0)
            this.triggerEvent('delete', {
                date: this.properties.list[itemIndex],
                index: itemIndex,
                list: arr
            })
        }
    }
})