最近在群里面看到一個很有意思的提問:
用戶希望上傳一張海報與另外一個小圖片,最后可以下載下來,圖片,小圖片位于大圖片的右下角
這種功能其實(shí)還挺常見的,比如生成一個帶二維碼的海報之類的,就抽時間做了一下
首先分析一下思路
- 要做圖片合成,那么必定會使用到canvas
- 怎么進(jìn)行合成?這個就很簡單了?將canvas設(shè)置成大圖的寬高,繪制大圖,再在對應(yīng)位置繪制小圖,那么就得到我們所需要的圖案
- 最后需要做的是如何下載,可以使用a標(biāo)簽來完成
具體代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/rxjs/6.5.2/rxjs.umd.js"></script>
</head>
<body>
<canvas id="combination"></canvas>
<a download id="download">下載</a>
<script>
const src0 = 'http://img5.imgtn.bdimg.com/it/u=3108685336,1951350863&fm=26&gp=0.jpg'
const src1 = 'http://img5.imgtn.bdimg.com/it/u=2221756694,2517784243&fm=26&gp=0.jpg'
const img0 = new Image()
const img1 = new Image()
img0.src = src0
img1.src = src1
// 當(dāng)兩張圖都加載好了的時候進(jìn)行canvas設(shè)置
const img0Load$ = rxjs.fromEvent(img0, 'load')
const img1Load$ = rxjs.fromEvent(img1, 'load')
rxjs.zip(img0Load$, img1Load$).subscribe(([e0, e1]) => {
console.log(e0);
const img0 = e0.path[0]
const img1 = e1.path[0]
const imgSrc0 = img0.currentSrc
const imgSrc1 = img1.currentSrc
const img0Width = img0.naturalWidth
const img1Width = img1.naturalWidth
const img0Height = img0.naturalHeight
const img1Height = img1.naturalHeight
const minWidth = Math.min(img0Width, img1Width)
const maxWidth = Math.max(img0Width, img1Width)
const minHeight = Math.min(img0Height, img1Height)
const maxHeight = Math.max(img0Height, img1Height)
const canvas = document.getElementById('combination')
canvas.width = maxWidth
canvas.height = maxHeight
const ctx = canvas.getContext('2d')
let background = new Image()
let minImage = new Image()
// 如果不設(shè)置會導(dǎo)致報錯
// Tainted canvases may not be exported
// https://stackoverflow.com/questions/22710627/tainted-canvases-may-not-be-exported
background.setAttribute('crossOrigin', 'anonymous')
minImage.setAttribute('crossOrigin', 'anonymous')
if (maxHeight === img0Height) {
background.src = imgSrc0
minImage.src = imgSrc1
} else {
background.src = imgSrc1
minImage.src = imgSrc0
}
rxjs.zip(rxjs.fromEvent(background, 'load'), rxjs.fromEvent(minImage, 'load')).subscribe(() => {
// 繪制背景
ctx.drawImage(background, 0, 0, maxWidth, maxHeight)
// 繪制小圖
ctx.drawImage(minImage, maxWidth - minWidth, maxHeight - minHeight, minWidth, minHeight);
const downloadImageSrc = canvas.toDataURL("image/png")
document.getElementById('download').href = downloadImageSrc
})
})
</script>
</body>
</html>
下面對代碼進(jìn)行解析
- 請無視rxjs的代碼,只要知道他們的作用是:當(dāng)兩張圖片都加載完成的時候,執(zhí)行里面的內(nèi)容,類似Promise.all
- 使用drawImage來繪制圖片,注意第一個參數(shù)不是src而是一個image對象
- 注意minImage.setAttribute('crossOrigin', 'anonymous') 這句代碼,如果沒有這段代碼會導(dǎo)致跨域加載圖片的問題出現(xiàn)導(dǎo)致 canvas.toDataURL("image/png") 無法執(zhí)行
可見還是相對比較簡單的,在實(shí)際開發(fā)中并不需要去識別那個是大圖那個是小圖,一般在上傳的時候就決定了,而且一般圖片大小都是固定的,所以并不需要使用這么麻煩的rxjs代碼
項目地址