最近接到一個需求,用文字和圖片合成一個海報,用於活動結尾頁在微信長按分享,接到需求的第一時間,我就想到用 canvas 來畫,可是看到 canvas 繁瑣的繪製過程,不禁得感到頭大,後幾經搜索,果真發現已經有人造好了輪子。此篇文章主要記錄下實現過程,以及遇到的問題。
QRCode
這個依賴主要是用於移動端將 url 生成二維碼,注意名字叫 qrcodejs2 別安裝錯css
npm install qrcodejs2 --save import QRCode from "qrcodejs2"
html2canvas
html
這個依賴主要是將當前 HTML 結構以及 css 樣式轉換爲 canvas。比本身用 api 去畫方便多了 git
Canvas2Image
Github github
這個依賴主要是將 canvas 轉換爲圖片,實際上,Canvas2Image.js 也是基於 canvas.toDataURL 的封裝,相比原生的 API 對於轉爲圖片的功能上考慮更爲具體(未壓縮的包大小爲 7.4KB ),適合項目使用。web
將全部的海報結構都寫在一個父級結構中,而後調用 html2canvas 轉換爲圖片,建立 image,經過 css 層級和定位,將 image 置爲最頂層,來實現長按分享npm
html 部分canvas
<!--html 結構,具體 css 不寫了--> <!-- 海報 html 元素 --> <div id="posterHtml" :style="{backgroundImage: 'url('+posterHtmlBg+')'}" > <div class="posterHtml"> <div class="posterklass">我是{{name}},邀請您:</div> <!-- 二維碼 --> <div id="qrcodeImg" :if="postcode"></div> </div> </div> <!--image 即將要插入的位置,這樣的結構處理,配合方法裏面的 css 會自動置爲最頂層--> <div id="myCanvas"></div> <!--提示用戶的文案,其實也能夠寫在海報 html 結構中,經過 html2canvas 的 ignore 來忽略生成--> <span class="tip">長按保存該海報,邀請好友來測!</span>
js 部分segmentfault
//生成二維碼 createQrcode(text) { const qrcodeImgEl = document.getElementById("qrcodeImg"); qrcodeImgEl.innerHTML = ""; let width = document.documentElement.clientWidth; //寬度本身定義 我取的設備的寬度 *0.32 width = width * 0.32; let qrcode = new QRCode(qrcodeImgEl, { width: width, height: width, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); //text是 要生成的 url qrcode.makeCode(text); //生成二維碼以後調用生成海報 this.createPoster(); } //基於 html2canvas.js 可將一個元素渲染爲 canvas,只須要簡單的調用 html2canvas(element[, options]) 便可,會返回一個包含有 <canvas> 元素的promise: //生成海報 createPoster() { const vm = this; const domObj = document.getElementById("posterHtml"); var width = document.documentElement.clientWidth; var height = document.documentElement.clientHeight; var canvas = document.createElement("canvas"); //各項參數的意義可參考 html2canvas 文檔 var opts = { canvas: canvas, logging: true, width: width, height: height, useCORS: true, allowTaint: false, logging: false, letterRendering: true, }; html2canvas(domObj, opts).then(function(canvas) { var context = canvas.getContext("2d"); var img = Canvas2Image.convertToImage( canvas, canvas.width, canvas.height ); img.style.width = canvas.width; img.style.height = canvas.height; img.style.position = "absolute"; img.style.top = "0px"; document.getElementById("myCanvas").appendChild(img); //插入圖片後將 html 結構隱藏,避免微信圖片上拉 顯示圖片下 html 結構 vm.$nextTick(() => { let e = document.querySelector("#posterHtml"); e.style.display = "none"; }) } //數據示例,隨便寫了 意思下 data() { return { name:'', //海報文案 posterHtmlBg: require('../../assets/images/poster/invite_poster_bg.jpg'), // 背景圖 } },
按理說,這時候的需求應該實現了,可是運行事後,會發現轉換成的圖片,清晰度感人。確定不符合需求,因此我搜索了一番,發現如下解決方案。 api
第一,背景圖不能經過 background 來引用,要單獨寫個 image 結構promise
<div class="main"> <!-- 海報 html 元素 --> <div id="posterHtml" > <div class="posterHtml"> <img :src="posterHtmlBg" style="width:100%;height:100%" > <div class="posterklass">我是{{name}},邀請您:</div> <!-- 二維碼 --> <div id="qrcodeImg" :if="postcode"></div> </div> </div> <div id="myCanvas"></div> <span class="tip">長按保存該海報,邀請好友來測!</span> </div>
canvas 尺寸也會影響到轉換質量 上面的代碼雖然設置了尺寸,但遠遠不夠,由於瀏覽器繪製 Canvas 渲染到屏幕中分兩個過程:
繪製過程:webkitBackingStorePixelRatio
webkitBackingStorePixelRatio 表示瀏覽器在繪製 Canvas 到緩存區時的繪製比例,若圖片寬高爲 200px,webkitBackingStorePixelRatio 爲 2,那麼 Canvas 繪製這個圖片到緩存區時,寬高就變成 400px渲染過程:devicePixelRatio
Canvas 顯示到屏幕中還須要渲染過程,渲染過程會根據 devicePixelRatio 參數將緩存區中的 Canvas 進行縮放渲染到屏幕中
Canvas 繪製會模糊的緣由就能夠推測:
一、devicePixelRatio = device pixel / CSS pixel
若是 devicePixelRatio = 2 那麼對於 200px 200px 的圖片要繪製到屏幕中,那麼對應的屏幕像素(物理像素) 就是 400px 400px二、在大部分高清屏中,webkitBackingStorePixelRatio = 1 devicePixelRatio = 2
將100px * 100px 的圖片繪製到屏幕中會經歷如下處理:webkitBackingStorePixelRatio = 1
繪製到緩存區的大小也爲:200px * 200px
devicePixelRatio = 2200px 200px 的圖片對應到屏幕像素爲 400px 400px,devicePixelRatio = 2 瀏覽器就把緩存區的 200px * 200px 寬高分別放大兩倍渲染到屏幕中,因此就致使模糊
so 咱們的解決方案就是將 canvas 寬高設置爲屏幕的 2 倍
另外 canvas 會默認開啓抗鋸齒,咱們要手動將抗鋸齒關閉來實現圖片的銳化
context.mozImageSmoothingEnabled = false; context.webkitImageSmoothingEnabled = false; context.msImageSmoothingEnabled = false; context.imageSmoothingEnabled = false;
最終的代碼以下
html 部分
<div class="main"> <!-- 海報 html 元素 --> <div id="posterHtml" > <div class="posterHtml"> <img :src="posterHtmlBg" style="width:100%;height:100%" > <div class="posterklass">我是{{name}},邀請您:</div> <!-- 二維碼 --> <div id="qrcodeImg" :if="postcode"></div> </div> </div> <div id="myCanvas"></div> <span class="tip">長按保存該海報,邀請好友來測!</span> </div>
js 部分
createQrcode: function(text) { // 生成二維碼 const qrcodeImgEl = document.getElementById("qrcodeImg"); qrcodeImgEl.innerHTML = ""; let width = document.documentElement.clientWidth; width = width * 0.32; let qrcode = new QRCode(qrcodeImgEl, { width: width, height: width, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); qrcode.makeCode(text); this.createPoster(); }, createPoster() { // 生成海報 const vm = this; const domObj = document.getElementById("posterHtml"); var width = document.documentElement.clientWidth; var height = document.documentElement.clientHeight; var canvas = document.createElement("canvas"); var scale = 2; canvas.width = width * scale; canvas.height = height * scale; canvas.getContext("2d").scale(scale, scale); var opts = { scale: scale, canvas: canvas, logging: true, width: width, height: height, useCORS: true, allowTaint: false, logging: false, letterRendering: true, }; html2canvas(domObj, opts).then(function(canvas) { var context = canvas.getContext("2d"); // 重要 關閉抗鋸齒 context.mozImageSmoothingEnabled = false; context.webkitImageSmoothingEnabled = false; context.msImageSmoothingEnabled = false; context.imageSmoothingEnabled = false; var img = Canvas2Image.convertToImage( canvas, canvas.width, canvas.height ); vm.postshow = false; vm.postcode = false; img.style.width = canvas.width / 2 + "px"; img.style.height = canvas.height / 2 + "px"; img.style.position = "absolute"; img.style.top = "0px"; document.getElementById("myCanvas").appendChild(img); vm.$nextTick(() => { let e = document.querySelector("#posterHtml"); e.style.display = "none"; }) }); }
到此,需求就完美實現了。若是你有更好的思路請留言告訴我。