微信小程序內(nèi)拖動(dòng)圖片實(shí)現(xiàn)移動(dòng)、放大、旋轉(zhuǎn)的方法
屏幕就像是數(shù)學(xué)上的坐標(biāo)軸,且在第四象限,以屏幕左上角為圓點(diǎn),X軸向右為正向左為負(fù),Y軸向下為正向上為負(fù)(這點(diǎn)和數(shù)學(xué)上相反的)以圓點(diǎn)為基點(diǎn)畫個(gè)距離圓點(diǎn)上下50寬高100的矩形來演示canvas基本用法
微信小程序這里提供了兩個(gè)API
wx.createContext() 創(chuàng)建并返回繪圖上下文context對(duì)象
- getActions 獲取當(dāng)前context上存儲(chǔ)的繪圖動(dòng)作,對(duì)應(yīng)wx.drawCanvas(object)中的actions
- clearActions 清空當(dāng)前的存儲(chǔ)繪圖動(dòng)作
wx.drawCanvas(object) 繪制
- canvasId 畫布標(biāo)識(shí),傳入的cavas-id,這里的標(biāo)識(shí)可以為Number,也可以是String
- actions 繪圖動(dòng)作數(shù)組,由wx.createContext創(chuàng)建的context,調(diào)用getActions方法導(dǎo)出繪圖動(dòng)作數(shù)組。
最近接到一個(gè)任務(wù),在微信小程序內(nèi)拖動(dòng)圖片組件實(shí)現(xiàn)移動(dòng)、放大、旋轉(zhuǎn),并記錄這些圖片的移動(dòng)位置,放大比例,旋轉(zhuǎn)角度,在一個(gè)畫布上生成一張圖片,最后保存到手機(jī)相冊(cè)。
我的具體實(shí)現(xiàn)思路是這樣的:
一共三個(gè)功能,可以先把功能分為圖片 拖動(dòng) 和圖片 旋轉(zhuǎn)縮放 , 把圖片的縮放和旋轉(zhuǎn)做在了一起。
1.圖片移動(dòng):可移動(dòng)的圖片肯定是要?jiǎng)討B(tài)生成的,所以不能寫死,應(yīng)該是個(gè)數(shù)組,具備很多的屬性。
例如:(并不是我項(xiàng)目的真實(shí)數(shù)據(jù))
itemList: [{
id: 1,
image: '1.png',//圖片地址
top: 100,//初始圖片的位置
left: 100,
x: 155, //初始圓心位置,可再downImg之后又寬高和初始的圖片位置得出
y: 155,
scale: 1,//縮放比例 1為不縮放
angle: 0,//旋轉(zhuǎn)角度
active: false //判定點(diǎn)擊狀態(tài)
}, {
id: 2,
image: '2.png',
top: 50,
left: 50,
x: 155,
y: 155,
scale: 1,
angle: 0,
active: false
事件綁定圖片上或者圖片的父級(jí),綁定bindtouchstart bindtouchmove事件。再bindtouchstart事件里,獲取手指點(diǎn)擊的某一個(gè)圖片的點(diǎn)擊坐標(biāo),并記錄在這個(gè)圖片對(duì)象的屬性里面,在bindtouchmove事件里,移動(dòng)的時(shí)候記錄移動(dòng)后的坐標(biāo),并算出倆次滑動(dòng)的距離差值,追加給圖片對(duì)象的left、top、x、y上,最后把本次滑動(dòng)的坐標(biāo)賦值給bindtouchmove事件里拿到的坐標(biāo),作為老坐標(biāo)。這樣就可以實(shí)現(xiàn)圖片的滑動(dòng)。
注:代碼里的 items 只是我定義的一個(gè)全局變量,是一個(gè)空數(shù)組,在onLoad函數(shù)里 items = this.data.itemLits;
這樣就不會(huì)頻繁的去setData,我只需要處理items,處理完之后,再this.setData({itemLits:items })
WraptouchStart: function (e) {
for (let i = 0; i < items.length; i++) { //旋轉(zhuǎn)數(shù)據(jù)找到點(diǎn)擊的
items[i].active = false;
if (e.currentTarget.dataset.id == items[i].id) {
index = i; //記錄下標(biāo)
items[index].active = true; //開啟點(diǎn)擊屬性
}
}
items[index].lx = e.touches[0].clientX; // 記錄點(diǎn)擊時(shí)的坐標(biāo)值
items[index].ly = e.touches[0].clientY;
this.setData({ //賦值
itemList: items
})
}
, WraptouchMove: function (e) {
//移動(dòng)時(shí)的坐標(biāo)值也寫圖片的屬性里
items[index]._lx = e.touches[0].clientX;
items[index]._ly = e.touches[0].clientY;
//追加改動(dòng)值
items[index].left += items[index]._lx - items[index].lx; // x方向
items[index].top += items[index]._ly - items[index].ly; // y方向
items[index].x += items[index]._lx - items[index].lx;
items[index].y += items[index]._ly - items[index].ly;
//把新的值賦給老的值
items[index].lx = e.touches[0].clientX;
items[index].ly = e.touches[0].clientY;
this.setData({//賦值就移動(dòng)了
itemList: items
})
}
2.圖片的旋轉(zhuǎn)和縮放,因?yàn)閳D片上已經(jīng)有了touch事件,所以解決辦法采用常規(guī)的在圖片的一角添加一個(gè)控件解決這個(gè)問題,控件大致如圖:

左邊控件是刪除按鈕,右邊控件則是手指按著旋轉(zhuǎn)切縮放圖片的控件,綁定bindtouchstart bindtouchmove事件。
index也是設(shè)置的全局變量。
// 觸摸開始事件 items是this.data.itemList的全局變量,便于賦值 所有的值都應(yīng)給到對(duì)應(yīng)的對(duì)象里
touchStart: function (e) {
//找到點(diǎn)擊的那個(gè)圖片對(duì)象,并記錄
for (let i = 0; i < items.length; i++) {
items[i].active = false;
if (e.currentTarget.dataset.id == items[i].id) {
console.log('e.currentTarget.dataset.id', e.currentTarget.dataset.id)
index = i;
console.log(items[index])
items[index].active = true;
}
}
//獲取作為移動(dòng)前角度的坐標(biāo)
items[index].tx = e.touches[0].clientX;
items[index].ty = e.touches[0].clientY;
//移動(dòng)前的角度
items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)
//獲取圖片半徑
items[index].r = this.getDistancs(items[index].x, items[index].y, items[index].left, items[index].top)
},
// 觸摸移動(dòng)事件
touchMove: function (e) {
//記錄移動(dòng)后的位置
items[index]._tx = e.touches[0].clientX;
items[index]._ty = e.touches[0].clientY;
//移動(dòng)的點(diǎn)到圓心的距離 * 因?yàn)閳A心的坐標(biāo)是相對(duì)與父元素定位的 ,所有要減去父元素的OffsetLeft和OffsetTop來計(jì)算移動(dòng)的點(diǎn)到圓心的距離
items[index].disPtoO = this.getDistancs(items[index].x, items[index].y, items[index]._tx - this.sysData.windowWidth * 0.125, items[index]._ty - 10)
items[index].scale = items[index].disPtoO / items[index].r; //手指滑動(dòng)的點(diǎn)到圓心的距離與半徑的比值作為圖片的放大比例
items[index].oScale = 1 / items[index].scale;//圖片放大響應(yīng)的右下角按鈕同比縮小
//移動(dòng)后位置的角度
items[index].angleNext = this.countDeg(items[index].x, items[index].y, items[index]._tx, items[index]._ty)
//角度差
items[index].new_rotate = items[index].angleNext - items[index].anglePre;
//疊加的角度差
items[index].rotate += items[index].new_rotate;
items[index].angle = items[index].rotate; //賦值
//用過移動(dòng)后的坐標(biāo)賦值為移動(dòng)前坐標(biāo)
items[index].tx = e.touches[0].clientX;
items[index].ty = e.touches[0].clientY;
items[index].anglePre = this.countDeg(items[index].x, items[index].y, items[index].tx, items[index].ty)
//賦值setData渲染
this.setData({
itemList: items
})
}
頁面上是這樣寫的:
<!-- *************操作區(qū)域************* -->
<block wx:for="{{itemList}}" wx:key="{{item.id}}">
<!-- 圓心坐標(biāo) <text style='position:absolute;top:{{item.y}}px;left:{{item.x}}px;width:2px;height:2px;background-color:yellow;z-index:500'></text> -->
<view class='touchWrap' style='transform: scale({{item.scale}});top:{{item.top}}px;left:{{item.left}}px; '>
<view class='imgWrap {{item.active? "touchActive":""}}' style="transform: rotate({{item.angle}}deg);">
<image src='{{item.image}}' data-id='{{item.id}}' style='width:{{item.width}}px;height:{{item.height}}px;' bindtouchstart='WraptouchStart' bindload='loadImg' hidden='{{!item.isload}} bindtouchmove='WraptouchMove' bindtouchend='WraptouchEnd'></image>
<image class='x' src='../../images/x.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtap='deleteItem'></image>
<image class='o' src='../../images/o.png' style='transform: scale({{item.oScale}});transform-origin:center;' data-id='{{item.id}}' bindtouchstart='touchStart' bindtouchmove='touchMove' bindtouchend='touchEnd'></image>
</view>
</view>
</block>
<!-- **************操作區(qū)域************ -->
這樣一來就解決了微信小程序內(nèi)拖動(dòng)圖片實(shí)現(xiàn)移動(dòng)、放大、旋轉(zhuǎn)的問題,操作也比較順滑,也耗費(fèi)我近四天的時(shí)間才把我的小程序上線,代碼有點(diǎn)混亂,如果各位大佬有什么意見可以給我留言,我的小程序名字是:水逆轉(zhuǎn)運(yùn)符文,以后會(huì)持續(xù)改進(jìn)。
2018/5/7補(bǔ)充一條生成圖片時(shí),組件的屬性:

我的失誤,忘了附上角度計(jì)算函數(shù) countDeg :
/*
*參數(shù)1和2為圖片圓心坐標(biāo)
*參數(shù)3和4為手點(diǎn)擊的坐標(biāo)
*返回值為手點(diǎn)擊的坐標(biāo)到圓心的角度
*/
countDeg: function (cx, cy, pointer_x, pointer_y) {
var ox = pointer_x - cx;
var oy = pointer_y - cy;
var to = Math.abs(ox / oy);
var angle = Math.atan(to) / (2 * Math.PI) * 360;//鼠標(biāo)相對(duì)于旋轉(zhuǎn)中心的角度
console.log("ox.oy:", ox, oy)
if (ox < 0 && oy < 0)//相對(duì)在左上角,第四象限,js中坐標(biāo)系是從左上角開始的,這里的象限是正常坐標(biāo)系
{
angle = -angle;
} else if (ox <= 0 && oy >= 0)//左下角,3象限
{
angle = -(180 - angle)
} else if (ox > 0 && oy < 0)//右上角,1象限
{
angle = angle;
} else if (ox > 0 && oy > 0)//右下角,2象限
{
angle = 180 - angle;
}
return angle;
}
計(jì)算觸摸點(diǎn)到圓心的距離:
getDistancs(cx, cy, pointer_x, pointer_y) {
var ox = pointer_x - cx;
var oy = pointer_y - cy;
return Math.sqrt(
ox * ox + oy * oy
);
}
點(diǎn)擊配件時(shí)的事件(因?yàn)樵傥覝y(cè)試在canvas中,圖片不能是網(wǎng)絡(luò)路徑,所以需要下載): 【18/6/22】
tpDownload: function(data, isDownload) { //data為組件的參數(shù),isDownload判斷是否為https網(wǎng)絡(luò)圖片來判斷是否需要下載
if (yy < 0) { //改變生成圖片時(shí)的位置
speed = -speed
}
if (yy > 300) {
speed = -speed
}
yy += speed;
let _this = this;
let newTpdata = {};
newTpdata.id = data.id;
newTpdata.itemid = data.itemid;
newTpdata.top = 100 + yy;
newTpdata.left = 100;
newTpdata.width = _this.sysData.windowWidth / 4;
newTpdata.scale = 1;
newTpdata.angle = 0;
newTpdata.rotate = 0;
newTpdata.active = true;
for (let i = 0; i < items.length; i++) {
items[i].active = false;
}
if (isDownload) {
wx.downloadFile({
url: data.image,
success: res => {
newTpdata.image = res.tempFilePath;
items.push(newTpdata);
_this.setData({
itemList: items
})
wx.hideLoading();
}
})
} else {
newTpdata.image = data.image;
items.push(newTpdata);
_this.setData({
itemList: items
})
wx.hideLoading();
}
}
我的項(xiàng)目中生成canvas用到的代碼 (繪圖是通過保存按鈕觸發(fā))
save: function() {
this.setData({
showCanvas: true,
canvasHeight: this.sysData.windowHeight * 0.85
})
let obj = this.data.item;
/*
canvasWidth值為canvas寬度;
this.data.canvasPre是占屏幕寬度的百分比(80)
*/
let canvasWidth = this.sysData.windowWidth * this.data.canvasPre / 100; //
/*
num為canvas內(nèi)背景圖占canvas的百分比,若全背景num =1
this.sysData.windowWidth * 0.75為可移動(dòng)區(qū)的寬度
prop值為canvas內(nèi)背景的寬度與可移動(dòng)區(qū)域的寬度的比,如一致,則prop =1;
*/
let prop = (canvasWidth * num) / (this.sysData.windowWidth * 0.75);
maskCanvas.save();
maskCanvas.beginPath();
//一張白圖
maskCanvas.setFillStyle('#fff');
maskCanvas.fillRect(0, 0, this.sysData.windowWidth, this.data.canvasHeight)
maskCanvas.closePath();
maskCanvas.stroke();
//圖頭像
let image = {
w: canvasWidth * num * 0.287,
h: canvasWidth * num * 0.287,
r: canvasWidth * num * 0.287 / 2
};
//畫背景 hCw 為 1.7781 背景圖的高寬比
maskCanvas.drawImage(obj.bgImg, canvasWidth * (1 - num) / 2, 10, canvasWidth * num, canvasWidth * num * hCw)
//畫底圖
maskCanvas.drawImage('../../images/xcx.png', canvasWidth * (1 - num) / 2, canvasWidth * num * hCw + 15, canvasWidth * num, this.data.canvasHeight * 0.15)
//畫原
maskCanvas.save();
maskCanvas.beginPath();
maskCanvas.arc(canvasWidth / 2, canvasWidth * num * hCw * obj.userTop / 100 + 10 + image.w / 2, image.r, 0, Math.PI * 2, false);
// maskCanvas.stroke()
maskCanvas.clip(); //截取
//畫頭像
maskCanvas.drawImage(obj.avatarUrl, (canvasWidth - image.w) / 2, canvasWidth * num * hCw * obj.userTop / 100 + 10, image.w, image.h)
maskCanvas.closePath();
maskCanvas.restore();
//繪制文字
maskCanvas.save();
maskCanvas.beginPath();
let fontSize = this.sysData.screenWidth / 375 * 15;
let textColor = obj.color || '#000';
maskCanvas.setFontSize(parseInt(fontSize) * prop)
maskCanvas.setFillStyle(textColor)
maskCanvas.setTextAlign('center')
maskCanvas.fillText(obj.nickName, canvasWidth / 2, obj.titleTop / 100 * canvasWidth * num * hCw + 10 * 0.9 * prop + fontSize * prop);
maskCanvas.closePath();
maskCanvas.stroke();
/**
* x
* y
* scale
* prop
* width
* height
*
*/
//畫組件
items.forEach((currentValue,index)=>{
maskCanvas.save();
maskCanvas.translate(canvasWidth * (1 - num) / 2, 10);
maskCanvas.beginPath();
maskCanvas.translate(currentValue.x * prop, currentValue.y * prop); //圓心坐標(biāo)
maskCanvas.rotate(currentValue.angle * Math.PI / 180); // 旋轉(zhuǎn)值
maskCanvas.translate(-(currentValue.width * currentValue.scale * prop / 2), -(currentValue.height * currentValue.scale * prop / 2))
maskCanvas.drawImage(currentValue.image, 0, 0, currentValue.width * currentValue.scale * prop, currentValue.height * currentValue.scale * prop);
maskCanvas.restore();
})
maskCanvas.draw(false, (e)=> {
wx.canvasToTempFilePath({
canvasId: 'maskCanvas',
success: res => {
this.setData({
canvasTemImg: res.tempFilePath
})
}
}, this)
})
}
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript設(shè)計(jì)模式之代理模式介紹
這篇文章主要介紹了JavaScript設(shè)計(jì)模式之代理模式介紹,代理模式顧名思義就是用一個(gè)類來代替另一個(gè)類來執(zhí)行方法功能,需要的朋友可以參考下2014-12-12
javascript 多種搜索引擎集成的頁面實(shí)現(xiàn)代碼
這個(gè)頁面是為了方便自己同時(shí)使用多種搜索引擎(呵呵我用其作默認(rèn)主頁),在IE5/IE6/FireFox下均運(yùn)行正常,效果如下圖2010-01-01
[分享]一個(gè)非常漂亮的進(jìn)度滾動(dòng)條
[分享]一個(gè)非常漂亮的進(jìn)度滾動(dòng)條...2007-02-02
Code Review 方法論與實(shí)踐總結(jié)梳理
這篇文章主要為大家介紹了Code Review 方法論與實(shí)踐總結(jié)梳理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
解析如何利用iframe標(biāo)簽以及js制作時(shí)鐘
本文對(duì)如何利用iframe標(biāo)簽以及js制作時(shí)鐘進(jìn)行了全面解析,分步說明,條理清晰,感興趣的朋友可以看下2016-12-12
用director.js實(shí)現(xiàn)前端路由使用實(shí)例
本篇文章主要介紹了director.js實(shí)現(xiàn)前端路由,在不刷新的情況下,利用“#”號(hào)組織不同的URL路徑,并根據(jù)不同的URL路徑進(jìn)行不同的方法調(diào)用。有興趣的了解一下。2017-01-01
怎么理解wx.navigateTo的events參數(shù)使用詳情
這篇文章主要介紹了怎么理解wx.navigateTo的events參數(shù)使用詳情,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
javascript 極速 隱藏/顯示萬行表格列只需 60毫秒
隱藏表格列 這種方式的效率極低。例如,隱藏一個(gè)千行表格的某列,在我的筆記本(P4 M 1.4G,768M內(nèi)存)上執(zhí)行需要約 4000毫秒的時(shí)間,令人無法忍受。2009-03-03

