canvas生成海報大小錯誤、模糊以及跨域的問題
最近要做一個生成海報的h5, 原理就是用canvas
的drawImage
API把圖片畫出來,想著應該很簡單,卻發現裡面有大坑。在填完坑後分享下解決方案,文章主要圍繞以下兩個問題來展開。
- 繪製圖片會有跨域的問題
- 生成的圖片、文字大小不正確,還會模糊,不清晰
1. demo初試
<style>
.J_ret_poster {
position: fixed;
z-index: 999;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
<canvas hidden id="J_poster">你的瀏覽器版本較低,請換個瀏覽器試試</canvas>
<button class="J_btn">生成海報</button>
<script>
const $ = q => document.querySelector(q),
$JPoster = $('#J_poster'),
$btn = $('J_btn'),
ctx = $canvas.getContext('2d'),
$bg = new Image(), // 需要的背景圖片(海報底圖)
$retImg = new Image(), // 最終生成的圖片
winW = window.innerWidth,
winH = window.innerHeight
// 設定canvas的大小為全屏
$JPoster.setAttribute('width', winW);
$JPoster.setAttribute('height', winH);
// 設定生成的圖片相關資訊
$retImg.setAttribute('width', winW);
$retImg.setAttribute('height', winH);
$retImg.setAttribute('alt', '海報');
$retImg.setAttribute('class', 'J_ret_poster');
$bg.src = 'http://canvas-img.oss-cn-shenzhen.aliyuncs.com/normal.jpg';
$bg.onload = () => {
ctx.drawImage($bg, 0, 0, winW, winH); // 從視窗的左上角開始畫起,鋪滿整個螢幕
ctx.font = '14px Arial';
ctx.fillStyle = '#fff';
ctx.fillText('這是test測試文字123', 100, 100);
const retUrl = $JPoster.toDataURL('image/png'); // 生成的圖片url
$retImg.setAttribute('src', retUrl);
$retImg.onload = () => {
$('body').appendChild($retImg); // 新增到body下
}
}
</script>
複製程式碼
執行之後出現錯誤
1.1 出現圖片跨域問題
報了一個跨域的錯誤1.2 為圖片新增跨域請求頭 access-control-allow-origin:*
由於我這裡使用的是阿里雲的oss,這裡就用圖說明下該如何操作(沒有阿里雲oss的也可以自己用node進行轉發)
然後又出現了問題!這裡是相關說明
1.3 為圖片新增crossOrigin屬性
$bg.crossOrigin = ''; // 跨域設定,這裡不用設定為`Anonymous`也是可以的
複製程式碼
然後圖片就可以出來了
這時又出現了問題,圖片尺寸不對
2. canvas大小錯誤以及模糊
2.1 大小錯誤
原因:需要繪製的圖片尺寸(
750*1334
)遠大於我們的螢幕尺寸iphone 6sp414*736
,因此猜想把canvas
的css
大小設定為我們螢幕的大小,這樣繪製應該就是這個螢幕的區域了。
// css定寬高為全屏
$JPoster.style.width = winW + 'px';
$JPoster.style.height = winH + 'px';
複製程式碼
然後在chrome的模擬器下圖片大小顯示一切正常
但是,在手機上又出現了新的問題,圖片和文字都是 模糊的!!(看不出模糊的可以和後面的一張圖對比看看)
2.2 繪製模糊
因為
canvas
不是向量圖,而是像圖片一樣是點陣圖模式的。高dpi
顯示裝置意味著每平方英寸有更多的畫素。也就是說二
倍屏,瀏覽器就會以2
個畫素點的寬度來渲染一個畫素,該canvas
在Retina
螢幕下相當於佔據了2
倍的空間,相當於圖片被放大了一倍,因此繪製出來的圖片文字等會變模糊。 因此,要做Retina
屏適配,關鍵是知道當前螢幕的裝置畫素比,然後將canvas
放大到該裝置畫素比來繪製,然後將canvas
用css
設定為螢幕的大小來展示。
解決思路: 在瀏覽器的 window
物件中有一個 devicePixelRatio
的屬性,該屬性表示了螢幕的裝置畫素比,即用幾個畫素點寬度來渲染1
個畫素。
舉例來說,假設 devicePixelRatio
的值為 2
,一張 100×100
畫素大小的圖片,在 Retina
螢幕下,會用 2
個畫素點的寬度去渲染圖片的 1
個畫素點,因此該圖片在 Retina
螢幕上實際會佔據 200×200
畫素的空間,相當於圖片被放大了1
倍,因此圖片會變得模糊。
類似的,在 canvas context
中也存在一個 backingStorePixelRatio
的屬性,該屬性的值決定了瀏覽器在渲染canvas
之前會用幾個畫素來來儲存畫布資訊。 backingStorePixelRatio
屬性在各瀏覽器廠商的獲取方式不一樣,所以需要加上瀏覽器字首來實現相容。
程式碼實現:
let devRatio = window.devicePixelRatio || 1, // 獲取裝置畫素比
// ctx的畫素比
backingStore = ctx.backingStorePixelRatio ||
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
const ratio = devRatio / backingStore;
// canvas放大畫素比倍
$JPoster.setAttribute('width', winW * ratio);
$JPoster.setAttribute('height', winH * ratio);
// canvas 放大後,相應的繪製圖片也要放大
ctx.scale(ratio, ratio);
複製程式碼
然後我們的圖片終於正常繪製出來了,手機上也顯示清晰。
3. 其他需要注意的點
一是我們繪製的圖片大小要控制好,太大就去壓縮一下,不然生成的base64太大,繪製時間長,還可能會出錯。二是瀏覽器上標題欄會佔據高度,導致視窗大小比例是不對的,生成的圖片會發生變形,需要注意處理一下,就不展開說了。