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
然后,就是取得移动过程中不断变化的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
同理,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(
{
imglist &&
imglist.map((item,index) => (
))
}
)
}
}
//拖动组件
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; ithis.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.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 (
{this.props.children}
)
}
}
export default MobileTouch
代码地址:https://github.com/kellywang1314/react_kw
3、总结:目前对于end之后的位置判断过于呆板,不够灵敏,导致拖动效果不够好;需要继续改进,再者,组件的耦合程度过高,需要进一步解耦。