Taro UI开发小程序实现左滑喜欢右滑不喜欢效果

前言:年后入职了一家新公司,与前同事交接完之后,发现公司有一个四端的项目(iOS,Android,H5,小程序),iOS和安卓都实现了左滑右滑的效果,而h5和小程序端没实现,询问得知前同事因网上没找到对应的插件,相关博客也特别少,所以没做就搁置下来了,趁这段时间相对来说比较富裕,于是乎在网上也搜索了一下,发现确实很少,但是有人提到可以用小程序可拖动组件movable-view来实现,自己尝试来一下发现可行,于是来写这篇博客记录一下,希望能帮助到后面需要用到这个功能的人!

 

先上效果图:

 

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

可拖动组件文档地址:

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

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

 

思路:

一,我们首先把movable-areamovable-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: '//edgefix-image.edgecom.top/ABD846F6672997A7F76CD38E8A57F954.jpg', 
      }, 
      { 
        img: '//edgefix-image.edgecom.top/F6E5801C304CC76DA63C02C9FB38B8F4.jpg', 
      }, 
      { 
        img: '//edgefix-image.edgecom.top/D518952AD1DD61B2D32556E20CC527C4.jpg', 
      }, 
      { 
        img: '//edgefix-image.edgecom.top/1D187E28B349679908A44BBE81F3D3CA.jpg', 
      }, 
      { 
        img: '//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: '//edgefix-image.edgecom.top/ABD846F6672997A7F76CD38E8A57F954.jpg',
      },
      {
        id: 2,
        img: '//edgefix-image.edgecom.top/F6E5801C304CC76DA63C02C9FB38B8F4.jpg',
      },
      {
        id: 3,
        img: '//edgefix-image.edgecom.top/D518952AD1DD61B2D32556E20CC527C4.jpg',
      },
      {
        id: 4,
        img: '//edgefix-image.edgecom.top/1D187E28B349679908A44BBE81F3D3CA.jpg',
      },
      {
        id: 5,
        img: '//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: '//edgefix-image.edgecom.top/ABD846F6672997A7F76CD38E8A57F954.jpg',
      },
      {
        id: 2,
        img: '//edgefix-image.edgecom.top/F6E5801C304CC76DA63C02C9FB38B8F4.jpg',
      },
      {
        id: 3,
        img: '//edgefix-image.edgecom.top/D518952AD1DD61B2D32556E20CC527C4.jpg',
      },
      {
        id: 4,
        img: '//edgefix-image.edgecom.top/1D187E28B349679908A44BBE81F3D3CA.jpg',
      },
      {
        id: 5,
        img: '//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;
    }
  }
}

好了,小程序左滑右滑效果就说到这里了,如果大家有更好的办法请在下方留言,上述的方法可能一下拖动效果之类的还需要加以完善,有什么好的建议和改善欢迎提出,谢谢了🙏