前言图片拖拽排序是一个比较常用的组件,常用于发帖、评论等内容上传模块。借用这篇文章《一款优雅的小程序拖拽排序组件实现》的实现思路,打包成wx-drag-img发布到npm。实现原理:我会把每个图片初始化封装成一个拖拽数据结构,然后触发touch事件改变transform的位置,从而实现拖拽效果。功能包括图片上传、拖拽、删除,最后贴出源码和npm地址。如果觉得不错,欢迎star。下面我会逐步分析这个组件的实现思路。使用以下变量//拖放数据结构接口IDragImg{src:string;//图片路径key:number;//身份证号;//for用于循环遍历,不会改变,自增idtranX:number;//x轴位移距离tranY:number;//y轴位移距离}//props{previewSize//图片大小defaultImgList//初始化图片数组maxCount//图片上传限制列数//列数间隙//图片间隔deleteStyle//删除样式}data:{dragImgList:IDragImg[],containerRes:{top:0,//容器到页面顶部的px距离left:0,//容器到页面左侧的距离pxwidth:0,//容器的宽度pxheight:0,//容器的高度px},//拖拽容器的属性currentKey:-1,//被拖拽图片的keycurrentIndex:-1,//被拖动图片的索引tranX:0,//被拖动图片的x距离tranY:0,//被拖动图片的y距离uploadPosition:{//上传图标位移距离tranX:0,tranY:0,}},WXMLx视图>视图>=maxCount}}"style="transform:translate({{uploadPosition.tranX}}px,{{uploadPosition.tranY}}px);宽度:{{previewSize}}px;高度:{{previewSize}}px;">+图片上传图片上传很简单,就是初始化上传的图片,然后将其拼接在已有的图片上,最后修改上传图标的位置点击上传区回调函数uploadImage/***上传图片*/asyncuploadImage(){let{dragImgList,maxCount}=this.data;try{constres=awaitwx.chooseMedia({count:maxCount-dragImgList.length,mediaType:['image'],});//获取到上传的图片数据后,需要初始化图片拖拽结构体constimgList=this.getDragImgList(res?.tempFiles?.map(({tempFilePath})=>temp文件路径)||[],错误的);dragImgList=dragImgList.concat(imgList);//修改上传区域位置this.setUploaPosition(dragImgList.length);this.setData({dragImgList,});this.updateEvent(dragImgList);}赶上(错误){console.log(错误);}},拖拽的数据结构上传后需要初始化/***根据图片列表生成拖拽列表数据结构*@paramlistimagesrclist*@paraminit是否初始化*/getDragImgList(list,init=true){let{dragImgList,previewSize,columns,gap}=this.data;returnlist.map((item,index)=>{consti=(init?0:dragImgList.length)+index;return{tranX:(previewSize+gap)*(i%columns),tranY:Math.floor(i/columns)*(previewSize+gap),src:item,id:i,key:i,};});},修改上传区位置/***修改上传区位置*@paramlistLength数组长度*/setUploaPosition(listLength){const{previewSize,columns,gap}=this.data;constuploadPosition={tranX:listLength%columns*(previewSize+gap),tranY:Math.地板(列表长度/列)*(previewSize+gap),};const{宽度,高度}=this.getContainerRect(listLength);this.setData({uploadPosition,['containerRes.width']:width,['containerRes.height']:height,});},改变图片数量后,需要重新获取宽高ofthecontainer/***获取改变图片数量后容器的宽高*@parmalistLengtharraylength*/getContainerRect(listLength){const{columns,previewSize,maxCount,gap}=this.data;constnumber=listLength===maxCount?列表长度:列表长度+1;constrow=Math.ceil(number/columns)return{width:columns*previewSize+(columns-1)*gap,height:row*previewSize+gap*(row-1),};},updateEvent/***复制代码updateEvent*@describe上传、删除、拖动后,触发事件将列表数据发送到页面*/updateEvent(dragImgList){constlist=[...dragImgList].sort((a,b)=>a.key-b.key).map((item)=>item.src);this.triggerEvent('updateImageList',{list,});},图片删除首先从图片列表中删除想要的图片,然后修改列表,将所有大于所选图片key的key减一,最后计算剩余图片的位置和上传图标的位置/***删除图片*/deleteImg(e){constkey=e.mark.key;常量列表=this.data.dragImgList.filter((item)=>item.key!==key);//所有大于删除图片key的key全部减1list.forEach((item)=>{item.key>key&&item.key--;});//获取this.getListPosition(list);this.setUploaPosition(list.length);},/***计算数组的位移位置*@paramlist拖放图片数组*/getListPosition(list){const{previewSize,columns,gap}=this.data;constdragImgList=list.map((item)=>{item.tranX=(previewSize+gap)*(item.key%columns);item.tranY=Math.floor(item.key/columns)*(previewSize+gap));返回项目;})this.setData({dragImgList,});this.updateEvent(dragImgList);},图片拖动初始化初始化只需要获取容器的位置信息即可,因为拖动组件不一定在页面的左上角,所以需要知道位置信息容器的生命周期:{ready(){this.createSelectorQuery().select(".drag-container").boundingClientRect(({top,left})=>{this.setData({['containerRes.top']:top,['containerRes.left']:left,});}).exec();}},这里的longPress使用的是longPress,而不是touchStart,一是为了优化体验,二是touchStart与删除按钮冲突/***长按图片*/longPress(e){constindex=e.mark.index;const{pageX,pageY}=e.touches[0];const{previewSize,containerRes:{top,left}}=this.data;this.setData({currentIndex:index,tranX:pageX-previewSize/2-left,tranY:pageY-previewSize/2-top,});},touchMovetouchMove先计算位移距离,再计算停放位置的key根据位移距离,如果不同,修改位置touchMove/***touchMove*/touchMove(e){//如果currentIndex<0,说明longPress没有被触发if(this.data.currentIndex<0){返回;}const{pageX,pageY}=e.touches[0];const{previewSize,containerRes:{top,left}}=this.data;consttranX=pageX-previewSize/2-左;consttranY=pageY-previewSize/2-top;this.setData({tranX,tranY});//比较当前移动的key和停放位置的key,不同则修改positionconstcurrentKey=e.mark.key;constmoveKey=this.getMoveKey(tranX,tranY);//当移动的key等于停放位置的key时,不需要处理if(currentKey===moveKey||this.data.currentKey===currentKey){r返回;}this.data.currentKey=currentKey;this.replace(currentKey,moveKey);},getMoveKey/***计算移动键*@paramtranX是拖动图片的tranX*@paramtranY是拖动图片tranY*/getMoveKey(tranX,tranY){const{dragImgList:list,previewSize,columns}=this.data;const_getPositionNumber=(drag,limit)=>{constpositionNumber=Math.round(drag/previewSize);返回positionNumber>=限制?limit-1:positionNumber<0?0:位置编号;}constendKey=columns*_getPositionNumber(tranY,Math.ceil(list.length/columns))+_getPositionNumber(tranX,columns);返回endKey>=list.length?list.length-1:endKey;},replace/***拖拽后生成一个新的数组*@paramstart拖拽开始的key*@paramend拖拽结束的key*/replace(start,end){constdragImgList=this.data.dragImgList;dragImgList.forEach((item)=>{if(startstart&&item.key<=end)item.key--;elseif(item.key===start)item.key=结束;}elseif(start>end){if(item.key>=end&&item.key