canvas如何實(shí)現(xiàn)多張圖片編輯的圖片編輯器
前言
圖片編輯器雖然是圖片涂鴉工具,但是在當(dāng)前疫情的大背景下,該工具還可以成為老師在線修改學(xué)生提交的家庭作業(yè)的工具使用的,它可以極大地減輕了老師的批改作業(yè)的工作負(fù)擔(dān),相信這軟件是有市場的。
本次不具體介紹canvas的各種操作畫布的api,而且相信大家在網(wǎng)上能搜到大量的關(guān)于canvas涂鴉的開源代碼,即使是這樣,我也相信真正把canvas涂鴉用于公司實(shí)際的項(xiàng)目的不多,改裝成圖片編輯器的會更少,而用于多張圖片編輯然后多張統(tǒng)一保存、上傳的更是寥寥無幾。下面,我將在我曾經(jīng)寫過的一個canvas涂鴉的基礎(chǔ)上,將多張圖片編輯器的開發(fā)和思考過程記錄下來。
圖片編輯器產(chǎn)品需求
先說需求,由于涉及到實(shí)際公司的項(xiàng)目開發(fā),滿足需求的圖片編輯器可能只是編輯一張單獨(dú)的圖片,可能是給你一個圖片列表也就是多張圖片的編輯,在用戶保存之前,用戶可以來回切換現(xiàn)在編輯哪張圖片,而且要記住每張照片的編輯操作,都要允許可以撤銷,最后統(tǒng)一點(diǎn)擊保存按鈕,沒有編輯過的圖片將被丟棄,已經(jīng)編輯過的帶著涂鴉的圖片上傳給服務(wù)器。
準(zhǔn)備工作,必須了解的相關(guān)知識點(diǎn)
html2canvas的使用
html2canvas這個插件實(shí)際是將網(wǎng)頁上的普通html元素轉(zhuǎn)化成canvas。那么為什么要將html元素轉(zhuǎn)化成canvas呢?那是因?yàn)閏anva具有許多html元素不具備的特點(diǎn),例如可以在canvas上畫圖,畫線條等等操作,而且canvas直接提供api可以將畫布上展示的內(nèi)容導(dǎo)成圖片。是不是有點(diǎn)類似于截圖,html2canvas就是利用這一點(diǎn)。 我最開始使用html2canvas時在公司h5項(xiàng)目有一個手寫板的項(xiàng)目。需要在是手寫板中簽字,然后將簽字之后的圖片導(dǎo)出然后上傳。后來又有一個在web端的圖片編輯器的項(xiàng)目,又用到了html2canvas這個插件,下面來介紹下該插件
介紹
html2canvas的缺點(diǎn):
用html2canvas轉(zhuǎn)成的canvas,在最后生成的視覺效果上并不是100%還原原來的元素,也就是說會有部分像素點(diǎn)會丟失。
html2canvas的安裝
npm install html2canvas
html2canvas的引入
import html2canvas from 'html2canvas'
html2canvas的使用
如果一個帶圖片(無論是img標(biāo)簽還是背景圖)的html元素在轉(zhuǎn)成canvas之后,該生成的canvas中img或者背景圖缺失,變成了白色的底,并且控制臺都告訴你圖片跨域了,解決方案如下:
以下第1條必須滿足,然后第2、3是根據(jù)是html元素是img標(biāo)簽還是背景圖,來添加不同的配置項(xiàng)。
1.圖片服務(wù)器配置Access-Control-Allow-Origin 或使用代理
2.html2canvas的配置項(xiàng)中配置useCORS:true
3.img標(biāo)簽增加 crossOrigin='anonymous'
html轉(zhuǎn)canvas,并且解決圖片跨域問題
html2canvas(dom, {
useCORS: true, // 背景圖片跨域
scale: window.devicePixelRatio,//像素比
width: dom.offsetWidth
height: dom.offsetHeight
}).then(canvas => {
//得到base64
let base64 = canvas.toDateUrl()
})
因?yàn)閔tml2canvas將普通的html轉(zhuǎn)成了canvas,而canvas自帶將自身轉(zhuǎn)成的base64的方法,而base64格式文件可以直接作為img標(biāo)簽的src,展示在頁面上
<img :src='base64' />
此時如果希望將生成的圖片上傳,base64是無法直接上傳的,需要將base64轉(zhuǎn)成blob格式。所以如下:我們將得到的base64數(shù)據(jù)傳進(jìn)base64ToBlob函數(shù),最后函數(shù)return的結(jié)果就是blob數(shù)據(jù)。請注意:該方法只是單純的將base64轉(zhuǎn)成blob的通用方法
base64轉(zhuǎn)blob
function base64ToBlob(base64){
var arr = base64.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return (new Blob([u8arr], { type: mime }))
}
其實(shí)canvas自帶將canas轉(zhuǎn)化成blob對象的方法
canvas轉(zhuǎn)blob
canvas.toBlob(file=>{
//此時file就是blob數(shù)據(jù)格式。
//成功上傳成blob之后需要為該blob數(shù)據(jù)添加name屬性,否則后面上傳到服務(wù)器會報錯:size not set
file.name = 'cover.png'
})
但是在實(shí)際項(xiàng)目開發(fā)中遇到部分低版本的手機(jī)瀏覽器中的canvas元素不支持toBlob方法,報錯toBlob is not a function。所以此時我們需要對當(dāng)前不支持canvas.toBlo方法的瀏覽器做polyfill
DOM canvas元素暴露了HTMLCanvasElement接口,該接口提供了用來操作一個canvas元素布局和呈現(xiàn)的屬性和方法HTMLCanvasElement接口繼承了element接口的屬性和方法。(來自MDN)
toBlob方法兼容性寫法
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var binStr = atob(this.toDataURL(type, quality).split(',')[1])
var len = binStr.length
var arr = new Uint8Array(len)
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i)
}
callback(new Blob([arr], { type: type || 'image/png' }))
}
})
}
以上代碼判斷當(dāng)前瀏覽器HTMLCanvasElement.prototype內(nèi)是否含有toBlob方法,如果沒有該方法,則為HTMLCanvasElement.prototype添加toBlob方法。
到目前為止,無論是base64直接轉(zhuǎn)成blob,還是canvas直接轉(zhuǎn)成blob,最后目的都是為了將blob數(shù)據(jù)上傳到服務(wù)器。請注意:當(dāng)前頁面的blob數(shù)據(jù)是存儲在緩沖區(qū)的,而不是像電腦本地文件存儲在硬盤中上傳那樣,所以需要添加 isLocalFile:false屬性,fileData屬性接收一個數(shù)組,如果是一張圖片,則是個長度為1的,元素為blob格式的數(shù)組。上傳文件到服務(wù)器在這里不做介紹。
提交blob到服務(wù)器
// 轉(zhuǎn)完圖片數(shù)據(jù),提交服務(wù)器
async upload (blobArr) {
const oss = await this.$uploadOSS.init({
fileData: blobArr,
isLocalFile: false,
onProgress: percent => {},
onSuccess: (res) => {
console.log(res)
}
})
if (oss) {
const data = await oss.start()
if (!data.code) {
this.$message.error(data.message)
}
}
},
以上是對html2canvas好和canvas的常見的屬性的介紹,同時也是開發(fā)圖片編輯器的前提。下面會一步步詳細(xì)介紹下在已有的canvas涂鴉板的基礎(chǔ)上的開發(fā)多張圖片編輯器的思路和過程。
基于canvas的圖片編輯器的怎么做?
1、既然要編輯圖片,自然要提供給我們的圖片的地址,可能是本地相對路徑,也可能是遠(yuǎn)程地址,也可能是一張或者是多張圖片。所以進(jìn)行操作的數(shù)據(jù)結(jié)構(gòu)是數(shù)組,如果只有一張圖那就是長度為1的,元素為圖片地址的數(shù)組,另外需要解決圖片跨域來避免生成的圖片只有涂鴉部分,且背景白色、頭像或者圖片丟失的情況,上面已經(jīng)提供了詳細(xì)的解決方案。
2、圖片編輯器肯定是個通用組件,所以根據(jù)父組件傳進(jìn)來的圖片src的一個長度為length的數(shù)組,來填充一個相同長度的二維數(shù)組,二維數(shù)組中的元素記錄的是每一張圖片被編輯的歷史記錄的集合,也就是每一次操作canvas的時候生成的base64數(shù)據(jù),在開始編輯之前初始化的時候該圖片默認(rèn)填充空數(shù)組[],在這里介紹一個填充數(shù)組很好用的方法fill,值得注意:fill的內(nèi)容如果是引用類型,會導(dǎo)致fill的每一項(xiàng)都是同一個引用,導(dǎo)致在操作其中一個元素時,其余所有的元素都一起改變。
//result:[[],[],...]
this.canvasArrBase = Array(length).fill(0).map(item => {
return []
})
2、點(diǎn)擊不同的圖片,將該圖片的src傳進(jìn)編輯器組件,并將該src作為canvas背景圖,此時視覺上看到了一圖,在dom中其實(shí)是canvas的一張背景圖為src。
3、開始在頁面涂鴉畫布,為了避免無法在surface等觸控屏設(shè)備上使用畫布涂鴉或者圖片編輯器,請使用touch事件,而不是mouse事件。
// 開始繪制
canvas.addEventListener('touchstart', e=> {
...
},false)
// 繪制中
canvas.addEventListener('touchmove', e=>{
...
},false)
// 結(jié)束繪制
canvas.addEventListener('touchend', e=>{
...
},false)
4、在進(jìn)入圖片編輯器之前將點(diǎn)擊的圖片的索引和圖片所在的圖片數(shù)組,一起作為props傳入圖片編輯器,記住當(dāng)前在編輯第幾張圖片currentIndex,才能將涂鴉或者撤銷操作都記錄在這張圖片之上。當(dāng)用戶在畫布上每操作一次,也就是開始touchstart,然后touchmove,最后touchend的時候,將當(dāng)前的canvas畫布的內(nèi)容(所有內(nèi)容,不只是剛剛涂鴉的那一筆。類似于git的每一次提交,即使只是提交一行代碼,git也都會為整個項(xiàng)目生成一份快照,而不只是記錄本次修改的一行代碼,git回滾就是快照的回滾),包括這張背景圖片使用canvas.toDateUrl()生成base64,將其push進(jìn)我們上面介紹的this.canvasArrBase[currenIndex]里,這時currenIndex所在數(shù)組的圖片多了一張歷史記錄,也就是剛剛涂鴉的畫面被我們保存下來了。這也就是canvas圖片編輯器,撤銷的基礎(chǔ),有了它我們才知道撤銷之后展示什么。
//新的土涂鴉操作生成的快照存儲在當(dāng)前索引的數(shù)組內(nèi) this.canvasArrBase[this.currentIdex].push(canvas.toDataUrl())
5、切換圖片也就意味著改變了canvas的背景圖,改變了currentIndex,然后在新的currentIndex上對另外一張圖片進(jìn)行編輯,其實(shí)也是同樣的步驟,無非就是往當(dāng)前編輯的圖片的push畫布剛剛展示的所有內(nèi)容。
6、重點(diǎn)來了,撤銷如何做呢?如何撤銷,如何在畫布上一步步撤銷并且看到上一次,上上次的繪畫記錄呢?
隨著每張圖片的編輯,每張圖片產(chǎn)生一些歷史記錄,我們就是利用這些歷史記錄的來進(jìn)行回滾撤銷操作的。當(dāng)我們在當(dāng)前已經(jīng)編輯過的圖片中點(diǎn)一次撤銷的時候,我們需要將上次canvas中展示的畫布內(nèi)容畫到當(dāng)前的畫布中。在撤銷的時候可以直接將我們存儲的數(shù)據(jù)源直接刪掉,所以只需要利用 canvas.drawImage() 方法,將需要展示的那一條記錄畫到canvas畫布中即可。
drawImage方法有很多參數(shù),用法點(diǎn)擊查看詳情
let newBaseArr = this.canvasArrBase[this.currentIdex]
let { offsetWidth: canvasWidth, offsetHeight: canvasHeight } = canvas
//生成一張新的圖片
let image = new Image()
//圖片的src賦值成當(dāng)前需要展示的base64的值(也就是讓用戶看到撤銷后的畫面)
image.src = this.canvasArrBase[this.currentIdex][newBaseArr.length-1]
//給img的src賦值后,使用onload監(jiān)聽加載圖片情況,加載完成后再繪制,不加onload就極可能繪制失敗。
image.onload = function () {
canvas.drawImage(image, 0, 0, canvasWidth, canvasHeight)
}
7、關(guān)于性能優(yōu)化:由于你根本不知道用戶會拿多么大的圖片來進(jìn)行編輯,所以如果有9張圖片,每張圖片都編輯10次,那么存在內(nèi)存中的base64數(shù)據(jù)量就會非常龐大,在進(jìn)行頁面切換或者圖片涂鴉歷史撤銷的時候就可能造成頁面的卡頓,為了避免這種情況,在切換圖片之前,我只會存儲當(dāng)前圖片最后一張圖片的base64。
8、提交,保存之前判斷this.canvasArrBase中每一項(xiàng)的長度,大于0,則是有編輯過,否則被過濾掉。然后利用前面介紹上傳方法上傳到服務(wù)器,就OK啦!
總結(jié)
在學(xué)習(xí)canvas的道路上它總能給我?guī)硪恍@喜和驚訝,一些我們熟知的工具,例如:echart,高德地圖等無一不是基于canvas做出的。我曾經(jīng)有位同事是一位echart圖表大神,受他的影響,我逐漸對echart的威力有所領(lǐng)略,希望在這樣一個開源社區(qū)里和大家一起學(xué)習(xí),進(jìn)步。
到此這篇關(guān)于canvas如何實(shí)現(xiàn)多張圖片編輯的圖片編輯器的文章就介紹到這了,更多相關(guān)canvas圖片編輯器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章

HTML5實(shí)現(xiàn)的移動端購物車自動結(jié)算功能示例代碼
本文介紹HTML5實(shí)現(xiàn)移動端購物車自動結(jié)算,通過WebStorage、事件監(jiān)聽、DOM操作等技術(shù),確保實(shí)時更新與數(shù)據(jù)同步,優(yōu)化性能及無障礙性,提升用戶體驗(yàn),感興趣的朋友一起看看吧2025-06-18- 在HTML5中,<button>標(biāo)簽用于定義一個可點(diǎn)擊的按鈕,它是創(chuàng)建交互式網(wǎng)頁的重要元素之一,本文將深入解析HTML5中的<button>標(biāo)簽,詳細(xì)介紹其屬性、樣式以及實(shí)際2025-06-18
基于 HTML5 Canvas 實(shí)現(xiàn)圖片旋轉(zhuǎn)與下載功能(完整代碼展示)
本文將深入剖析一段基于 HTML5 Canvas 的代碼,該代碼實(shí)現(xiàn)了圖片的旋轉(zhuǎn)(90 度和 180 度)以及旋轉(zhuǎn)后圖片的下載功能,通過對代碼的解讀,我們可以學(xué)習(xí)到如何利用 Canvas API2025-06-18
HTML5 getUserMedia API網(wǎng)頁錄音實(shí)現(xiàn)指南示例小結(jié)
本教程將指導(dǎo)你如何利用這一API,結(jié)合Web Audio API,實(shí)現(xiàn)網(wǎng)頁錄音功能,從獲取音頻流到處理和保存錄音,整個過程將逐步詳解,此外,還討論了getUserMedia API的使用限制和最2025-06-16- HTML5的搜索框是一個強(qiáng)大的工具,能夠有效提升用戶體驗(yàn),通過結(jié)合自動補(bǔ)全功能和適當(dāng)?shù)臉邮?,可以?chuàng)建出既美觀又實(shí)用的搜索界面,這篇文章給大家介紹HTML5 搜索框Search Box2025-06-13
- Checkbox是HTML5中非常重要的表單元素之一,通過合理使用其屬性和樣式自定義方法,可以為用戶提供豐富多樣的交互體驗(yàn),這篇文章給大家介紹HTML5中Checkbox標(biāo)簽的深入全面解2025-06-13
HTML5無插件拖拽圖片上傳功能實(shí)現(xiàn)過程
本實(shí)例展示了一種基于HTML5技術(shù)的圖片上傳功能,無需外部插件即可通過拖放圖片實(shí)現(xiàn)上傳,涉及到HTML5的拖放API和File API,以及使用CSS來增強(qiáng)用戶界面的交互性和視覺反饋,2025-05-16- 在HTML5和CSS中,定位(positioning)是控制元素在頁面上位置的重要機(jī)制,主要有四種定位方式:靜態(tài)定位(static)、相對定位(relative)、絕對定位(absolute)和固定定位(fixed),2025-05-13
- Microdata作為HTML5新增的一個特性,它允許開發(fā)者在HTML文檔中添加更多的語義信息,以便于搜索引擎和瀏覽器更好地理解頁面內(nèi)容,本文將探討HTML5中Microdata的使用方法以及2025-04-21
在HTML語法中,表格主要通過< table >、< tr >和< td >3個標(biāo)簽構(gòu)成,本文通過實(shí)例代碼講解HTML5表格語法格式,感興趣的朋友一起看看吧2025-04-21




