Taro 就是可以用 React 语法写小程序的框架,拥有多端转换能力,一套代码可编译为微信小程序、百度小程序、支付宝小程序、H5、RN等
摘要:
年后入职了一家新公司,与前同事交接完之后,发现公司有一个四端的项目(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: 50; } .shop-imgbox:nth-child(2) { width: 676px; z-index: 40; margin: 15px 0px 0px 5px; } .shop-imgbox:nth-child(3) { width: 666px; z-index: 30; margin: 30px 0px 0px 10px; } .shop-imgbox:nth-child(4) { width: 656px; z-index: 20; margin: 0px 0px 0px 15px; } .shop-imgbox:nth-child(5) { width: 646px; z-index: 10; 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; } } }