微信小程序圖片上傳組件實現(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),這里其實有兩種使用方式:
對每個元素使用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>
圖片只是展示;單獨設(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)勢在于:可以有更加“真實”的效果。這里的真實意為重新排列時也有滑動的動畫效果。但是帶來的性能損耗也是極大的,你只能盡力調(diào)控各種數(shù)據(jù)來讓顯示更加“跟手”一些。但是基于此,你可以通過js計算達(dá)到像QQ空間那樣的實時排列效果!
第二種方式的優(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) {
// 使手機振動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é)點的相關(guān)屬性!fields API的參數(shù)及默認(rèn)值有:
id:false,//是否返回節(jié)點id rect:fasle,//是否返回節(jié)點布局位置 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 計算實時位置。所以它需要在結(jié)束后進行排序。
這時候問題就來了:像本文這種場景,同時有 x 和 y 兩個方向的位置,sort將會極其復(fù)雜,而且sort本身的性能將會被內(nèi)部繁雜的代碼死死拖住。這就是上面說第一種方法性能問題的原因所在。
但是本文這種方法將sort簡化為當(dāng)前拖動元素和目標(biāo)位置圖片兩個物體的四個方向判斷,也就是經(jīng)典“小球撞墻”臨界問題。這也是其優(yōu)勢所在。
另一個需要注意的地方就是 touchm函數(shù)中的setData。這里面進行的是拖拽元素位置改變,也就是“跟手率”(我自己編的)??梢詫Υ藬?shù)值進行微調(diào)來讓效果更加nice一些。
使用方式
首先在json文件中進行組件引入:
{
"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)于微信小程序圖片上傳組件實現(xiàn)圖片拖拽排序的文章就介紹到這了,更多相關(guān)微信小程序圖片拖拽排序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 微信小程序?qū)崿F(xiàn)上傳照片代碼實例解析
- 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)文章
JavaScript中常用的字符串方法函數(shù)操作方法總結(jié)
這篇文章主要介紹了JavaScript中所有的字符串函數(shù)操作方法整理匯總,包括字符串的長度、連接、查找、截取、替換、分隔、轉(zhuǎn)換等處理方法,以及網(wǎng)址中獲取文件名等等,需要的朋友可以參考下2023-12-12
html+css+js實現(xiàn)canvas跟隨鼠標(biāo)的小圓特效源碼
這篇文章主要介紹了html+css+js實現(xiàn)canvas跟隨鼠標(biāo)的小圓特效源碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
JavaScript處理數(shù)組數(shù)據(jù)的示例詳解
這篇文章主要為大家詳細(xì)介紹了JavaScript如何處理數(shù)組數(shù)據(jù),包括數(shù)據(jù)匹配和剔除,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-10-10
JavaScript子窗口調(diào)用父窗口變量和函數(shù)的方法
這篇文章主要介紹了JavaScript子窗口調(diào)用父窗口變量和函數(shù)的方法,涉及JavaScript窗口調(diào)用的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
純js實現(xiàn)html轉(zhuǎn)pdf的簡單實例(推薦)
下面小編就為大家?guī)硪黄僯s實現(xiàn)html轉(zhuǎn)pdf的簡單實例(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02
微信小程序基于movable-view實現(xiàn)滑動刪除效果
這篇文章主要介紹了微信小程序基于movable-view實現(xiàn)滑動刪除效果,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01
用JavaScript實現(xiàn)PHP的urlencode與urldecode函數(shù)
這篇文章主要介紹了用JavaScript實現(xiàn)PHP的urlencode與urldecode函數(shù),很多情況下我們用了出來php urlencode出來的網(wǎng)址,需要的朋友可以參考下2015-08-08

