微信小程序圖片上傳組件實(shí)現(xiàn)圖片拖拽排序
引言
圖片上傳組件是一個組件庫目前來看必不可少的功能了。筆者近日給自己開源的toy工具庫也添加了這一功能。相比原生和大部分組件庫來說,它不僅支持長按提示刪除,還能夠支持圖片的拖拽排序,很是nice!
(也是為了畢設(shè)時身邊同學(xué)能夠更快上手小程序,更加將中心側(cè)重于邏輯和設(shè)計??)
本文我將繼續(xù)介紹組件的設(shè)計思路:
首先來看效果
對于組件內(nèi)部來說。筆者提供了一個參數(shù)去讓開發(fā)者決定是否應(yīng)該在場景中支持拖動排序。這里略去這些無關(guān)代碼。
拖拽排序功能使用了微信小程序提供的movable-area組件(標(biāo)簽,但小程序也是封裝了HTML,所以以原生組件代稱)。它相當(dāng)于提供了一個可滑動區(qū)域,在此區(qū)域內(nèi)的movable-view組件內(nèi)容可以任意排列。其效果就相當(dāng)于window中的“桌面圖標(biāo)非對齊”效果 —— 記住這個描述,它和下面的內(nèi)容聯(lián)系緊密!
其中主要的兩個參數(shù)是:
- x:定義x軸方向的偏移,如果x的值不在可移動范圍內(nèi),會自動移動到可移動范圍;改變x的值會觸發(fā)動畫;
- y:定義y軸方向的偏移,如果y的值不在可移動范圍內(nèi),會自動移動到可移動范圍;改變y的值會觸發(fā)動畫;
嗯,可以知道,其內(nèi)部是通過 js 觸發(fā)的動畫。而且可能是requestAnimeFrame API。
組件設(shè)計
知道了所用標(biāo)簽,接下來就該正式開發(fā)了。但是你會發(fā)現(xiàn),這里其實(shí)有兩種使用方式:
對每個元素使用movable-view包裹,讓他們可以隨意拖拽位置:
<view class="container"> <movable-area style="width: 100%;height: auto;"> <view class="image-list"> <!-- 顯示圖片 --> <block wx:if="{{yMovable}}"> <movable-view x="{{x}}" y="{{y}}" direction="all" inertia damping="{{5000}}" friction="{{1}}" disabled="{{disabled}}" wx:for="{{images}}" wx:key="{{item.index}}"> <view class="image-wrap image-bg {{(images.length > 2) ? 'image-flex' : ''}}" id="{{item.index}}" data-index='{{index}}' bindlongpress='onShowMenu' bindtouchstart='touchs' bindtouchend='touchend' bindtouchmove='touchm'> <image class="image" src="{{item.img}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item.img}}"></image> <i class="iconfont icon-delete" wx:if="{{showMenuImg}}" bind:tap="onDelImage" data-index="{{index}}"></i> </view> </movable-view> </block> <!-- 選擇圖片 --> <view class="image-wrap selectphoto" bind:tap="onChooseImage" hidden="{{!selectPhoto}}"> <i class="iconfont icon-jiashang"></i> </view> </view> </movable-area> </view>
圖片只是展示;單獨(dú)設(shè)置一個元素,在長按圖片時顯示,其值為當(dāng)前選中的圖片,拖拽的是這個元素,到達(dá)目標(biāo)位置后消失,圖片列表重新排序。
<view class="container"> <movable-area style="width: 100%;height: auto;"> <view class="image-list"> <!-- 顯示圖片 --> <block wx:if="{{yMovable}}"> <block wx:for="{{images}}" wx:key="{{item.index}}"> <view class="image-wrap image-bg {{(images.length > 2) ? 'image-flex' : ''}}" id="{{item.index}}" data-index='{{index}}' bindlongpress='onShowMenu' bindtouchstart='touchs' bindtouchend='touchend' bindtouchmove='touchm'> <image class="image" src="{{item.img}}" mode="aspectFill" bind:tap="onPreviewImage" data-imgsrc="{{item.img}}"></image> <i class="iconfont icon-delete" wx:if="{{showMenuImg}}" bind:tap="onDelImage" data-index="{{index}}"></i> </view> </block> <movable-view x="{{x}}" y="{{y}}" direction="all" inertia damping="{{5000}}" friction="{{1}}" disabled="{{disabled}}"> <view class='image-wrap image-check' style="z-index: 3;" hidden='{{hidden}}'> <image class="image" src="{{doubleImg}}" mode="aspectFill"></image> </view> </movable-view> </block> <!-- 選擇圖片 --> <view class="image-wrap selectphoto" bind:tap="onChooseImage" hidden="{{!selectPhoto}}"> <i class="iconfont icon-jiashang"></i> </view> </view> </movable-area> </view>
第一種方式的優(yōu)勢在于:可以有更加“真實(shí)”的效果。這里的真實(shí)意為重新排列時也有滑動的動畫效果。但是帶來的性能損耗也是極大的,你只能盡力調(diào)控各種數(shù)據(jù)來讓顯示更加“跟手”一些。但是基于此,你可以通過js計算達(dá)到像QQ空間那樣的實(shí)時排列效果!
第二種方式的優(yōu)勢在于:性能開銷相對小一些。但展示效果更像web而非APP(這兩個的區(qū)別你應(yīng)該是知道的)。
當(dāng)前版本中,筆者采用的是第二種方式。其關(guān)鍵 js 代碼如下:
const MAX_IMG_NUM=9; Component({ /** * 組件的屬性列表 */ properties: { yMovable:{ type:Boolean, value:false }, }, /** * 組件的初始數(shù)據(jù) */ data: { images:[], selectPhoto:true, showMenuImg: false, flag: false, hidden:true, x:0, y:0, disabled: true, elements:[], doubleImg: "" }, /** * 組件的方法列表 */ methods: { //長按事件 onShowMenu(e){ const detail = e.currentTarget; if(!this.data.showMenuImg) { // 使手機(jī)振動15ms wx.vibrateShort(); } this.setData({ showMenuImg: true }) if(this.properties.yMovable) { this.setData({ x: detail.offsetLeft+5, y: detail.offsetTop, hidden: false, flag:true, doubleImg: this.data.images[detail.dataset.index].img }) } }, //觸摸開始 touchs:function(e){ this.setData({ beginIndex:e.currentTarget.dataset.index }) }, //觸摸結(jié)束 touchend:function(e){ if (!this.data.flag) { return; } const x = e.changedTouches[0].pageX const y = e.changedTouches[0].pageY const list = this.data.elements; let data = this.data.images for(var j = 0; j<list.length; j++){ const item = list[j]; if(x>item.left && x<item.right && y>item.top && y<item.bottom){ const endIndex = item.dataset.index; const beginIndex = this.data.beginIndex; //向后移動 if (beginIndex < endIndex) { let tem = data[beginIndex]; for (let i = beginIndex; i < endIndex; i++) { data[i] = data[i + 1] } data[endIndex] = tem; } //向前移動 if (beginIndex > endIndex) { let tem = data[beginIndex]; for (let i = beginIndex; i > endIndex; i--) { data[i] = data[i - 1] } data[endIndex] = tem; } this.setData({ images: data }) this.initImg(this.triggerMsg(data, "sort-img")) } } this.setData({ hidden: true, flag: false }) }, //滑動 touchm:function(e){ if(this.data.flag){ const x = e.touches[0].pageX const y = e.touches[0].pageY this.setData({ x: x - 75, y: y - 45 }) } }, //選擇圖片 onChooseImage(){ let images = this.data.images; let imageLen = images.length; let max=MAX_IMG_NUM-imageLen; wx.chooseImage({ count:max, sizeType:['original','compressed'], sourceType:['album','camera'], success: (res) => { max-=res.tempFilePaths.length; let _images = images.map(item => { return item.img }) images = _images.concat(res.tempFilePaths) for(let i=0;i<images.length;i++) { images[i] = { img: images[i], index: i+1 } } this.setData({ selectPhoto:max<=0?false:true, images, showMenuImg: false }) this.triggerMsg(images, "choose-img") if(this.properties.yMovable) { this.initImg() } }, fail:(res)=>{ } }) }, // 初始化位置信息 initImg(fn=function(){}) { let query = wx.createSelectorQuery().in(this); let nodesRef = query.selectAll(".image-bg"); nodesRef.fields({ dataset: true, rect:true },(result)=>{ this.setData({ elements: result; fn(); }) }).exec() }, //刪除 onDelImage(event){ let images = this.data.images; images.splice(event.target.dataset.index,1) this.setData({ images }) this.initImg(this.triggerMsg(images, "delete-img")) if(images.length== MAX_IMG_NUM-1){ this.setData({ selectPhoto:true }) } }, triggerMsg(images, key) { this.triggerEvent('chooseImg', { images: images.map(item => { return item.img }), key: key }) }, } })
上面代碼中最重要的就是initImg函數(shù)的這段代碼!它用來獲取wxml節(jié)點(diǎn)的相關(guān)屬性!fields API的參數(shù)及默認(rèn)值有:
id:false,//是否返回節(jié)點(diǎn)id rect:fasle,//是否返回節(jié)點(diǎn)布局位置 dataset: true,//返回數(shù)據(jù)集 size: true,//返回寬高 scrollOffset: true,//返回 scrollLeft,scrollTop properties: ['scrollX', 'scrollY'],//監(jiān)聽屬性名 computedStyle: ['margin', 'backgroundColor']//此處返回指定要返回的樣式名
這個API的調(diào)用是后面定位的關(guān)鍵,它必須放在獲取到圖片數(shù)組之后執(zhí)行(不管同步還是異步的)。也是第二種方法和第一種方法的區(qū)別之處 —— 第一種方法是純 js 計算實(shí)時位置。所以它需要在結(jié)束后進(jìn)行排序。
這時候問題就來了:像本文這種場景,同時有 x 和 y 兩個方向的位置,sort將會極其復(fù)雜,而且sort本身的性能將會被內(nèi)部繁雜的代碼死死拖住。這就是上面說第一種方法性能問題的原因所在。
但是本文這種方法將sort簡化為當(dāng)前拖動元素和目標(biāo)位置圖片兩個物體的四個方向判斷,也就是經(jīng)典“小球撞墻”臨界問題。這也是其優(yōu)勢所在。
另一個需要注意的地方就是 touchm函數(shù)中的setData。這里面進(jìn)行的是拖拽元素位置改變,也就是“跟手率”(我自己編的)??梢詫Υ藬?shù)值進(jìn)行微調(diào)來讓效果更加nice一些。
使用方式
首先在json文件中進(jìn)行組件引入:
{ "usingComponents": { "y-img":"/components/yImg/index" } }
然后再wxml中:
<view class="container"> <y-img bind:chooseImg="chooseImg"></y-img> <!--或:--> <y-img yMovable bind:chooseImg="chooseImg"></y-img> </view>
chooseImg(e) { console.log(e.detail) },
GitHub地址:https://github.com/1314mxc/yunUI#img,歡迎使用、查看和star!
總結(jié)
到此這篇關(guān)于微信小程序圖片上傳組件實(shí)現(xiàn)圖片拖拽排序的文章就介紹到這了,更多相關(guān)微信小程序圖片拖拽排序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 微信小程序?qū)崿F(xiàn)上傳照片代碼實(shí)例解析
- uni-app開發(fā)微信小程序之H5壓縮上傳圖片的問題詳解
- 微信小程序?qū)崿F(xiàn)云開發(fā)上傳文件、圖片功能
- 微信小程序?qū)崿F(xiàn)多文件或者圖片上傳
- 微信小程序?qū)崿F(xiàn)上傳圖片
- 微信小程序?qū)崿F(xiàn)上傳圖片的功能
- 微信小程序?qū)崿F(xiàn)上傳多張圖片、刪除圖片
- 微信小程序?qū)崿F(xiàn)同時上傳多張圖片
- 微信小程序?qū)崿F(xiàn)一張或多張圖片上傳(云開發(fā))
- 微信小程序?qū)崿F(xiàn)文件、圖片上傳功能
- 微信小程序?qū)崿F(xiàn)多張照片上傳功能
相關(guān)文章
TypeScript中的互斥類型實(shí)現(xiàn)方法示例
用了一年時間的TypeScript了,下面這篇文章主要給大家介紹了關(guān)于TypeScript中互斥類型實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04JavaScript中常用的字符串方法函數(shù)操作方法總結(jié)
這篇文章主要介紹了JavaScript中所有的字符串函數(shù)操作方法整理匯總,包括字符串的長度、連接、查找、截取、替換、分隔、轉(zhuǎn)換等處理方法,以及網(wǎng)址中獲取文件名等等,需要的朋友可以參考下2023-12-12html+css+js實(shí)現(xiàn)canvas跟隨鼠標(biāo)的小圓特效源碼
這篇文章主要介紹了html+css+js實(shí)現(xiàn)canvas跟隨鼠標(biāo)的小圓特效源碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03JavaScript處理數(shù)組數(shù)據(jù)的示例詳解
這篇文章主要為大家詳細(xì)介紹了JavaScript如何處理數(shù)組數(shù)據(jù),包括數(shù)據(jù)匹配和剔除,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-10-10JavaScript子窗口調(diào)用父窗口變量和函數(shù)的方法
這篇文章主要介紹了JavaScript子窗口調(diào)用父窗口變量和函數(shù)的方法,涉及JavaScript窗口調(diào)用的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10純js實(shí)現(xiàn)html轉(zhuǎn)pdf的簡單實(shí)例(推薦)
下面小編就為大家?guī)硪黄僯s實(shí)現(xiàn)html轉(zhuǎn)pdf的簡單實(shí)例(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02微信小程序基于movable-view實(shí)現(xiàn)滑動刪除效果
這篇文章主要介紹了微信小程序基于movable-view實(shí)現(xiàn)滑動刪除效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01用JavaScript實(shí)現(xiàn)PHP的urlencode與urldecode函數(shù)
這篇文章主要介紹了用JavaScript實(shí)現(xiàn)PHP的urlencode與urldecode函數(shù),很多情況下我們用了出來php urlencode出來的網(wǎng)址,需要的朋友可以參考下2015-08-08