React+Taro開發小程序實現左滑喜歡右滑不喜歡效果


序言:

      年后入職了一家新公司,與前同事交接完之后,發現公司有一個四端的項目(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-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: '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;
    }
  }
}

好了,小程序左滑右滑效果就說到這里了,如果大家有更好的辦法請在下方留言,如果有什么不懂的可以在下面提問,有時間我會一一回復的,謝謝了!

 

第二種實現方式:

更優解決方法

GIF效果如下:

附加:由於MovableView目前只支持小程序,並且圖片無法完全滑出頁面,h5不太適用,接下來提供第二種方法,效果比第一種方法更好,直接貼源碼

import Taro, { Component } from '@tarojs/taro';
import { View, Image, Button, Text } from '@tarojs/components';
import { observer, inject } from '@tarojs/mobx';
import { AtButton, AtFloatLayout  } from 'taro-ui';
import { userRecommend, userFavorite, userFavoHistory } from '../../api/index';
import userStore from '../../store/user.store';

import './stroll.scss';

@inject('userStore')
@observer
class Stroll extends Component {
  config = {
    navigationBarTitleText: '逛',
    disableScroll: true,
  }

  state = {
    // 列表參數
    lastId: '',
    count: 20,
    // 推薦商品數組
    cardshow: false,
    shopList: [{skuId:'',skuPicture:''}],
    // 歷史瀏覽條數
    browseCount: '',
    recommendCount: '',
    // 按鈕顯示狀態
    dislikeshow: true,
    likeshow: true,
    // 提示彈窗狀態
    popupshow: false,
    popupstate: 0,
    // basicdata數據包含組件基本數據
    startx: '', // 記錄起始位置
    starty: '', // 記錄起始位置
    endx: '', // 記錄終點位置
    endy: '', // 記錄終點位置
    currentPage: 0, // 默認首圖的序列
    // temporaryData數據包含組件臨時數據
    poswidth: 0, // 記錄位移
    posheight: 0, // 記錄位移
    dxangle: 0, // 拖拽角度
    tracking: false, // 是否在滑動,防止多次操作,影響體驗
    animation: false, // 首圖是否啟用動畫效果,默認為否
    opacity: 1, // 記錄首圖透明度
  }

  componentWillMount () { }

  componentWillReact () { }

  componentDidShow () {
    // 用戶推薦列表
    this.recommendList();
    // 判斷用戶是否是第一次進入該頁面,儲存一個判斷值
    if (Taro.getStorageSync('stepState') === '') {
      this.setState({
        popupshow: true,
        popupstate: 1,
      });
    }
  }

  // 推薦列表接口
  async recommendList() {
    // 第一次進來需要調用加載圖
    if (this.state.cardshow === false) {
      Taro.showLoading();
    }
    const [err, res] = await userRecommend({
      lastId: this.state.lastId, 
      count:this.state.count,
    });
    Taro.hideLoading();
    if (err) {
      return;
    }
    if (!err&&res&&res.data) {
      this.setState({
        // 顯示狀態
        cardshow: true,
        currentPage: 0,
        // 歷史瀏覽條數
        browseCount: res.data.browseCount,
        recommendCount: res.data.recommendCount,
        // 圖片數組
        shopList: res.data.record,
      });
    }
  }

  // 進入頁面執行
  componentDidMount() {
    //登錄並且沒有選擇性別
    setTimeout(() => {
      if(this.props.userStore.hasLogin&&!this.props.userStore.hasChoiceSex){
        Taro.navigateTo({
          url:'/sub-pages/gender/gender',
        });
      }
    },1500);
  }

  componentWillUnmount () {}

  componentDidHide () { 
    console.log('11111',this.state.currentPage,this.state.shopList);
    this.setState({
      // 數組計算
      shopList: this.state.shopList.splice(this.state.currentPage,100),
    },() => {
      this.setState({
        // 序列清零
        currentPage: 0,
      },() => {
        console.log('2222',this.state.currentPage,this.state.shopList);
      });
    });
  }

  // 首頁樣式切換
  transformIndex (index,color) {
    // console.log('transformIndex', index);
    // 處理3D效果
    if (index === this.state.currentPage) {
      let style = {};
      style['transform'] = 'translate3D(' + this.state.poswidth + 'px' + ',' + this.state.posheight + 'px' + ',0px)'+ 'rotate(' + this.state.dxangle + 'deg)';
      style['opacity'] = this.state.opacity;
      style['zIndex'] = 10;
      style['box-shadow'] = '0px 1px 20px 0px rgba(0,0,0,0.1)';
      style['background'] = color;
      if (this.state.animation) {
        style['transitionTimingFunction'] = 'ease';
        style['transitionDuration'] = 400 + 'ms';
      }
      // console.log('style1', style);
      return style;
    }
  }

  // 非首頁樣式切換
  transform (index,color) {
    // console.log('transform', index);
    if (index > this.state.currentPage) {
      let style = {};
      let visible = 3;
      let perIndex = index - this.state.currentPage;
      // visible可見數量前滑塊的樣式
      if (index <= this.state.currentPage + visible) {
        style['opacity'] = '1';
        style['transform'] = 'translate3D(0,0,' + -1 * perIndex * 10 + 'px' + ')';
        style['zIndex'] = visible - index + this.state.currentPage;
        style['transitionTimingFunction'] = 'ease';
        style['transitionDuration'] = 400 + 'ms';
        style['box-shadow'] = '0px 1px 20px 0px rgba(0,0,0,0.1)';
        style['background'] = color;
      } else {
        style['zIndex'] = '-1';
        style['transform'] = 'translate3D(0,0,' + -1 * visible * 10 + 'px' + ')';
        style['background'] = color;
      }
      // console.log('==========', index, this.state.currentPage);
      // console.log('style2', style);
      return style;
    } else {
      let style = {};
      style['opacity'] = '0';
      style['transitionDuration'] = '0ms';
      // console.log('+++++++++++', index, this.state.currentPage);
      // console.log('style3', style);
      return style;
    }
  }

  //觸摸開始
  touchstart (e) {
    e.preventDefault();
    // console.log('touchstart', e);
    // 是否在滑動
    if (this.state.tracking) {
      return;
    }
    // 是否為touch
    if (e.type === 'touchstart') {
      if (e.touches.length > 1) {
        // console.log('觸摸開始-----------------------touches.length > 1');
        this.setState({
          tracking: false,
        });
        return;
      } else {
        // console.log('觸摸開始-----------------------startxstarty');
        // 記錄起始位置
        this.setState({
          startx: e.changedTouches[0].clientX,
          starty: e.changedTouches[0].clientY,
        });
        this.setState({
          endx: e.changedTouches[0].clientX,
          endy: e.changedTouches[0].clientY,
        });
      }
    }
    this.setState({
      tracking: true,
      animation: false,
    });
    // console.log('1011');
  }

  // 觸摸移動
  touchmove (e) {
    e.preventDefault();
    // console.log('touchmove', e);
    // 記錄滑動位置
    if (this.state.tracking && !this.state.animation) {
      // console.log('觸摸移動-----------------------');
      if (e.type === 'touchmove') {
        // console.log('觸摸移動-----------------------endxendy');
        this.setState({
          endx: e.changedTouches[0].clientX,
          endy: e.changedTouches[0].clientY,
        });
      }
      // 計算滑動值與偏移角度
      this.setState({
        poswidth: this.state.endx - this.state.startx,
        posheight: this.state.endy - this.state.starty,
        dxangle: (this.state.endx - this.state.startx)/10,
      });
      // 判斷移動的距離改變按鈕狀態
      if (this.state.poswidth < -100) {
        this.setState({
          dislikeshow: false,
        });
      } else if (this.state.poswidth > 100) {
        this.setState({
          likeshow: false,
        });
      } else {
        this.setState({
          dislikeshow: true,
          likeshow: true,
        });
      }
    }
  }

  touchend (id,type,e) {
    e.preventDefault();
    // console.log('touchend', e);
    // 是否在滑動,動畫
    this.setState({
      tracking: false,
      animation: true
    });
    // 滑動結束,觸發判斷
    // 簡單判斷滑動寬度超出100像素時觸發滑出
    if (Math.abs(this.state.poswidth) >= 100) {
      // console.log('滑動寬度超出100像素時觸發滑出-----------------------');
      // 最終位移簡單設定為x軸200像素的偏移
      let ratio = Math.abs(this.state.posheight / this.state.poswidth);
      this.setState({
        poswidth: this.state.poswidth >= 0 ? this.state.poswidth + 500 : this.state.poswidth - 500,
        posheight: this.state.posheight >= 0 ? Math.abs(this.state.poswidth * ratio) : -Math.abs(this.state.poswidth * ratio),
        opacity: 0,
      }, () => {
        setTimeout( () => {
          this.setState({
            // 刪除第一張圖片和重置樣式
            currentPage: this.state.currentPage+1,
            poswidth: 0,
            posheight: 0,
            dxangle: 0,
            opacity: 1,
            animation: false,
            // 按鈕狀態
            dislikeshow: true,
            likeshow: true,
            // index顯示
            browseCount: this.state.browseCount+1,
          },() => {
            if (this.state.currentPage === this.state.shopList.length) {
              this.setState({
                // 重置樣式
                shopList: [],
                currentPage: 0,
              });
            }
          });
        },400);
      });
      // 調用接口
      if (this.state.poswidth >= 0) {
        // 喜歡接口
        this.rightLike(id,type);
      } else {
        // 不喜歡接口
        this.leftNoLike(id,type);
      }
    // 不滿足條件則滑入
    } else {
      // console.log('滑回來-----------------------');
      this.setState({
        poswidth: 0,
        posheight: 0,
        dxangle: 0,
      });
    }
  }

  // 不喜歡按鈕
  dislikebtn() {
    // console.log('不喜歡');
    // 不喜歡接口
    this.leftNoLike(this.state.shopList[this.state.currentPage].skuId,this.state.shopList[this.state.currentPage].productSourceType);
    this.setState({
      // 按鈕狀態
      dislikeshow: false,
      // 圖片位移效果
      poswidth: -500,
      posheight: 100,
      dxangle: -20,
      opacity: 0,
      animation: true,
    }, () => {
      setTimeout( () => {
        this.setState({
          // 刪除第一張圖片和重置樣式
          currentPage: this.state.currentPage+1,
          poswidth: 0,
          posheight: 0,
          dxangle: 0,
          opacity: 1,
          animation: false,
          // 按鈕狀態
          dislikeshow: true,
          // index顯示
          browseCount: this.state.browseCount+1,
        },() => {
          if (this.state.currentPage === this.state.shopList.length) {
            this.setState({
              // 重置樣式
              shopList: [],
              currentPage: 0,
            });
          }
        });
      },400);
    });
  }

  // 喜歡按鈕
  likebtn() {
    console.log('喜歡');
    // 喜歡接口
    this.rightLike(this.state.shopList[this.state.currentPage].skuId,this.state.shopList[this.state.currentPage].productSourceType);
    this.setState({
      // 按鈕狀態
      likeshow: false,
      // 圖片位移效果
      poswidth: 500,
      posheight: 100,
      dxangle: 20,
      opacity: 0,
      animation: true,
    }, () => {
      setTimeout( () => {
        this.setState({
          // 刪除第一張圖片和重置樣式
          currentPage: this.state.currentPage+1,
          poswidth: 0,
          posheight: 0,
          dxangle: 0,
          opacity: 1,
          animation: false,
          // 按鈕狀態
          likeshow: true,
          // index顯示
          browseCount: this.state.browseCount+1,
        },() => {
          if (this.state.currentPage === this.state.shopList.length) {
            this.setState({
              // 重置樣式
              shopList: [],
              currentPage: 0,
            });
          }
        });
      },400);
    });
  }

  // 喜歡接口
  async rightLike(id,type) {
    const [err, res] = await userFavorite({
      skuId: id, 
      favoriteStatus: 'LIKE',
      productSourceType: type,
    });
  }

  // 不喜歡接口
  async leftNoLike(id,type) {
    const [err, res] = await userFavorite({
      skuId: id, 
      favoriteStatus: 'NOT_LIKE',
      productSourceType: type,
    });
  }

  // 查看我喜歡的
  lookMyLike() {
    Taro.navigateTo({
      url:'/stroll-pages/stroll-likelist/stroll-likelist'
    });
  }

  // 下一步
  stepone() {
    this.setState({
      popupstate: 2,
    });
  }
  steptwo() {
    this.setState({
      popupstate: 3,
    });
  }
  stepthree() {
    this.setState({
      popupshow: false,
    });
    // 改變是否第一次進入頁面狀態
    Taro.setStorageSync('stepState', '1');
  }

  render() {
    let cardList = this.state.shopList;
    return (
      <View className='stroll-tab'>
        <View className='stroll-text'>
          <Text className='text-tip1'>慧搭腦每天為你精推薦{this.state.recommendCount}件單品</Text>
          <View className='text-tip2'>
            <Text className='t1'>左滑無感</Text>
            <Image src={require('./img/text_dislike.svg')} className='icon-image'></Image>
            <Text className='t1'>,  右滑喜歡</Text>
            <Image src={require('./img/text_like.svg')} className='icon-image'></Image>
          </View>
        </View>
        {
          this.state.cardshow && cardList.length !== 0 && 
          <View className='stack'>
            {
              cardList && cardList.map((item, index) => {
                return(
                  <View key={item.id} className='stack-item' 
                    style={this.transformIndex(index,item.picMainColor)||this.transform(index,item.picMainColor)}
                    onTouchStart={this.touchstart.bind(this)}
                    onTouchMove={this.touchmove.bind(this)}
                    onTouchEnd={this.touchend.bind(this, item.skuId, item.productSourceType)}
                  >
                    <View className='item-data'>
                      <Image className='imgs' mode='aspectFit' src={item.skuPicture}></Image>
                    </View>
                  </View>
                );
              })
            }
            <View className='card-num'>{this.state.browseCount+1}/{this.state.recommendCount}</View>
            <View className='card-btn'>
              {
                this.state.dislikeshow ?
                (<Image src={require('./img/dislike_default.png')} className='dislike-image' onClick={this.dislikebtn.bind(this)}></Image>):
                (<Image src={require('./img/dislike_light.png')} className='dislike-image'></Image>)
              }
              {
                this.state.likeshow ?
                (<Image src={require('./img/like_default.png')} className='like-image'  onClick={this.likebtn.bind(this)}></Image>):
                (<Image src={require('./img/like_light.png')} className='like-image'></Image>)
              }
            </View>
          </View>
        }
        {
          this.state.cardshow && cardList.length === 0&&
          <View className='noshop-card'>
            <Image src='https://edgefix-image.edgecom.top/403EC3F8835C5521A500F04F35E3ADF7.png' className='noshop-image' mode='widthFix'></Image>
          </View>
        }
        <View className='fotter-box'>
          <Text className='fot-text' onClick={this.lookMyLike.bind(this)}>查看我喜歡的</Text>
        </View>
        {
          this.state.popupshow&&
          <View className='popup-box'>
            {
              this.state.popupstate === 1 &&
              <Image src='https://edgefix-image.edgecom.top/F8BEDD2B180FE71C05F7B6E461ECB759.png' className='stepimage1' onClick={this.stepone.bind(this)}></Image>
            }
            {
              this.state.popupstate === 2 &&
              <Image src='https://edgefix-image.edgecom.top/387AF72683F8598EACA618F1B95A0C23.png' className='stepimage2' onClick={this.steptwo.bind(this)}></Image>
            }
            {
              this.state.popupstate === 3 &&
              <Image src='https://edgefix-image.edgecom.top/90C93EEE270440031187CFDC917DF9AC.png' className='stepimage3' onClick={this.stepthree.bind(this)}></Image>
            }
          </View>
        }
      </View>
    );
  }
}

export default Stroll;
page {
  height: 100%;
  background: #F6F6F6;
}

.stroll-tab {
  width: 100%;
  height: 100vh;
  background: #F6F6F6;
  position: relative;
  overflow-x: hidden;
  overflow-y: hidden;
  .stroll-text {
    width: 100%;
    padding-top: 40px;
    padding-bottom: 20px;
    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;
        margin-left: 4px;
      }
    }
  }
  .stack {
    width: 100%;
    height: 72.072072vh;
    position: relative;
    perspective: 1000px; //子元素視距
    perspective-origin: 50% 150%; //子元素透視位置
    -webkit-perspective: 1000px;
    -webkit-perspective-origin: 50% 150%;
    margin: 0;
    padding: 0;
    .stack-item{
      background: #fff;
      width: 686px;
      height: 72.072072vh;
      border-radius: 24px;
      overflow: hidden;
      position: absolute;
      left: 32px;
      top: 0px;
      .item-data {
        position: relative;
        width: 100%;
        height: 100%;
        .imgs {
          position: absolute;
          left: 0px;
          top: 0px;
          width: 100%;
          height: 100%;
          display: block;
          pointer-events: none;
        }
      }
    }
    .card-num {
      position: absolute;
      right: 64px;
      top: 40px;
      width: 144px;
      height: 64px;
      background: rgba(0,0,0,0.3);
      border-radius: 32px;
      z-index: 100;
      font-size: 32px;
      color: #FFFFFF;
      text-align: center;
      line-height: 64px;
    }
    .card-btn {
      position: absolute;
      left: 0px;
      bottom: 48px;
      z-index: 20;
      width: 100%;
      display: flex;
      flex-direction: row;
      align-items: center;
      justify-content: center;
      .dislike-image {
        width: 120px;
        height: 120px;
        border-radius: 50%;
        box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.05);
      }
      .like-image {
        width: 120px;
        height: 120px;
        margin-left: 48px;
        border-radius: 50%;
        box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.05);
      }
    }
  }
  .noshop-card {
    width: 100%;
    padding: 0px 16px;
    .noshop-image {
      width: 100%;
    }
  }
  .fotter-box {
    width: 100%;
    text-align: center;
    position: absolute;
    bottom: 20px;
    left: 0px;
    .fot-text {
      color: #368BE5;
      font-size: 28px;
      padding: 10px 20px;
    }
  }
  .popup-box {
    width: 100%;
    height: 100vh;
    position: absolute;
    left: 0;
    top: 0;
    background:rgba(0,0,0,0.5);
    .stepimage1 {
      width: 100%;
      height: 344px;
      margin-top: 36.036036vh;
    }
    .stepimage2 {
      width: 100%;
      height: 300px;
      margin-top: 68.468468vh;
    }
    .stepimage3 {
      width: 100%;
      height: 300px;
      margin-top: 68.468468vh;
    }
  }
}  

不論什么框架,代碼都是相通的,看上方源碼了解方法和邏輯就可以都適用了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM