用 Vue.js 製作圖片版 EDM 生成器

用 Vue.js 製作圖片版 EDM 生成器
用 Vue.js 製作圖片版 EDM 生成器

2020.08.31更新:原始碼部份新增二欄的功能。在最後一段原始碼下載中可以下載。

本篇要解決的問題

之前常遇到臨時要出一份EDM,雖然就是個簡單的步驟,不花什麼時間,但就是會打斷其他正在寫的案子,或是要下班前才接到電話阻止人下班,為了解決這種零碎的問題,這幾天開發了一個EDM生成器。

開發的過程中,只能說踩坑又踩坑,這邊是紀錄踩了坑後,最後解決的辦法,重點整理如下:

  • 預覽區的 HTML、實際產出的 HTML 要分開,不然 Vue.js 的 render 會刪掉註解,意思就是寫給 outlook 用的 hack 會全部被刪掉。
  • 因為寬度是動態改變,所以 outlook hack 的部份要改用替代的。
  • 下載成 .html 時,<!DOCTYPE html><html> 這個會被刪掉,要補上。
  • css 的部份,選擇器第一層要是編輯器的 id,以免 css 影響到 edm。
  • 包成 zip 用套件:JSZip、FileSaver.js。
  • 為了能包進 zip 裡,抓圖檔的部份要用 FileReader,才能抓到 base64。
  • 用 JSZip 抓圖檔時,FileReader 抓出來的開頭要刪掉,才能設成 base64: true

如果需要文字版的,也有用 Netlyfy CMS 製作一個版本,請看:

如何用 Netlify CMS 製作電子報生成器


預覽、產出的HTML,要分開

寫的 HTML 架構如下:

<div id="app">
  <div class="row">

    <!-- 編輯區 -->
    <div id="lego-editor"></div>

    <!-- 預覽區 -->
    <div id="preview"></div>

  </div>
</div>

<!-- 實際產出 -->
<main id="output"></main>

為了不被 Vue.js 刪除掉 hack,產出區不能包在 #app 裡,而且為了讓使用者看不到產出區,要加上 display: none


用 replace 替代 hack,補上 width、html

下一個是要改變 hack 裡的 width,因為 width 的值是抓 v-model 的值,因此產出成 HTML 時要另外再把 hack 補進去,這邊用的是 replace。

在產出成 HTML 時,因為抓的是某個 ID 裡的 HTML,所以頁面開頭的宣告跟 html tag 都會被刪掉,在下載前也要補上,這邊一樣用 replace 補。

replace 的部份最後寫的如下:

document.getElementById('output')
  .outerHTML.replace('<main id="output">', '<!DOCTYPE html><html>')
  .replace('</main>', '</html>')
  .replace(/<div class="js-width-start"><\/div>/g, '<!--[if (gte mso 9)|(IE)]><table width="' + this.maxWidth + '" data-width cellpadding="0" cellspacing="0" border="0" align="center"><tr><td><![endif]-->');

css 選擇器,編輯、預覽的 id 要是第一層

因為 outlook 不支援 <style> 的 tag,因此所有樣式都是寫在行內。

為了不讓其它的 css 影響了 EDM 的樣式,css 選擇器上的第一層要是編輯區、預覽區的 id 或 class。像這樣:

#lego-editor
  &, & *
    box-sizing: border-box
  position: fixed
  h1, h2
    font-weight: bold
    color: #000

#preview
  padding-top: 15px
  margin-left: auto

檔案包成 zip 的套件:JSZip、FileSaver.js

原本還在煩惱前端要怎麼把 HTML 跟一堆圖檔打包成 zip 讓使用者下載的,google 一下後竟然就發現有神人開發出了套件。

打包 zip – JSZip:https://stuk.github.io/jszip/

下載 zip – FileSaver:https://github.com/eligrey/FileSaver.js/

JSZip 整體來說使用蠻簡單的,像這樣:

const zip = new JSZip();

// 抓HTML
zip.file(檔名, 檔案內容);

// 抓圖檔
zip.file(圖檔檔名, 圖檔的base64, { base64: true });

// 自動下載
zip.generateAsync({type:"blob"}).then(content => {
  saveAs(content, 'zip的檔名.zip');
});

抓圖檔要用 FileReader

原本抓圖檔用的是 URL.createObjectURL,因為出來的網址簡單。

但,一直找不到把 URL.createObjectURL 轉成 base64 的方法,造成 JSZip 要存圖檔時一直存不到,只好換成 FileReader。

客製 input file 的部份,可以參考這篇:File API 客製上傳檔案按鈕(input file)

這邊寫用 FileReader 的部份。

// 以下為 Vue.js 的 methods
previewImg(event) {
  var file = event.target.files[0];
  var reader = new FileReader();
  reader.addEventListener('load', () => {
    console.log(reader.result);
  }, false);
  if(file) reader.readAsDataURL(file);
}

JSZip 抓圖檔,要刪掉 base64 的開頭

當我們用 FileReader 抓出圖片的 base64 後,會看到以下:

data:image/png;base64,xxxxxxxxxxxxxxx………

當 JSZip 在抓圖檔時,如果原封不動的給,就會看見報錯,報說格式不對。

這個問題很簡單,就把開頭的「data:image/png;base64,」給去掉就行。

這邊直接用 split,取第一個半形逗號來分,如下:

zip.file(t.name, t.img.split(',')[1], { base64: true })

demo 及原始檔下載

原始檔整理在 Github 上,也用了 Github Pages 生成 demo 頁面。

2020.08.31更新:原始碼新增了可以編輯二欄的功能,因此程式碼部份分成只有一欄的,跟今日新增可以有二欄的。

demo:https://letswritetw.github.io/letswrite-image-edm-build/

原始檔(一欄):https://github.com/letswritetw/letswrite-image-edm-build/releases/tag/v1.0.0

原始檔(二欄):https://github.com/letswritetw/letswrite-image-edm-build

Summary
用Vue.js製作圖片版EDM生成器
Article Name
用Vue.js製作圖片版EDM生成器
Description
本篇大綱:本篇要解決的問題。預覽、產出的HTML,要分開。用 replace 替代 hack,補上 width、html。檔案包成 zip 的套件:JSZip、FileSaver.js。抓圖檔要用 FileReader。JSZip 抓圖檔,要刪掉 base64 的開頭。demo 及原始檔下載。
August
Let's Write
Let's Write
https://letswritetw.github.io/letswritetw/dist/img/logo_512.png
訂閱
通知
guest

0 Comments
最舊
最新
Inline Feedbacks
看所有留言