效果展示
组件地址
组件已上传至码云,需要可自行下载码云地址
解析需求
- 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
})
}
}
})