DEV Community 👩‍💻👨‍💻

Cover image for 做一個簡單的網頁截圖功能
Let's Write
Let's Write

Posted on • Originally published at letswrite.tw

做一個簡單的網頁截圖功能

本篇要解決的問題

幾年前公司有一個案子,是要讓使用者可以做一個簡單的卡片,然後把這卡片存成圖檔分享給朋友,那時就有寫過一次把指定的 <div> 存成圖檔的 JS,結果,案子太久遠了,竟然沒把當時寫存圖檔的 function 給存下來 XD,直到前陣子前同事在問才想起這件事。

把網頁的區塊存成圖檔,總覺得以後有機會再用到,就先整理這篇筆記文,以後真的要用的時候就可以直接 複製貼上 複習一下怎麼寫。

要把頁面存成圖檔有二個步驟,各自用了一個套件。

  1. 把指定的 HTML 區塊轉成 Canvas:html2canvas
  2. 把 Canvas 轉存成圖檔:Canvas2image

本篇有做一個 Demo 出來,在繼續往下閱讀前大家可以先玩玩看:

https://letswritetw.github.io/letswrite-web-to-image/


將指定區塊轉成 Canvas

html2canvas 這個套件很方便,基本上官方文件上就明明白白的寫了使用方法:

const el = document.querySelector("#something");
html2canvas(el, [option])
  .then(canvas => {
    document.body.appendChild(canvas)
  });
Enter fullscreen mode Exit fullscreen mode

option 是選填,可寫可不寫,項目請參考官方文件的 Configuration,如果去爬一些教學文,會看見最常寫到的就是 useCORS: true

useCORS: true 是在我們要抓圖的 div 裡如果存在其它網域的圖片,就必須告訴 html2canvas 說我們有跨網域的圖片。

啊不過,經 August 人體實驗證明,當有圖片是跨網域的來源,很常會抓圖失敗,失敗機率是 3 張失敗 1 ~ 2 張,如果真要用這套把 div 轉成 Canvas,div 裡的圖要用同網域的會比較保險。

在本篇的範例裡,除了 useCORS,也有用到 backgroundColor,直接把空白的地方填入網頁裡的背景色,看上去才會無違和。

如果要好玩一點,也可以背後再接一個 Color Thief 抓出圖檔的主要色系當背景色,就不用寫死背景色的值。


把 Canvas 轉存成圖檔

Canvas2image 這套很妙,一開始 August 在寫 Demo 時,JS 是引用 CDN 上的絕對路徑,然後一直無法自訂圖檔的檔名,可是看官方的原始碼裡又確實有參數是讓人放檔名的。

如此如此,這般這般,就決定直接從官方 GitHub 上下載 JS 檔來用,然後果不其然就直接成功設定圖檔檔名勒。

只是官方給的 JS 要用 import 才能使用,如果對 import 還不熟的朋友,可以考慮下載 August 前陣子寫的 開發初始檔,就能直接編譯 ES6 的 JS 檔。

Canvas2image 的使用方式很簡單:

Canvas2Image.saveAsImage(canvasObj, width, height, type, filename)
Canvas2Image.saveAsPNG(canvasObj, width, height, filename)
Canvas2Image.saveAsJPEG(canvasObj, width, height, filename)
Canvas2Image.saveAsGIF(canvasObj, width, height, filename)
Canvas2Image.saveAsBMP(canvasObj, width, height, filename)
Enter fullscreen mode Exit fullscreen mode

官方的說明文件沒有寫上 filename 這個參數,應該是漏了寫,因為 August 去看了 JS 原始碼是有這個參數的。

canvasObj 就直接寫我們從 html2canvas 取得的 canvas 就行。

widthheight 這二個的值 canvas 本身就會有,如果沒有其它需求可以直接寫 canvas.widthcanvas.height

第一個的 saveAsImage 需要多帶 type 參數,寫上要什麼檔案類型就行,比方 pngjpeg。不過建議就直接用後面四個 function 吧,不讓使用者選直接存成我們希望使用者擁有的類型省事多了。

總合上面二個步驟,程式碼簡單整理如下。

// 將指定區塊轉成 Canvas
function htmlToCanvas(id) {
  const el = document.getElementById(id);
  html2canvas(el).then(canvas => {
    canvasToImage(canvas);
  });
}

// 把 Canvas 轉存成圖檔
function canvasToImage(canvas) {
  const filename = 'xxxxx';
  Canvas2Image.saveAsPNG(
    canvas,
    canvas.width, canvas.height,
    filename
  );
}
Enter fullscreen mode Exit fullscreen mode

截多個 div 存成圖

假設今天我們要截的圖是頁面上幾個 div 併在一起的,方法很簡單,在本篇的 Demo 裡就有寫出來,那個「截圖 Section 1 + 2」的按鈕就是在示範這件事。

Demo 中有二個 sectoin,分別是 <section id="section1"><section id="section2">

我們的目標是一個按鈕,存出來的圖片包含這二個 section。

首先,建立第三個 section,隨意取叫 <section id="section3">,然後用 JS 把 1、2 二個 section 的 HTML 塞進去。

塞進 3 後,就可以用 html2canvas 來指定 3 轉成 Canvas。

這樣會遇到一個小狀況,就是 html2canvas 要的是真的要存在頁面上的區塊才行,而當我們把 1、2 的內容都塞到 3 後,3 就會有內容,就會被使用者看到。

把 3 寫上 display: none 是不行的,因為在網頁上就不會有寬高,會被認定是圖片不存在,就無法透過 html2canvas 轉成 Canvas。

山不轉路轉,藏起來不行,那我們就把它移到畫面之外,讓使用者在螢幕上看不到就行。

August 在 Demo 裡的作法如下:

html, body {
  width: 100;
  overflow-x: hidden;
}
#section3 {
  position: absolute;
  left: -100%;
}
Enter fullscreen mode Exit fullscreen mode

這樣,就可以順利截圖,而使用者是看不到那個被我們偷偷塞了內容的 section3 的。


範例及原始碼

原始碼存在 GitHub 上,也用了 GitHub Pages 生成 Demo 頁。

要取用前麻煩分享本篇,或是 GitHub 上點個星星,你的小小動作對本站都是大大的鼓勵。

原始碼:https://github.com/letswritetw/letswrite-web-to-image

Demo:https://letswritetw.github.io/letswrite-web-to-image/

Top comments (1)

What image format should you use in your next project? 🤔