最近在爲公司的一個比賽制做專題頁,碰到一個使用參賽者上傳的照片生成專屬海報的需求,實現過程當中用到了一些之前沒用過的 api,也踩了一些坑,因而將其記錄下來。ajax
用戶點擊按鈕進行照片上傳canvas
照片上傳完成後,將照片進行裁剪,並和海報背景、姓名等組合獲得海報api
將生成的海報上傳瀏覽器
效果大概以下:app
海報背景:異步
成品:post
canvas
canvas#poster-canvas(width='960' height='1280')
function initCanvas() { canvasCtx = document.getElementById("poster-canvas").getContext('2d'); }
海報背景爲預先提供的一張照片,將其設置到一個隱藏的 img
標籤裏面,而且預留一個 canvas
元素用於繪製海報:字體
img.poster-background(src='/assets/xxx/poster-background.jpeg')
頁面加載完成後,將海報背景繪製到 canvas
內:url
$('img.poster-background').on('load', function () { var backgroundImg = $('img.poster-background')[0]; canvasCtx.drawImage(backgroundImg, 0, 0, 960, 1280); renderName(); });
海報背景繪製完成以後,須要將用戶姓名繪製到特定位置。因爲用戶姓名長度不一,所以須要進行計算肯定字體大小:spa
function renderName() { var name = $('input[name="chName"]').val(); var fontSize; if (name.length < 3) { fontSize = 100; } else { fontSize = parseInt(320 / name.length); } canvasCtx.font = "bold " + fontSize + "px Courier New"; canvasCtx.fillStyle = "#de071b"; canvasCtx.fillText(name, 20, 1066); }
使用 file
類型的 input
元素,由於頁面上表現爲點擊按鈕,所以使用經典的將 input
元素透明化並覆蓋按鈕的方法:
a.upload-btn input#photo(type='file' name='photo' accept='image/jpeg, image/png') | 上傳本身的照片生成專屬海報
.upload-btn input { position: absolute; left: 0; top: 0; opacity: 0; width: 100%; height: 68px; cursor: pointer; }
而後監聽 input
元素的 change
事件,而後使用 FormData API
構造表單數據,使用 ajax
進行異步上傳,照片上傳完成以後。獲得一個地址,將這個地址設置到頁面上預留的一個 img
標籤裏面:
$('#photo').on('change', function (e) { var file = e.target.files[0]; var type = file.type; if (type !== 'image/jpeg' && type !== 'image/png') { window.toastr.error('請上傳 jpg 或 png 格式的圖片'); } else { var formData = new FormData(); formData.append('avatar', file); $.ajax({ type: 'POST', url: '/upload_url', data: formData, contentType: false, processData: false, success: function(result) { var avatarUrl = result.data.url; $('img.avatar').attr('src', avatarUrl); }, error: function(err) { } }); } });
海報中放置照片的區域爲正方形,可是用戶上傳的照片卻不必定,所以須要對照片進行裁剪,裁剪的原則爲取照片中間部分。而後將裁剪參數傳進 canvas
的 drawImage
方法,進行繪製:
$('img.avatar').on('load', function () { var avatarImg = $('img.avatar')[0]; var originWidth = avatarImg.width; var originHeight = avatarImg.height; var newWidth, cutStartX, cutStartY; if (originWidth < originHeight) { newWidth = originWidth; cutStartX = 0; cutStartY = (originHeight - originWidth) / 2; } else if (originWidth > originHeight) { newWidth = originHeight; cutStartX = (originWidth - originHeight) / 2; cutStartY = 0; } else { newWidth = originWidth; cutStartX = 0; cutStartY = 0; } canvasCtx.drawImage(avatarImg, cutStartX, cutStartY, newWidth, newWidth, 0, 0, 960, 960); uploadPoster(); });
前面繪製海報背景和這裏繪製照片,調用的是同一個方法,只不事後者多傳進去了裁剪參數。可是須要注意的是,裁剪參數是在繪製位置以前傳進去的,而不是簡單的補在後面:
canvasCtx.drawImage(backgroundImg, 0, 0, 960, 1280); canvasCtx.drawImage(avatarImg, cutStartX, cutStartY, newWidth, newWidth, 0, 0, 960, 960);
依然使用 FormData API
,所以須要先用 canvas
構造一個 Blob
對象。新版本的 Chrome 和 Firefox 支持 canvas
的 toBlob
方法,能夠直接使用:
document.getElementById("poster-canvas").toBlob(function (blob) {});
其它瀏覽器裏,能夠先用 toDataURL
方法獲得 base64
格式的圖片數據,再轉爲 Blob
:
var blob = dataURLtoBlob(document.getElementById("poster-canvas").toDataURL()); function dataURLtoBlob(dataurl) { if (dataurl.indexOf('base64') < 0) { dataurl = 'data:image/jpeg;base64,' + dataurl; } var arr = dataurl.split(','); var mime = arr[0].match(/:(.*?);/)[1]; var bstr = atob(arr[1]); var n = bstr.length; var u8arr = new Uint8Array(n); while (n --) { u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], {type: mime}); }
而後進行上傳,步驟和前面上傳照片一致:
var formData = new FormData(); formData.append('poster', blob); $.ajax({ type: 'POST', url: '/upload_poster_url', data: formdata, contentType: false, processData: false, success: function(result) { }, error: function(err) { } });
至此,整個流程完結。