移動端h5使用touchstart,touchmove實現多圖片拖拽效果

1、需求描述:對移動端多張圖片可以進行拖動排序(例子中使用兩排5張圖片)
2、實現過程:
主要思想就是能夠通過用戶手勢,捕捉到被拖 動元素以及拖動結束後的被交換元素,通過交換這兩個元素實現拖動排序。除此之外,我們還需要獲得被拖動元素在拖動過程中的的left和top值,增加拖動過程中的效果。

在這個過程中會用到移動端的手勢事件touchstart,touchmove,touchend

首先,需要獲得被拖動的目標圖片,做法是首先計算出所有圖片的一個左上角的left,top值數組,然後根據手勢位置是否在[left,left+imgwidth]和[top,top+imgheight]之間來確定目標圖片,並且賦值給一個初始值currentindex:

//計算全部圖片的left和top
 getStartPos() {
        let doms = Array.prototype.slice.call(document.getElementsByTagName('li'))
        let centerpos = []
        doms.map(item => {
          centerpos.push({left:item.offsetLeft, top:item.offsetTop})
        })
        return centerpos
      }
//touchstart的事件監聽函數
dragStart(ev) {
        let centerpos = this.getStartPos()
        let target = ev.target, self = this
        if(ev.changedTouches) {
          this.startX = ev.changedTouches[0].pageX;
          this.startY = ev.changedTouches[0].pageY;
        } else {
          this.startX = ev.clientX;
          this.startY = ev.clientY;
        }
        //鼠標邊界性
        this.startX >this.clientWidth ? this.startX = this.clientWidth: null
        this.startY >this.clientHeight ? this.startY = this.clientHeight: null
    
        //偏移位置 = 鼠標的初始值 - 元素的offset
        this.disX = this.startX - target.offsetLeft
        this.disY = this.startY - target.offsetTop
        //判斷用戶拖動的是第幾張圖片
        for(let i=0; i<centerpos.length; i++){
          let X = centerpos[i].left<this.startX && this.startX<centerpos[i].left + this.elementWid
          let Y = centerpos[i].top<this.startY && this.startY<centerpos[i].top + this.elementHeight
          if(X && Y){
            self.setState({
              currentindex:i
            })
          }
        }
        this._dragMove = this.dragMove.bind(this);
        this._dragEnd = this.dragEnd.bind(this);
      } 

然後,就是取得移動過程中不斷變化的left和top來實現圖片移動的效果:

 dragMove(ev) {
        if(ev.changedTouches) {
          this.clientX = ev.changedTouches[0].pageX;
          this.clientY = ev.changedTouches[0].pageY;
        } else {
          this.clientX = ev.clientX;
          this.clientY = ev.clientY;
        }   
         //鼠標邊界性
         this.clientX >this.clientWidth ? this.clientX = this.clientWidth: null
         this.clientY >this.clientHeight ? this.clientY = this.clientHeight: null
         // 元素位置 = 現在鼠標位置 - 元素的偏移值
         let left = this.clientX - this.disX;
         let top = this.clientY - this.disY;
          //邊界
          if (left < 0) {
            left = 0;
          }
          if (top < 0) {
            top = 0;
          }
          if (left > this.clientWidth - this.elementWid) {  
            left = this.clientWidth - this.elementWid
          }
          if (top > this.clientHeight - this.elementHeight) {
            top = this.clientHeight - this.elementHeight;
          }
          this.setState({
            left: left,
            top: top
          })
          this.props.handletranstion(this.state.currentindex,left,top)
      }

其中handletranstion是調用該拖動組件的父組件傳遞過來的處理函數,通過這個函數可以在父組件中設置當前被拖動元素的left和top,以達到拖動過程中位置的變化

最後就是獲得拖動結束,放置位置的圖片編號,判斷的方法同第一步中,也是採用放置的手勢位置和元素的left和top等位置的判斷:

 dragEnd(e) {
        //判斷移動到哪個圖片上方
        let self = this
        const {left,top} = this.state
        let centerpos = this.getStartPos()
        for(let i=0; i<centerpos.length; i++){
            let L = centerpos[i].left<left && left<centerpos[i].left+this.elementWid
            let T = centerpos[i].top<top && top<centerpos[i].top+this.elementHeight
            if(L && T){
            self.setState({
              targetindex:i
            })
          }
        }
        this.props.handleposition(this.state.currentindex,this.state.targetindex)
      }
    

同理,handleposition也是父組件的一個處理函數,用來把原始的圖片List按照被拖動元素和放置元素進行交換,重新渲染UI。
完整的代碼:

//父組件
import React from 'react'
import MobileTouch from './MobileTouch'
export default class MobileTouchExp extends React.Component{
    constructor(props){
        super(props)
        this.state = {
            top:0,
            left:0,
            currentindex:10,
            imglist:[{imageUrl:'http://photos.tuchong.com/349669/f/6683901.jpg'},{imageUrl:'https://dimg06.c-ctrip.com/images/fd/tg/g4/M01/4A/BF/CggYHVXjzyeAOeymABuBTaHIgFc792.jpg'},{imageUrl:'https://dimg01.c-ctrip.com/images/fd/tg/g6/M01/7E/11/CggYs1cqtn2AIL7FACVmLZ72uhI947.jpg'},
            {imageUrl:'https://dimg04.c-ctrip.com/images/300v0x000000liuks9C9C_C_750_420_Q90.jpg'},{imageUrl:'https://dimg04.c-ctrip.com/images/300q0x000000l9393930E_C_750_420_Q90.jpg'}],
        }
    }

    handleposition (currentindex, targetindex){
       const { imglist } = this.state
       let newimglist = this.swapArray(imglist,currentindex,targetindex)
       this.setState({
        imglist:newimglist,
        left: 0,
        top:0,
        currentindex:10,
       })
    }

    //被拖動元素移動效果
    handletranstion (currentindex, left, top){
        this.setState({
            currentindex:currentindex,
            left:left,
            top:top
           })
    }


    swapArray (arr, index1, index2){
        arr[index1] = arr.splice(index2, 1, arr[index1])[0]
         return arr
     }
    render(){
        let { imglist, currentindex, left,top } = this.state
        return(
                <MobileTouch 
                    maxWidth = {600}
                    maxHeight={400}
                    handleposition = {this.handleposition.bind(this)}
                    handletranstion = {this.handletranstion.bind(this)}
                >
                    {
                        imglist && 
                        imglist.map((item,index) => (
                            <li 
                                key={index} 
                            >
                                <img src={item.imageUrl} id={`img${index}`} style={{position:'absolute', width:100, height:100,  left:(currentindex === index) ? left : '', top:(currentindex === index) ? top : '',zIndex:(currentindex === index) ? 999 : ''}}/>
                            </li>
                        ))
                    }
                </MobileTouch>
        )
    }
}


//拖動組件

import React from 'react'
import classnames from 'classnames'
class MobileTouch extends React.Component {
    constructor(props) {
        super(props);
        this.elementWid = props.width || 100;
        this.elementHeight = props.height || 100;
        this.clientWidth = props.maxWidth;
        this.clientHeight = props.maxHeight;
        this._dragStart = this.dragStart.bind(this);
        this.state = {
          currentindex:0,
          targetindex:0,
          left: 0,
          top: 0
        };
      }
    
      dragStart(ev) {
        let centerpos = this.getStartPos()
        let target = ev.target, self = this
        if(ev.changedTouches) {
          this.startX = ev.changedTouches[0].pageX;
          this.startY = ev.changedTouches[0].pageY;
        } else {
          this.startX = ev.clientX;
          this.startY = ev.clientY;
        }
        //鼠標邊界性
        this.startX >this.clientWidth ? this.startX = this.clientWidth: null
        this.startY >this.clientHeight ? this.startY = this.clientHeight: null
    
        //偏移位置 = 鼠標的初始值 - 元素的offset
        this.disX = this.startX - target.offsetLeft
        this.disY = this.startY - target.offsetTop
        //判斷用戶拖動的是第幾張圖片
        for(let i=0; i<centerpos.length; i++){
          let X = centerpos[i].left<this.startX && this.startX<centerpos[i].left + this.elementWid
          let Y = centerpos[i].top<this.startY && this.startY<centerpos[i].top + this.elementHeight
          if(X && Y){
            self.setState({
              currentindex:i
            })
          }
        }
        this._dragMove = this.dragMove.bind(this);
        this._dragEnd = this.dragEnd.bind(this);
      } 
    
      dragMove(ev) {
        if(ev.changedTouches) {
          this.clientX = ev.changedTouches[0].pageX;
          this.clientY = ev.changedTouches[0].pageY;
        } else {
          this.clientX = ev.clientX;
          this.clientY = ev.clientY;
        }   
         //鼠標邊界性
         this.clientX >this.clientWidth ? this.clientX = this.clientWidth: null
         this.clientY >this.clientHeight ? this.clientY = this.clientHeight: null
         // 元素位置 = 現在鼠標位置 - 元素的偏移值
         let left = this.clientX - this.disX;
         let top = this.clientY - this.disY;
          //邊界
          if (left < 0) {
            left = 0;
          }
          if (top < 0) {
            top = 0;
          }
          if (left > this.clientWidth - this.elementWid) {  
            left = this.clientWidth - this.elementWid
          }
          if (top > this.clientHeight - this.elementHeight) {
            top = this.clientHeight - this.elementHeight;
          }
          this.setState({
            left: left,
            top: top
          })
          this.props.handletranstion(this.state.currentindex,left,top)
      }
    
      dragEnd(e) {
        //判斷移動到哪個圖片上方
        let self = this
        const {left,top} = this.state
        let centerpos = this.getStartPos()
        for(let i=0; i<centerpos.length; i++){
            let L = centerpos[i].left<left && left<centerpos[i].left+this.elementWid
            let T = centerpos[i].top<top && top<centerpos[i].top+this.elementHeight
            if(L && T){
            self.setState({
              targetindex:i
            })
          }
        }
        this.props.handleposition(this.state.currentindex,this.state.targetindex)
      }
    
      getStartPos() {
        let doms = Array.prototype.slice.call(document.getElementsByTagName('li'))
        let centerpos = []
        doms.map(item => {
          centerpos.push({left:item.offsetLeft, top:item.offsetTop})
        })
        return centerpos
      }
    
      render() {
        const { maxWidth, maxHeight} = this.props;
        let styles = {
          width:maxWidth,
          height:maxHeight
        }
        const cls = classnames('moblie')
    
        return (
          <div 
            className ={cls}
            onTouchStart={this._dragStart} 
            onTouchMove={this._dragMove}
            onTouchEnd ={this._dragEnd}
            style={styles}
            ref="dragElement"
          >
            {this.props.children}
          </div>
        )
      }
}

export default MobileTouch

代碼地址:https://github.com/kellywang1314/react_kw
3、總結:目前對於end之後的位置判斷過於呆板,不夠靈敏,導致拖動效果不夠好;需要繼續改進,再者,組件的耦合程度過高,需要進一步解耦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章