序言:

年后入职了一家新公司,与前同事交接完之后,发现公司有一个四端的项目(iOS,Android,H5,小程序),iOS和安卓都实现了左滑右滑的效果,而h5和小程序端没实现,询问得知前同事因网上没找到对应的插件相关博客也比较少,加上公司任务比较紧,所以没做就搁置下来了。

利用闲暇时间,于是乎在网上也搜索了一下,发现相关博客确实很少,但是有人提到可以用小程序可拖动组件movable-view来实现,自己尝试来一下发现可行,于是来写这篇博客记录一下,希望能帮助到后面需要用到这个功能的人!

先上效果图:展示了左滑,右滑,点击喜欢按钮,不喜欢按钮分别的效果;

主要技术:Taro+Taro UI+React(如果你是小程序原生或者uniapp+vue写法都差不多,可以通用)

可拖动组件文档地址:

Taro:     https://taro-docs.jd.com/taro/docs/components/viewContainer/movable-view.html

微信小程序:    https://developers.weixin.qq.com/miniprogram/dev/component/movable-view.html

思路:

一,我们首先把movable-area和movable-view标签写出来;

<movable-area>
<movable-view>
......
</movable-view>
</movable-area>

二,我们可以看到文档里面有一个onChange方法,即拖动过程中触发的事件;

<movable-area>
<movable-view onChange ={this. onChange.bind(this)}>
......
</movable-view>
</movable-area> // 触发方法,打印参数
onChange(e) {
console.log('参数',e);
}

我们可以看到打印出了,拖动的位置和产生移动的原因等;

三,我们接着加入开始onTouchstart,移动onTouchmove,结束onTouchcancel,onTouchend三个事件方法;

<MovableView
key={item.id}
onTouchcancel={this.onCancel}
onTouchend={this.onCancel}
onTouchstart={this.onTouchStart}
onTouchmove={this.onTouchMove}
x={this.state.x} // 横坐标位置
y={this.state.y} // 纵坐标位置
direction='all' // 移动方向都可以
outOfBounds // 可超过可移动区域
className='shop-imgbox'
>
<--中间加入图片之类的滑动内容-->
</MovableView>

初始数据如下:

state = {
x: '16',
y: '16',
like: false,
unlike: false,
shopList: [
{
img: 'https://edgefix-image.edgecom.top/ABD846F6672997A7F76CD38E8A57F954.jpg',
},
{
img: 'https://edgefix-image.edgecom.top/F6E5801C304CC76DA63C02C9FB38B8F4.jpg',
},
{
img: 'https://edgefix-image.edgecom.top/D518952AD1DD61B2D32556E20CC527C4.jpg',
},
{
img: 'https://edgefix-image.edgecom.top/1D187E28B349679908A44BBE81F3D3CA.jpg',
},
{
img: 'https://edgefix-image.edgecom.top/1129A411AC9CF5F81187CBED181B6F57.jpg',
}
]
}

三个方法我们可以取到移动后改变的位置,来改变喜欢与不喜欢的状态css,以及实现卡片滑动的效果:

1. 触摸触发的时候,我们获取到刚刚开始触摸卡片的x,y的位置坐标;

2. 在触摸滑动时,我们通过滑动后的位置-滑动前的位置,来判断距离多少来改变喜欢和不喜欢的值;

3. 当手离开时,触发取消事件,我们需要把状态数据改为原始值,即回到最初的状态;

// 触摸触发
onTouchStart(e) {
console.log('222',e.touches[0].pageX);
this.setState({
x: e.touches[0].pageX,
y: e.touches[0].pageY,
});
}
// 触摸移动
onTouchMove(e) {
console.log('333',e.touches[0].pageX);
let dx = e.touches[0].pageX - this.state.x;
if (dx > 50) {
this.setState({
like: true,
unlike: false,
});
} else if (dx < -50) {
this.setState({
like: false,
unlike: true,
});
} else {
this.setState({
like: false,
unlike: false,
});
}
}
// 取消
onCancel(e) {
console.log('444',e.changedTouches[0].pageX);
this.setState({
x: '16',
y: '16',
like: false,
unlike: false,
});
}

当我们写到这里,我们去拖动我们的卡片时,你会发现确实可以拖动,并且取消的时候会回到原点,但是同样你也会发现一个问题,就是你拖动的时候,五张卡片都被触发来移动的效果,出现了触点混乱的问题,查找问题发现卡片共用了x,y,因此我们可以给每张卡片设置独立的参数;

四,给每张卡片独立的参数并且设置卡片倾斜度效果;

1.设置倾斜度效果

style={{transform:'rotate('+this.state.tiltAngle[index]+'deg)'}}

然后我们通过卡片移动位置计算出一个你决定合适的倾斜角度;

// 拖动后相差距离进行换算角度
let dxangle = (e.touches[0].pageX - this.state.startX) * 45 / 500;

2.设置独立的参数

方法携带索引,我们取到对应的卡片index,来改变对应卡片的数据;

<MovableView
key={item.id}
onTouchcancel={this.onCancel.bind(this,index)}
onTouchend={this.onCancel.bind(this,index)}
onTouchstart={this.onTouchStart.bind(this,index)}
onTouchmove={this.onTouchMove.bind(this,index)}
x={this.state.x[index]}
y={this.state.y[index]}
direction='all'
outOfBounds
className='shop-imgbox'
>
</MovableView>

同时,我们需要改变初始参数的形式为数组,我们通过索引改变对应卡片的值;

state = {
// 开始位置
startX: '',
// 开始位置-最终位置距离
placeX: '',
// 倾斜角度
tiltAngle: ['0','0','0','0','0'],
// 坐标
x: ['16','16','16','16','16'],
y: ['16','16','16','16','16'],
// 是否喜欢状态
like: [false,false,false,false,false],
unlike: [false,false,false,false,false],
// 推荐商品数组
shopList: [
{
id: 1,
img: 'https://edgefix-image.edgecom.top/ABD846F6672997A7F76CD38E8A57F954.jpg',
},
{
id: 2,
img: 'https://edgefix-image.edgecom.top/F6E5801C304CC76DA63C02C9FB38B8F4.jpg',
},
{
id: 3,
img: 'https://edgefix-image.edgecom.top/D518952AD1DD61B2D32556E20CC527C4.jpg',
},
{
id: 4,
img: 'https://edgefix-image.edgecom.top/1D187E28B349679908A44BBE81F3D3CA.jpg',
},
{
id: 5,
img: 'https://edgefix-image.edgecom.top/1129A411AC9CF5F81187CBED181B6F57.jpg',
}
]
}

方法我们就举一个例子,比如onTouchStart方法,我们遍历卡片数组,通过判断索引来得到是那张卡片,从而来改变对应值

// 触摸触发
onTouchStart(index,e) {
console.log('1111',index,e.touches[0].pageX,e.touches[0].pageY);
// 重定义数组
var againX = [];
var againY = [];
// 遍历,判断拖动的该数组的位置
for (var i=0; i<this.state.shopList.length; i++){
if (i == index) {
againX[i] = e.touches[0].pageX;
againY[i] = e.touches[0].pageY;
} else {
againX[i] = '16';
againY[i] = '16';
}
}
// 赋值
this.setState({
startX: e.touches[0].pageX,
x: againX,
y: againY,
});
}

这样,我们运行代码,发现拖动第一张卡片不会影响到后面卡片的位置了,

同时,我们现在拖动卡片删除的是数组,在实际项目中,我们在触发删除数组的地方接入接口,调用喜欢,不喜欢改变数据参数,从而也能改变数组的长度;

五,完整代码;

下面我将贴出完整的代码供大家参考

html文件:

import Taro, { Component } from '@tarojs/taro';
import { View, Image, Button, Text, MovableArea, MovableView } from '@tarojs/components';
import { observer, inject } from '@tarojs/mobx';
import { AtButton, AtFloatLayout } from 'taro-ui';
import userStore from '../../store/user.store'; import './stroll.scss'; @inject('userStore')
@observer
class Stroll extends Component {
config = {
navigationBarTitleText: '逛',
} state = {
// 开始位置
startX: '',
// 开始位置-最终位置距离
placeX: '',
// 倾斜角度
tiltAngle: ['0','0','0','0','0'],
// 坐标
x: ['16','16','16','16','16'],
y: ['16','16','16','16','16'],
// 是否喜欢状态
like: [false,false,false,false,false],
unlike: [false,false,false,false,false],
// 推荐商品数组
shopList: [
{
id: 1,
img: 'https://edgefix-image.edgecom.top/ABD846F6672997A7F76CD38E8A57F954.jpg',
},
{
id: 2,
img: 'https://edgefix-image.edgecom.top/F6E5801C304CC76DA63C02C9FB38B8F4.jpg',
},
{
id: 3,
img: 'https://edgefix-image.edgecom.top/D518952AD1DD61B2D32556E20CC527C4.jpg',
},
{
id: 4,
img: 'https://edgefix-image.edgecom.top/1D187E28B349679908A44BBE81F3D3CA.jpg',
},
{
id: 5,
img: 'https://edgefix-image.edgecom.top/1129A411AC9CF5F81187CBED181B6F57.jpg',
}
]
} componentWillMount () { } componentWillReact () { } componentDidMount () {
} // 触摸触发
onTouchStart(index,e) {
console.log('1111',index,e.touches[0].pageX,e.touches[0].pageY);
// 重定义数组
var againX = [];
var againY = [];
// 遍历,判断拖动的该数组的位置
for (var i=0; i<this.state.shopList.length; i++){
if (i == index) {
againX[i] = e.touches[0].pageX;
againY[i] = e.touches[0].pageY;
} else {
againX[i] = '16';
againY[i] = '16';
}
}
// 赋值
this.setState({
startX: e.touches[0].pageX,
x: againX,
y: againY,
});
}
// 触摸离开
onTouchMove(index,e) {
console.log('2222',index,e.touches[0].pageX,e.touches[0].pageY);
// 重定义数组
var tiltAngleT = [];
var againX = [];
var againY = [];
// 拖动后相差距离
let dxplace = e.touches[0].pageX - this.state.startX;
// 拖动后相差距离进行换算角度
let dxangle = (e.touches[0].pageX - this.state.startX) * 45 / 500;
console.log(dxangle);
// 遍历,判断拖动的该数组的位置
for (var i=0; i<this.state.shopList.length; i++){
if (i == index && dxplace > 50) {
tiltAngleT[i] = dxangle,
againX[i] = true;
againY[i] = false;
} else if (i == index && dxplace <= -50) {
tiltAngleT[i] = dxangle,
againX[i] = false;
againY[i] = true;
} else if (i == index && dxplace < 50 && dxplace > -50) {
tiltAngleT[i] = dxangle,
againX[i] = false;
againY[i] = false;
} else {
tiltAngleT[i] = '0',
againX[i] = false;
againY[i] = false;
}
}
// 赋值
this.setState({
placeX: dxplace,
tiltAngle: tiltAngleT,
like: againX,
unlike: againY,
});
}
// 取消
onCancel(index,e) {
console.log('3333',index,e.changedTouches[0].pageX,e.changedTouches[0].pageY);
// 赋值
this.setState({
tiltAngle: ['0','0','0','0','0'],
x: ['16','16','16','16','16'],
y: ['16','16','16','16','16'],
like: [false,false,false,false,false],
unlike: [false,false,false,false,false],
});
// 如果偏移已经达到则清除第一张图片
if (this.state.placeX > 50 || this.state.placeX < -50) {
this.setState({
shopList: this.state.shopList.splice(1,4),
});
}
}
// 不喜欢按钮点击
dislikebtn() {
// 改变按钮的状态以及图片位置及显示
this.setState({
tiltAngle: ['-18','0','0','0','0'],
x: ['-30','16','16','16','16'],
y: ['267','16','16','16','16'],
unlike: [true,false,false,false,false],
}, () => {
setTimeout( () => {
this.setState({
tiltAngle: ['0','0','0','0','0'],
x: ['16','16','16','16','16'],
y: ['16','16','16','16','16'],
unlike: [false,false,false,false,false],
shopList: this.state.shopList.splice(1,4),
});
},100);
});
}
// 喜欢按钮点击
likebtn() {
// 改变按钮的状态以及图片位置及显示
this.setState({
tiltAngle: ['18','0','0','0','0'],
x: ['284','16','16','16','16'],
y: ['267','16','16','16','16'],
like: [true,false,false,false,false],
}, () => {
setTimeout( () => {
this.setState({
tiltAngle: ['0','0','0','0','0'],
x: ['16','16','16','16','16'],
y: ['16','16','16','16','16'],
like: [false,false,false,false,false],
shopList: this.state.shopList.splice(1,4),
});
},100);
});
} componentWillUnmount () { } componentDidShow () {
} componentDidHide () { } render() {
return (
<View className='stroll-tab'>
<View className='stroll-text'>
<Text className='text-tip1'>搭配师每天为你推荐5件单品</Text>
<View className='text-tip2'>
<Text className='t1'>右滑喜欢</Text>
<Image src={require('./img/ic_like.png')} className='icon-image'></Image>
<Text className='t1'>,左滑不喜欢</Text>
<Image src={require('./img/ic_dislike.png')} className='icon-image'></Image>
</View>
</View>
{
this.state.shopList.length != 0&&
<MovableArea className='stroll-shop'>
{
this.state.shopList&&this.state.shopList.map((item,index) => {
return(
<MovableView
key={item.id}
onTouchcancel={this.onCancel.bind(this,index)}
onTouchend={this.onCancel.bind(this,index)}
onTouchstart={this.onTouchStart.bind(this,index)}
onTouchmove={this.onTouchMove.bind(this,index)}
x={this.state.x[index]}
y={this.state.y[index]}
direction='all'
outOfBounds
className='shop-imgbox'
>
<View className='images-box' style={{transform:'rotate('+this.state.tiltAngle[index]+'deg)'}}>
<Image src={item.img} className='images'></Image>
{
this.state.like[index]==true&&
<Image src={require('./img/text_like.png')} className='imagelike'></Image>
}
{
this.state.unlike[index]==true&&
<Image src={require('./img/text_dislike.png')} className='imageunlike'></Image>
}
</View>
</MovableView>
);})
}
</MovableArea>
}
{
this.state.shopList.length === 0&&
<View className='noshop-card'>
<Image src={require('./img/noshop.png')} className='noshop-image'></Image>
</View>
}
<View className='stroll-fotter'>
{
this.state.shopList.length != 0&&
<View className='fot-twoimg'>
{
this.state.unlike[0]==false&&
<Image src={require('./img/dislike_default.png')} className='dislike-image' onClick={this.dislikebtn.bind(this)}></Image>
}
{
this.state.unlike[0]==true&&
<Image src={require('./img/dislike_click.png')} className='dislike-image'></Image>
}
{
this.state.like[0]==false&&
<Image src={require('./img/like_default.png')} className='like-image' onClick={this.likebtn.bind(this)}></Image>
}
{
this.state.like[0]==true&&
<Image src={require('./img/like_click.png')} className='like-image'></Image>
}
</View>
}
<Text className='fot-text'>查看我喜欢的</Text>
</View>
</View>
);
}
} export default Stroll;

css文件:

page {
height: 100%;
background: #F6F6F6;
} .stroll-tab {
width: 100%;
min-height: 100vh;
background: #F6F6F6;
.stroll-text {
width: 100%;
margin-top: 40px;
display: flex;
flex-direction: column;
align-items: center;
.text-tip1 {
font-size: 28px;
color: #333333;
}
.text-tip2 {
display: flex;
flex-direction: row;
align-items: center;
.t1 {
font-size: 28px;
color: #333333;
}
.icon-image {
width:20px;
height:20px;
}
}
}
.stroll-shop {
width: 100%;
height: 700px;
margin-top: 40px;
.shop-imgbox {
height: 600px;
border-radius: 24px;
.images-box {
width: 100%;
height: 520px;
border-radius: 24px;
box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.1);
background-color: #fff;
position: relative;
.images {
width: 606px;
height: 480px;
position: absolute;
left: 40px;
top: 20px;
}
.imagelike {
width: 96px;
height: 48px;
position: absolute;
right: 40px;
top: 20px;
}
.imageunlike {
width: 148px;
height: 48px;
position: absolute;
left: 40px;
top: 20px;
}
}
}
.shop-imgbox:nth-child(1) {
width: 686px;
z-index:;
}
.shop-imgbox:nth-child(2) {
width: 676px;
z-index:;
margin: 15px 0px 0px 5px;
}
.shop-imgbox:nth-child(3) {
width: 666px;
z-index:;
margin: 30px 0px 0px 10px;
}
.shop-imgbox:nth-child(4) {
width: 656px;
z-index:;
margin: 0px 0px 0px 15px;
}
.shop-imgbox:nth-child(5) {
width: 646px;
z-index:;
margin: 0px 0px 0px 20px;
}
}
.noshop-card {
width: 100%;
margin-top: 40px;
padding: 0px 16px;
.noshop-image {
width: 100%;
height: 806px;
}
}
.stroll-fotter {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
.fot-twoimg {
display: flex;
flex-direction: row;
align-items: center;
.dislike-image {
width: 120px;
height: 120px;
}
.like-image {
width: 120px;
height: 120px;
margin-left: 48px;
}
}
.fot-text {
color: #368BE5;
font-size: 28px;
margin-top: 40px;
margin-bottom: 50px;
}
}
}

好了,小程序左滑右滑效果就说到这里了,如果大家有更好的办法请在下方留言,如果有什么不懂的可以在下面提问,有时间我会一一回复的,谢谢了!

跋尾:文章发布没多久,去百度上检索就发现我的知识成果迅速被剽窃了,由于不是博客园内部文章,相关工作人员也管不了,所以,只能在这里对剽窃别人知识成果的行为表示深深的鄙视,地址就不贴出来了,免得给他们增加流量;

Taro UI开发小程序实现左滑喜欢右滑不喜欢效果的更多相关文章

  1. 迅速上手:使用taro构建微信小程序基础教程

    前言 由于微信小程序在开发上不能安装npm依赖,和开发流程上也饱受诟病:Taro 是由京东·凹凸实验室(aotu.io)倾力打造的 多端开发解决方案,它的api基于react,在本篇文章中主要介绍了使 ...

  2. 使用mpvue开发小程序

    前言: 最近接到小程序的开发需求,由于之前也没开发过小程序,心情还是有点激动.先花15分钟看一遍小程序官方文档,再花10分钟看一遍mpvue官方文档,然后拿着原型图和UI图就开干.踩了不少坑,写篇博客 ...

  3. 如何用Baas快速在腾讯云上开发小程序-系列4:实现客户侧商品列表、商品详情页程序

    版权声明:本文由贺嘉 原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/431172001487671163 来源:腾云阁 h ...

  4. 用RegularJS开发小程序 — mpregular解析

    本文来自网易云社区. Mpregular 是基于 RegularJS(简称 Regular) 的小程序开发框架.开发者可以将直接用 RegularJS 开发小程序,或者将现有的 RegularJS 应 ...

  5. 【Taro全实践】Taro在微信小程序中的生命周期

    一.Taro的本身生命周期 生命周期componentWillMount在微信小程序中这一生命周期方法对应页面的onLoad或入口文件app中的onLaunch componentDidMount在微 ...

  6. 使用mpvue开发小程序教程(一)

    前段时间,美团开源了mpvue这个项目,使得我们又多了一种用来开发小程序的框架选项.由于mpvue框架是完全基于Vue框架的(重写了其runtime和compiler),因此在用法上面是高度和Vue一 ...

  7. 使用mpvue开发小程序教程(二)

    在上篇文章中,我们介绍了使用mpvue开发小程序所需要的一些开发环境的搭建,并创建了第一个mpvue小程序代码骨架并将其运行起来.在本文中,我们来研究熟悉一下mpvue项目的主要目录和文件结构. 在V ...

  8. 使用mpvue开发小程序教程(三)

    在上一篇文章中,我们熟悉了一下通过vue-cli生成的mpvue工程代码骨架的基本结构,大致了解了每一个部分的代码到底要放到何处.从本文起我们就开始涉及真正的编码部分,学习使用Vue的语法去编写小程序 ...

  9. 使用mpvue开发小程序教程(四)

    在上一章节中,我们将vue-cli命令行工具生成的代码骨架中的src目录清理了一遍,然后从头开始配置和编写了一个可以运行的小程序页面,算是正真走上了使用mpvue开发小程序的第一步.今天我们将进一步来 ...

随机推荐

  1. jmeter if控制器使用

    if控制器有两种用法 1.不勾选“interpret condition as variable expression” 直接输入我们需要判断的表达式即可,判断表达式为真时,执行if控制器下的请求 2 ...

  2. 开源软件SoftEther使用

    最近在寻找比较好用的开源VPN,感觉SoftEther很符合我的需求.一方面是SoftEther属于开源软件并且一直在更新,另一方面是功能强大,好用. VPN支持路由功能和NAT功能,还支持多种类型的 ...

  3. Springboot:员工管理之环境准备(十(1))

    1:静态资源 下载静态资源:https://files.cnblogs.com/files/applesnt/ztzy.zip 项目下载:https://files.cnblogs.com/files ...

  4. 在项目中部署redis的读写分离架构(包含节点间认证口令)

    #### 在项目中部署redis的读写分离架构(包含节点间认证口令) ##### 1.配置过程 ---  1.此前就是已经将redis在系统中已经安装好了,redis utils目录下,有个redis ...

  5. git .gitignore不生效

    原因是.gitignore只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的. 解决方法: 1.先把规则写好,然后把规则对应的文件删了,然后 ...

  6. MySql --FIND_IN_SET() 函数 (转)

    例子:https://www.jianshu.com/p/b2c1ba0ba34f 举个例子来说:有个文章表里面有个type字段,他存储的是文章类型,有 1头条,2推荐,3热点,4图文 .....11 ...

  7. window servet 2012 r2 配置php服务器环境

    绑定:https://jingyan.baidu.com/article/0bc808fc2c6a851bd485b92a.html 配置环境:http://www.jb51.net/article/ ...

  8. 2019-2020-1 20199328《Linux内核原理与分析》第八周作业

    笔记部分 2019/11/4 17:55:22 elf文件代码默认加载到0x8048000,然后是一段首部信息,然后到达程序的真实入口 正常的系统调用会先进入内核态->用户态->系统调用下 ...

  9. Spring Boot中的Properties

    文章目录 简介 使用注解注册一个Properties文件 使用属性文件 Spring Boot中的属性文件 @ConfigurationProperties yaml文件 Properties环境变量 ...

  10. Golang——Cron 定时任务

    开门见山写一个 package main import ( "fmt" "github.com/robfig/cron" "log" &qu ...