JavaScript基于用戶照片姓名生成海報(bào)
前言
最近在為公司的一個(gè)比賽制作專題頁(yè),碰到一個(gè)使用參賽者上傳的照片生成專屬海報(bào)的需求,實(shí)現(xiàn)過(guò)程中用到了一些以前沒(méi)用過(guò)的 api,也踩了一些坑,于是將其記錄下來(lái)。
需求描述
- 用戶點(diǎn)擊按鈕進(jìn)行照片上傳
- 照片上傳完成后,將照片進(jìn)行裁剪,并和海報(bào)背景、姓名等組合得到海報(bào)
- 將生成的海報(bào)上傳
效果大概如下:
海報(bào)背景:

成品:

實(shí)現(xiàn)過(guò)程
1、初始化 canvas
canvas#poster-canvas(width='960' height='1280')
function initCanvas() {
canvasCtx = document.getElementById("poster-canvas").getContext('2d');
}
2、繪制海報(bào)背景
海報(bào)背景為預(yù)先提供的一張照片,將其設(shè)置到一個(gè)隱藏的 img 標(biāo)簽里面,并且預(yù)留一個(gè) canvas 元素用于繪制海報(bào):
img.poster-background(src='/assets/xxx/poster-background.jpeg')
頁(yè)面加載完成后,將海報(bào)背景繪制到 canvas 內(nèi):
$('img.poster-background').on('load', function () {
var backgroundImg = $('img.poster-background')[0];
canvasCtx.drawImage(backgroundImg, 0, 0, 960, 1280);
renderName();
});
海報(bào)背景繪制完成之后,需要將用戶姓名繪制到特定位置。由于用戶姓名長(zhǎng)度不一,因此需要進(jìn)行計(jì)算確定字體大?。?/p>
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);
}
3、上傳照片
使用 file 類型的 input 元素,因?yàn)轫?yè)面上表現(xiàn)為點(diǎn)擊按鈕,因此使用經(jīng)典的將 input 元素透明化并覆蓋按鈕的方法:
a.upload-btn
input#photo(type='file' name='photo' accept='image/jpeg, image/png')
| 上傳自己的照片生成專屬海報(bào)
.upload-btn input {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 68px;
cursor: pointer;
}
然后監(jiān)聽(tīng) input 元素的 change 事件,然后使用 FormData API 構(gòu)造表單數(shù)據(jù),使用 ajax 進(jìn)行異步上傳,照片上傳完成之后。得到一個(gè)地址,將這個(gè)地址設(shè)置到頁(yè)面上預(yù)留的一個(gè) img 標(biāo)簽里面:
$('#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('請(qǐng)上傳 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) {
}
});
}
});
4、繪制照片
海報(bào)中放置照片的區(qū)域?yàn)檎叫?,但是用戶上傳的照片卻不一定,因此需要對(duì)照片進(jìn)行裁剪,裁剪的原則為取照片中間部分。然后將裁剪參數(shù)傳進(jìn) canvas 的 drawImage 方法,進(jìn)行繪制:
$('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();
});
前面繪制海報(bào)背景和這里繪制照片,調(diào)用的是同一個(gè)方法,只不過(guò)后者多傳進(jìn)去了裁剪參數(shù)。但是需要注意的是,裁剪參數(shù)是在繪制位置之前傳進(jìn)去的,而不是簡(jiǎn)單的補(bǔ)在后面:
canvasCtx.drawImage(backgroundImg, 0, 0, 960, 1280);
canvasCtx.drawImage(avatarImg, cutStartX, cutStartY, newWidth, newWidth, 0, 0, 960, 960);
5、上傳海報(bào)
依然使用 FormData API,因此需要先用 canvas 構(gòu)造一個(gè) Blob 對(duì)象。新版本的 Chrome 和 Firefox 支持 canvas 的 toBlob 方法,可以直接使用:
document.getElementById("poster-canvas").toBlob(function (blob) {});
其它瀏覽器里,可以先用 toDataURL方法得到 base64 格式的圖片數(shù)據(jù),再轉(zhuǎn)為 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});
}
然后進(jìn)行上傳,步驟和前面上傳照片一致:
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) {
}
});
至此,整個(gè)流程完結(jié)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
在web中js實(shí)現(xiàn)類似excel的表格控件
這篇文章主要介紹了如何在web中實(shí)現(xiàn)類似excel的表格控件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
關(guān)于Javascript模塊化和命名空間管理的問(wèn)題說(shuō)明
最近閑下來(lái)的時(shí)候,稍微想了想這個(gè)問(wèn)題。關(guān)于Javascript模塊化和命名空間管理2010-12-12
javascript數(shù)組元素刪除方法delete和splice解析
這篇文章主要介紹了javascaipt數(shù)組元素刪除方法delete和splice解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
詳解javascript中對(duì)數(shù)據(jù)格式化的思考
本篇文章主要介紹了詳解javascript中對(duì)數(shù)據(jù)文本格式化的思考 ,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01
JS獲取下拉列表所選中的TEXT和Value的實(shí)現(xiàn)代碼
本篇文章主要是對(duì)JS獲取下拉列表所選中的TEXT和Value的實(shí)現(xiàn)代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01

