一、canvas使用場景介紹
需求:使用者在H5裡面答題,然後根據得分情況生成一個獎狀,獎狀上會顯示使用者的個人資訊(微信暱稱、答題時間),獎狀可以儲存,實際效果如下圖所示。
二、專案中遇到的坑
1、把獎狀背景圖繪製到canvas上,會出現圖片和文字模糊問題;
2、使用者微信暱稱繪製到獎狀上的紅線位置,需要在紅線區域居中,但是使用者暱稱千奇百怪,較難處理;
3、獎狀上的繪製的文字字型大小在移動端需要自適應。
三、解決方案
1、問題1解決方案:
(1)canvas中存在兩種尺寸:
canvas 的 width/height 是畫布的寬/高,用來確定這個畫布的內容大小,類似於 img 標籤中的圖片的寬高屬性;
canvas 的 樣式表裡的 width/height 代表的是最終繪製到瀏覽器上的尺寸,是 canvas 的物理寬度,
類似於 img 標籤中 樣式表中設定的寬高屬性。
複製程式碼
(2)圖片和文字模糊原因
canvas 中屬性的寬高並不代表實際的寬度,所以在高解析度裝置下(高清屏),瀏覽器就會放大 canvas,導致模糊;
canvas 的最小單位是畫素,通常情況下,裝置的一個畫素可以繪製一個點,
但是當 window.ddevicePixelRatio > 1 |的時候,裝置就會用大於1的畫素個數繪製 canvas
上的一個點,這時候如果還按照原始的畫布大小去繪製圖片或者文字時,看到的線條就比較粗,出現模糊的現象。
複製程式碼
(3)根據 devicePixelRatio 的值,放大 canvas 的內容大小,去掉模糊效果
const canvasBg = new Image(); //省略獎狀圖片地址
let canvas = document.getElementById("myCanvas");
canvasBg.onload = () => {
let ctx = canvas.getContext("2d");
let getPixelRatio = function (context) {
let backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
};
}
let ratio = getPixelRatio(ctx); //獲取裝置物理畫素與裝置獨立畫素的比例
let imgW = canvasBg.width;
var imgH = canvasBg.height;
canvas.width = $(window).width() * ratio; //放大畫布內容的寬度
canvas.height = imgH * $(window).width() / imgW * ratio; //放大畫布內容的高度
let cW = $('#myCanvas').width();
let cH = $('#myCanvas').height();
canvas.style.width = "100%";
ctx.drawImage(canvasBg, 0, 0, cW, imgH * cW / imgW); //把獎狀模板繪製到canvas上,自適應獎狀背景圖,不拉伸或壓縮圖片
複製程式碼
2、問題2解決方案:
(1)解決思路:
先計算獎狀上紅線的寬度,然後在計算出使用者暱稱的寬度,對於超過紅線寬度的使用者暱稱做特殊處理,
截斷超過紅線的字串,用省略號替代
複製程式碼
//擷取字串方法,引數1為傳入的使用者暱稱,引數2為要擷取的字串長度
function cutstr(str, len) {
var str_length = 0,
str_cut = new String(),
str_len = str.length;
var charAtStr;
for (var i = 0; i < str_len; i++) {
//獲取str字串指定位置的字元
charAtStr = str.charAt(i);
str_length++;
//中文字元的長度經編碼之後大於4,其他字元小於4,因此中文字元佔兩位,其他字元佔一位
if (escape(charAtStr).length > 4) {
str_length++;
}
str_cut = str_cut.concat(charAtStr);
if (str_length >= len) {
str_cut = str_cut.concat("..");
return str_cut;
}
}
//如果給定字串小於指定長度,則返回源字串;
if (str_length < len) {
return str;
}
}
}
let userInfo = {
userName: "xxx張小二xxx"
};
let paddingLeft = 0; //居中的字串距離 canvas 左邊界的距離
//ctx.measureText(userInfo.userName).width 為字串的寬度
//146 * $('#myCanvas').width() / 727 * ratio 根據比例算出紅線的寬度
if (ctx.measureText(userInfo.userName).width > 146 * $('#myCanvas').width() / 727 * ratio) {
userInfo.userName = cutstr(userInfo.userName, 8);
}
//魔鬼數字為固定尺寸獎狀上的某些元素的寬度,然後在根據比例求出
paddingLeft = 68 * $('#myCanvas').width() / 360 * ratio + (146 * $('#myCanvas').width() / 727 * ratio - ctx.measureText(userInfo.userName).width) / 2;
//繪製使用者暱稱到獎狀上
ctx.fillText(userInfo.userName, paddingLeft, 210 * $('#myCanvas').height() / 640 * ratio);
複製程式碼
3、問題3解決方案:
(1)解決思路
利用rem設定字型大小,使用淘寶的 flexible 外掛計算出移動裝置根節點的font-size,
然後在某一固定解析度的裝置上計算出當前使用者暱稱對應的字型大小,然後根據比例求出其他裝置對應的字型大小
複製程式碼
ctx.fillStyle = "red";
let fontSize = getComputedStyle(window.document.documentElement)['font-size'];
//0.41 為在某個確定解析度下除錯出的字型rem值
ctx.font = fontSize.replace(/px/, "") * 0.41 * ratio + "px Arial";
複製程式碼