在之前的博文中,已經介紹了如何使用在前端開發(fā)中,實現較方便自由的圖片裁剪功能,可見博文: 如何一步步實現圖片裁剪功能。
本文將進一步講述在微信小程序中,我們實現圖片裁剪功能需要如何處理,文章末尾將附上小程序端圖片裁剪完整源碼的下載鏈接。
在小程序中進行圖片裁剪的效果如下圖所示:
圖片上傳與處理
要做圖片裁剪,首先要解決的是圖片上傳的事情,這塊在微信環(huán)境下,比較好處理,微信小程序提供了多個文件或圖片的操作接口,讓我們可以很方便的完成這個步驟。
本博之前關于小程序的圖片審核的文章:小程序圖片上傳與內容安全審核,已介紹了使用 wx.chooseMedia
接口來選擇圖片文件。
除此外,還可以有其他方式,如 wx.chooseMessageFile
接口可以從聊天記錄里選擇圖片,我們可以綜合處理如下:
function selectPhoto (callBack) {
let itemList = [ '拍照', '從手機相冊選擇', '從聊天記錄選擇' ]
wx.showActionSheet({
itemList,
success: (action) => {
if (action.tapIndex === 0) {
// 從拍照選擇圖片,使用 chooseMedia
wx.chooseMedia({ sourceType: [ 'camera' ] })
} else if (action.tapIndex === 1) {
// 從手機相冊選擇圖片,使用 chooseMedia
wx.chooseMedia({ sourceType: [ 'album' ] })
} else if (action.tapIndex === 2) {
// 從聊天記錄選擇圖片,使用 chooseMessageFile
wx.chooseMessageFile({})
}
}
})
}
其中,showActionSheet
可以顯示操作菜單,這個功能也比較常見,可以從拍照、相冊、聊天記錄里選擇文件進行加載:
當前,我們獲取到的是圖片文件的臨時路徑,得到圖片路徑以后,接下來要做的事情,就是如何正確適配的顯示出來。
圖片尺寸適配
由于要在前端對圖片進行裁剪,使用canvas繪制圖片也是不可少的,在繪制之前,我們需要根據圖片尺寸進行適配處理。
首先需要做的就是讀取圖片的尺寸大小,使用小程序的 wx.getImageInfo
接口即可,它能夠返回圖片原始的寬高屬性。
接著,根據圖片的寬高屬性、系統(tǒng)屏幕的寬高,一起在小程序頁面可見區(qū)域內設置圖片的縮放顯示:
// 根據窗口大小和圖片大小,設置圖片顯示尺寸以及縮放
// imgHeight 和 imgWidth 表示當前圖片的寬高
// 設置圖片顯示面板的寬高
let panelW = sys.windowWidth - 20
let panelH = sys.windowHeight - 100
if (panelH / panelW >= imgHeight / imgWidth) {
panelH = parseInt(panelW * imgHeight / imgWidth)
} else {
panelW = parseInt(panelH * imgWidth / imgHeight)
}
// 圖片在寬高上的縮放比,用于裁剪圖片時計算圖片實際尺寸
xScale = panelW / imgWidth
yScale = panelH / imgHeight
this.setData({
imagePath,
// 圖片顯示面板寬高
panelWidth: panelW,
panelHeight: panelH,
// 裁剪框的寬高,初始時與圖片面板一樣
clipWidth: panelW,
clipHeight: panelH,
// 裁剪的實際寬高
croppingImageWidth: imgWidth,
croppingImageHeight: imgHeight
})
通過以上代碼和注釋,我們知道了圖片加載時,需要計算的幾個寬高尺寸值,都是比較重要的。
圖片顯示與裁剪框
下面就可以在頁面上顯示圖片,直接展示圖片顯示區(qū)域的 wxml
代碼:
<view wx:if="{{imagePath}}" class="crop-container">
<view class="crop-content" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}};">
<image src="{{imagePath}}" class="pos-absolute" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: 0; top:0;"/>
<view class="mask-bg" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: 0; top: 0;"></view>
<view class="clip-path" style="width: {{clipWidth + 'px'}}; height: {{clipHeight + 'px'}}; left: {{clipX + 'px'}}; top: {{clipY + 'px'}};">
<image src="{{imagePath}}" class="pos-absolute" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: {{clipImgX + 'px'}}; top: {{clipImgY + 'px'}};"/>
</view>
<view class="clip-box" bind:touchmove="touchmoveM" bind:touchstart="touchstartM" style="width: {{clipWidth + 'px'}}; height: {{clipHeight + 'px'}}; left: {{clipX + 'px'}}; top: {{clipY + 'px'}};">
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="leftTop" class="corner-area left-top"></view>
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="rightTop" class="corner-area right-top"></view>
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="rightBottom" class="corner-area right-bottom"></view>
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="leftBottom" class="corner-area left-bottom"></view>
</view>
</view>
</view>
以上代碼中,
-
.crop-content
外層增加圖片面板,圖片資源直接使用<image>
組件加載,與外層面板寬高保持一致; -
.mask-bg
增加一個遮罩層,作為非裁剪區(qū)域的蒙層,以黑灰色透明度實現效果; -
.clip-path
于圖片面板內部,設置圖片的裁剪區(qū)域,這里使用的是CSS中的clip-path
屬性,內部加載一張圖片,作為裁剪區(qū)域的圖片顯示;clip-path
屬性之前已有博文介紹,可以查看 clip-path屬性 -
clip-box
設置裁剪框區(qū)域的四個corner角:左上、右上、右下和左下,里面四個元素對應這個四個角,可以對裁剪框進行縮放處理;
當然,我們也可以在各邊的中間位置再加上操作區(qū),那一共就是8個; -
.clip-path
圖片裁剪區(qū)與clip-box
裁剪框需要設置位置信息,用于在移動的時候進行定位。
通過設置對應的圖片元素、蒙層、裁剪框等等,我們就已經完成了圖片裁剪的結構布局了,具體可見下圖所示:
圖片裁剪框設置完成后,我們需要處理的就是裁剪框的拖動與縮放事件處理了。
裁剪框的拖動與縮放
上面的 wxml
代碼中,在裁剪框的元素部分,已經增加了 touchstart
和 touchmove
等事件,用于在處理拖動和縮放等操作。
當我們實現裁剪框的拖動,只需要如下兩個事件:
touchstartM (event) {
const { clipX, clipY } = this.data
const { pageX, pageY } = event.touches[0]
// 獲取鼠標點在裁剪框的內部位置信息
clipBoxMoveInnerX = pageX - clipX
clipBoxMoveInnerY = pageY - clipY
},
touchmoveM (event) {
const { pageX, pageY } = event.touches[0]
const { panelWidth, panelHeight, clipHeight, clipWidth } = this.data
// 裁剪框不能脫離面板
// X位置范圍為 0 到 (面板寬度-裁剪框寬度)
let clipX = pageX - clipBoxMoveInnerX
clipX = Math.max(clipX, 0)
const panelX = panelWidth - clipWidth
clipX = Math.min(clipX, panelX)
// Y位置范圍為 0 到 (面板高度-裁剪框高度)
let clipY = pageY - clipBoxMoveInnerY
clipY = Math.max(clipY, 0)
const panleY = panelHeight - clipHeight
clipY = Math.min(clipY, panleY)
// 裁剪框底圖位置信息
const clipImgX = 0 - clipX
const clipImgY = 0 - clipY
this.setData({
clipX,
clipY,
clipImgX,
clipImgY
})
}
以上代碼,通過計算圖片移動時的相對位移,重新計算裁剪框的新的位置信息,需要注意的是,移動時不要脫離外層的面板——即不能脫離圖片區(qū)域,否則無效。
縮放的操作則相對復雜一些,需要計算位移移動的距離以及當前位置下的裁剪寬高數據,并且要對每個不同的corner角進行特殊處理,這塊的完整代碼和注釋如下所示:
// 處理縮放動作中不同corner時的尺寸位置信息
getClipX (clipWidth) {
switch (activeCorner) {
case 'leftTop':
case 'leftBottom':
return clipBoxBeforeScaleClipX + (clipBoxBeforeScaleWidth - clipWidth)
case 'rightTop':
case 'rightBottom':
return clipBoxBeforeScaleClipX
default:
return 0
}
},
getClipY (clipHeight) {
switch (activeCorner) {
case 'leftTop':
case 'rightTop':
return clipBoxBeforeScaleClipY + (clipBoxBeforeScaleHeight - clipHeight)
case 'leftBottom':
case 'rightBottom':
return clipBoxBeforeScaleClipY
default:
return 0
}
},
getScaleXWidthOffset (offsetW) {
switch (activeCorner) {
case 'leftTop':
case 'leftBottom':
return -offsetW
case 'rightTop':
case 'rightBottom':
return offsetW
default:
return 0
}
},
getScaleYHeightOffset (offsetH) {
switch (activeCorner) {
case 'rightBottom':
case 'leftBottom':
return offsetH
case 'rightTop':
case 'leftTop':
return -offsetH
default:
return 0
}
},
touchstart (event) {
const dragId = event.currentTarget.dataset.id
const { pageX, pageY } = event.touches[0]
const { clipX, clipY, clipHeight, clipWidth } = this.data
// 設置縮放時臨時變量初始化值
activeCorner = dragId
clipBoxBeforeScalePageX = pageX
clipBoxBeforeScalePageY = pageY
clipBoxBeforeScaleClipX = clipX
clipBoxBeforeScaleClipY = clipY
clipBoxBeforeScaleWidth = clipWidth
clipBoxBeforeScaleHeight = clipHeight
},
touchmove (event) {
const { pageX, pageY } = event.touches[0]
const { panelWidth, panelHeight } = this.data
// 縮放在X上的偏移
const xWidthOffset = this.getScaleXWidthOffset(pageX - clipBoxBeforeScalePageX)
// 裁剪框最小寬度36
let clipWidth = Math.max(clipBoxBeforeScaleWidth + xWidthOffset, 36)
// 設置縮放最大寬度,放大時不能超過面板、縮小時不能超過初始裁剪框
let tempPanelWidth = pageX > clipBoxBeforeScalePageX ? panelWidth - clipBoxBeforeScaleClipX : clipBoxBeforeScaleWidth + clipBoxBeforeScaleClipX
// 設置裁剪框寬度
clipWidth = Math.min(clipWidth, tempPanelWidth)
// 縮放在Y上的偏移
const yHeightOffset = this.getScaleYHeightOffset(pageY - clipBoxBeforeScalePageY)
// 裁剪框最小高度36
let clipHeight = Math.max(clipBoxBeforeScaleHeight + yHeightOffset, 36)
// 設置縮放最大高度,放大時不能超過面板、縮小時不能超過初始裁剪框
let tempPanelHeight = pageY > clipBoxBeforeScalePageY ? panelHeight - clipBoxBeforeScaleClipY : clipBoxBeforeScaleHeight + clipBoxBeforeScaleClipY
// 設置裁剪框高度
clipHeight = Math.min(clipHeight, tempPanelHeight)
// 裁剪框位置信息
let clipX = this.getClipX(clipWidth)
let clipY = this.getClipY(clipHeight)
// 裁剪框底圖位置信息
let clipImgX = 0 - clipX
let clipImgY = 0 - clipY
this.setData({
clipWidth,
clipHeight,
clipX,
clipY,
clipImgX,
clipImgY,
croppingImageWidth: parseInt(clipWidth / xScale),
croppingImageHeight: parseInt(clipHeight / yScale)
})
}
至此,圖片裁剪的功能基本完成了,能夠加載圖片、設置裁剪框、拖動和縮放裁剪框大小,計算裁剪圖片的尺寸等等。
就剩下如何真正剪裁圖片了。
增加canvas并裁剪圖片
要剪裁圖片,我們在小程序使用canvas畫布組件來處理,在頁面上增加一個canvas元素:
<canvas id="main" canvasId="main" class="main-canvas" style="width: {{croppingImageWidth + 'px'}}; height: {{croppingImageHeight + 'px'}}"></canvas>
由于這個canvas只是用來對圖片進行裁剪操作,并不需要顯示出來,我們可以把它定位到視覺窗口以外,不影響可操作的界面元素。
給畫布設置圖片裁剪所需要的寬高,通過在同等寬高下繪制圖片,就能導出圖片:
wx.showLoading({ title: '正在裁剪...' })
preCtx.clearRect(0, 0, imageWidth, imageHeight)
const width = croppingImageWidth
const height = croppingImageHeight
const xPos = Math.abs(clipImgX / xScale)
const yPos = Math.abs(clipImgY / yScale)
// 繪制裁剪區(qū)內的圖片到畫布上
preCtx.drawImage(imagePath, xPos, yPos, width, height, 0, 0, width, height)
preCtx.save()
preCtx.restore()
const that = this
preCtx.draw(false, function () {
setTimeout(() => {
// 將畫布導出為臨時圖片文件
wx.canvasToTempFilePath({
x: 0,
y: 0,
width,
height,
destWidth: width,
destHeight: height,
canvasId: 'main',
success: (canRes) => {
wx.hideLoading()
that.initImage(width, height, canRes.tempFilePath)
},
fail (err) {
wx.hideLoading()
console.log(err)
}
})
}, 200)
})
如上代碼所示,通過裁剪圖片的真實寬高以及相對位置信息,通過canvas的 drawImage
方法,把圖片的裁剪區(qū)域的內容繪制到畫布上,此時,該畫布就對應一張裁剪后的圖片,我們只需要把畫布導出成圖片文件即可。
導出畫布,使用 wx.canvasToTempFilePath
方法,用于將畫布導出為圖片臨時圖片文件,這個圖片文件可以重新加載或者進行導出。
保存圖片到相冊
以上過程,生成裁剪圖片的臨時文件后,就可以使用小程序提供的API,將圖片文件保存到相冊中。
只需要使用 wx.saveImageToPhotosAlbum
方法,專門用于將圖片文件保存到系統(tǒng)相冊,接收臨時文件作為參數:
wx.saveImageToPhotosAlbum({
filePath: imgSrc,
success: () => {
wx.hideLoading()
wx.vibrateShort()
wx.showModal({
content: '圖片已保存到相冊~',
showCancel: false,
confirmText: '好的',
confirmColor: '#333'
})
}
})
該方法簡單方便,其中使用 wx.vibrateShort()
方法,作用是使手機發(fā)生較短時間的振動(15 ms),在小程序中也是常見的功能。
圖片保存到系統(tǒng)相冊功能完成后,我們就實現了在小程序中進行圖片剪裁的完整功能,包含加載圖片、圖片適配和裁剪框繪制、裁剪框拖動與縮放事件、圖片導出和保存的過程。文章來源:http://www.zghlxwxcb.cn/news/detail-414525.html
總結
本文詳細講述了在小程序中實現一個圖片裁剪功能的過程,可以看出和在瀏覽器Web端的實現差別并不大。
在核心的圖片適配、裁剪框繪制與縮放事件處理上,基本兩邊可以通用,在小程序中的實現邏輯可以完整在移到web瀏覽器上,反之亦然。
區(qū)別可能只在于圖片的加載和保存上,可以使用小程序提供的多種內置接口方法,能較方便的完成。
上文也附上了大量的核心代碼,根據這些代碼已經基本可以還原裁剪功能,如果需要完整的小程序圖片裁剪代碼,可以訪問下載:小程序圖片裁剪源碼下載文章來源地址http://www.zghlxwxcb.cn/news/detail-414525.html
到了這里,關于微信小程序圖片裁剪功能的實現的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!