解決canvas合成圖片大小錯誤、模糊以及跨域的問題

Lvzongqiiiiiii發表於2018-11-04

最近要做一個生成海報的h5, 原理就是用canvasdrawImageAPI把圖片畫出來,想著應該很簡單,卻發現裡面有大坑。在填完坑後分享下解決方案,文章主要圍繞以下兩個問題來展開。

  • 繪製圖片會有跨域的問題
  • 生成的圖片、文字大小不正確,還會模糊,不清晰

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>
 
 <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 出現圖片跨域問題

cors-error
報了一個跨域的錯誤

1.2 為圖片新增跨域請求頭 access-control-allow-origin:*

由於我這裡使用的是阿里雲的oss,這裡就用圖說明下該如何操作(沒有阿里雲oss的也可以自己用node進行轉發)

ali-set-cors
然後又出現了問題!
toDataURL-error
這裡是相關說明

1.3 為圖片新增crossOrigin屬性

$bg.crossOrigin = ''; // 跨域設定,這裡不用設定為`Anonymous`也是可以的
複製程式碼

然後圖片就可以出來了

size-big

這時又出現了問題,圖片尺寸不對

2. canvas大小錯誤以及模糊

2.1 大小錯誤

原因:需要繪製的圖片尺寸(1242*2208)遠大於我們的螢幕尺寸iphone 6sp414*736,因此猜想把canvascss大小設定為我們螢幕的大小,這樣繪製應該就是整個螢幕的區域了。

// css定寬高為全屏
$JPoster.style.width = winW + 'px';
$JPoster.style.height = winH + 'px';
複製程式碼

然後在chrome的模擬器下圖片大小顯示一切正常

pc-display


但是,在手機上又出現了新的問題,圖片和文字都是模糊的!!(看不出模糊的可以和後面的一張圖對比看看)

mobile-display

2.2 繪製模糊

因為 canvas 不是向量圖,而是像圖片一樣是點陣圖模式的。高dpi 顯示裝置意味著每平方英寸有更多的畫素。也就是說倍屏,瀏覽器就會以2個畫素點的寬度來渲染一個畫素,該 canvasRetina 螢幕下相當於佔據了2倍的空間,相當於圖片被放大了一倍,因此繪製出來的圖片文字等會變模糊。 因此,要做 Retina 屏適配,關鍵是知道當前螢幕的裝置畫素比,然後將 canvas 放大到該裝置畫素比來繪製,然後將 canvascss設定為螢幕的大小來展示。

解決思路: 在瀏覽器的 window 物件中有一個 devicePixelRatio 的屬性,該屬性表示了螢幕的裝置畫素比,即用幾個畫素點寬度來渲染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);
複製程式碼

然後我們的圖片終於正常繪製出來了,手機上也顯示清晰。

draw-right

3. 其他需要注意的點

一是我們繪製的圖片大小要控制好,太大就去壓縮一下,不然生成的base64太大,繪製時間長,還可能會出錯。二是瀏覽器上標題欄會佔據高度,導致視窗大小比例是不對的,生成的圖片會發生變形,需要注意處理一下,就不展開說了。

相關文章