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之後的位置判斷過於呆板,不夠靈敏,導致拖動效果不夠好;需要繼續改進,再者,組件的耦合程度過高,需要進一步解耦。