使用 JavaScript 根據用戶照片和姓名生成海報

前言

最近在爲公司的一個比賽制做專題頁,碰到一個使用參賽者上傳的照片生成專屬海報的需求,實現過程當中用到了一些之前沒用過的 api,也踩了一些坑,因而將其記錄下來。ajax

需求描述

  1. 用戶點擊按鈕進行照片上傳canvas

  2. 照片上傳完成後,將照片進行裁剪,並和海報背景、姓名等組合獲得海報api

  3. 將生成的海報上傳瀏覽器

效果大概以下: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) {
        
      }
    });
  }
});

四、繪製照片

海報中放置照片的區域爲正方形,可是用戶上傳的照片卻不必定,所以須要對照片進行裁剪,裁剪的原則爲取照片中間部分。而後將裁剪參數傳進 canvasdrawImage 方法,進行繪製:

$('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 支持 canvastoBlob 方法,能夠直接使用:

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) {
    
  }
});

至此,整個流程完結。

相關文章
相關標籤/搜索