純前端 Canvas 實現 HTML 轉圖片,自動生成微信閱讀卡片

brickyang發表於2018-08-27

最近公司微信公眾號想使用 Apple 式的圓角陰影卡片做文章推薦。這種效果用 Adobe XD 可以輕鬆做出來,但是沒法要求所有編輯都去學習新軟體,所以就打算用前端實現一個小工具。效果如下:

更新: 已增加 Electron,可打包成 dmg 或 exe 檔案執行。詳見 GitHub

screenshot

功能很簡單,選擇一張圖片,輸入標題文字,下載即可得到一張 PNG 格式透明背景的圓角陰影卡片圖。核心的步驟有這幾個:

  • 剪裁圖片
  • HTML 轉圖片
  • 生成圓角陰影樣式

其中關鍵一步 HTML 轉圖片,利用了著名的 html2canvas 庫,其他幾步都可以用 Canvas 輕鬆完成。

剪裁圖片

為了簡化功能,對選取的圖片統一按 16:9 居中剪裁。

獲取圖片檔案

input 獲得 File 物件後,把它轉成 Canvas 物件。

function listenFileInput() {
  const fileInput = document.querySelector(`#${INPUT_FILE_ID}`);
  fileInput.addEventListener('change', ev => {
    const file = ev.target.files[0];
    const image = new Image();
      
    image.onload = loadImage;  // 非同步過程
    
    image.src = window.URL.createObjectURL(file);
  });
}

function loadImage() {
  const src = cropImage(this);
  document.querySelector(`#${CARD_IMAGE_ID}`).src = src;
}
複製程式碼

剪裁

利用 .drawImage() 將原圖以剪裁模式「繪製」到新的 canvas 上,最後返回 base64 的圖片地址,可直接用於 <img> 標籤的 src 屬性(見上一段程式碼末尾)。

function cropImage(image) {
  const width = image.width;
  const height = Math.round(width * TARGET_RATIO);
  const cropX = 0;
  const cropY = Math.round((image.height - height) / 2);

  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;

  const ctx = canvas.getContext('2d');
  ctx.drawImage(image, cropX, cropY, width, height, 0, 0, width, height);

  return canvas.toDataURL();
}
複製程式碼

HTML to Canvas

直接利用 html2canvas 庫。這個庫目前在 GitHub 上有 13K+ star,最新的版本是 1.0.0-alpha.12,使用非常方便。

async function generateScreenshot() {
  const htmlDom = document.querySelector(`#${HTML_ID}`);
  const scale = getScale(); // 因為手機和 PC 的畫素比不同,HTML 轉圖片時要進行放大,否則在手機上圖片會比較模糊,通常 3 倍左右即可

  // HTML 轉 Canvas
  const origCanvas = await html2canvas(htmlDom, { scale });
  // 生成圓角圖片
  const roundCanvas = drawRound(origCanvas, scale);
  // 生成陰影效果
  return drawShadow(roundCanvas);
}
複製程式碼

圓角陰影

生成圓角矩形圖片需要用到 Canvas 的 .clip() 方法,其作用是在 canvas 上只顯示 clipping 區域內的內容。思路是先在 canvas 上畫出一個圓角矩形,然後將上一步生成的圖片「貼」進去。

由於經過 clip 的 canvas 只顯示 clipping 區域內的內容,所以不能在這個 canvas 上直接給圖片增加陰影,而是要將該 canvas 繪製到一個更大的 canvas 上,然後給這個圓角矩形的 canvas 增加陰影。

圓角矩形

function drawRound(origCanvas, scale) {
  const roundCanvas = document.createElement('canvas');
  roundCanvas.width = DOM_WIDTH * scale;
  roundCanvas.height = DOM_HEIGHT * scale;

  const roundCtx = roundCanvas.getContext('2d');
  const roundRadius = RADIUS * scale;
  
  // 在 canvas 上畫出圓角矩形
  const x1 = roundRadius;
  const y1 = 0;
  const x2 = x1 + roundCanvas.width - 2 * roundRadius;
  const y2 = y1;
  const x3 = x2 + roundRadius;
  const y3 = roundRadius;
  const x4 = x3;
  const y4 = y3 + roundCanvas.height - 2 * roundRadius;
  const x5 = x2;
  const y5 = y4 + roundRadius;
  const x6 = x1;
  const y6 = y5;
  const x7 = x6 - roundRadius;
  const y7 = y4;
  const x8 = x7;
  const y8 = y3;
  roundCtx.beginPath();
  roundCtx.moveTo(x1, y1);
  roundCtx.lineTo(x2, y2);
  roundCtx.quadraticCurveTo(x3, y2, x3, y3);
  roundCtx.lineTo(x4, y4);
  roundCtx.quadraticCurveTo(x4, y5, x5, y5);
  roundCtx.lineTo(x6, y6);
  roundCtx.quadraticCurveTo(x7, y6, x7, y7);
  roundCtx.lineTo(x8, y8);
  roundCtx.quadraticCurveTo(x8, y1, x1, y1);
    
  // 將圖片「貼」進 clipping 區域,得到一個圓角矩形的圖片
  roundCtx.clip();
  roundCtx.drawImage(origCanvas, 0, 0);

  return roundCanvas;
}
複製程式碼

陰影

根據陰影尺寸決定背景 canvas 的尺寸。最終下載的圖片的尺寸,就是背景 canvas 的尺寸。設定太小,陰影會顯示不完整;設定太大,則邊緣留白空間太大,浪費空間且影響使用。

function drawShadow(origCanvas) {
  const bgdCanvas = document.createElement('canvas');
  bgdCanvas.width = origCanvas.width + MARGIN_WIDTH;
  bgdCanvas.height = origCanvas.height + MARGIN_HEIGHT;
  const ctx = bgdCanvas.getContext('2d');

  ctx.shadowOffsetX = SHADOW_X;
  ctx.shadowOffsetY = SHADOW_Y;
  ctx.shadowBlur = SHADOW_BLUR;
  ctx.shadowColor = SHADOW_COLOR;
  ctx.drawImage(origCanvas, MARGIN_WIDTH / 2, 0);

  return bgdCanvas;
}
複製程式碼

下載

將做好的帶有陰影的 canvas,通過 .toDataURL() 得到 base64 地址,設為 <a href="" download />href 屬性,即可下載使用。

小工具的完整程式碼位於 GitHub:reading-card-generator

相關文章