項目需要畫一個餅圖,個插件沒找到符合要求的,於是自己手動畫了一個。可是到移動端的時候,或出現模糊不清的情況,研究了一下是高清屏的問題。
因為 canvas 不是矢量圖,而是像圖片一樣是位圖模式的。高 dpi 顯示設備意味着每平方英寸有更多的像素。也就是說二倍屏,瀏覽器就會以2個像素點的寬度來渲染一個像素,該 canvas 在 Retina 屏幕下相當於占據了2倍的空間,相當於圖片被放大了一倍,因此繪制出來的圖片文字等會變模糊。
因此,要做 Retina 屏適配,關鍵是知道當前屏幕的設備像素比,然后將 canvas 放大到該設備像素比來繪制,然后將 canvas 壓縮到一倍來展示。
所以只要加上以下代碼即可:
// *****************解決移動端糊的問題
let dpr = window.devicePixelRatio; // 假設dpr為2
// 獲取css的寬高
let { width: cssWidth, height: cssHeight } = canvas.getBoundingClientRect();
// 根據dpr,擴大canvas畫布的像素,使1個canvas像素和1個物理像素相等
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
canvas.width = dpr * cssWidth;
canvas.height = dpr * cssHeight;
// 由於畫布擴大,canvas的坐標系也跟着擴大,如果按照原先的坐標系繪圖內容會縮小
// 所以需要將繪制比例放大
ctx.scale(dpr,dpr);
// **************************
下面開始進行畫圖,填充文字就可以了,出來的效果就非常清晰了。
解決前:
解決后:
下面貼出來我的完整代碼:
<canvas id="canvas" width={200} height={200} />
let canvas = document.getElementById('canvas');
if(canvas.getContext) {
let ctx = canvas.getContext('2d');
// *****************解決移動端糊的問題
let dpr = window.devicePixelRatio; // 假設dpr為2
// 獲取css的寬高
let { width: cssWidth, height: cssHeight } = canvas.getBoundingClientRect();
// 根據dpr,擴大canvas畫布的像素,使1個canvas像素和1個物理像素相等
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
canvas.width = dpr * cssWidth;
canvas.height = dpr * cssHeight;
// 由於畫布擴大,canvas的坐標系也跟着擴大,如果按照原先的坐標系繪圖內容會縮小
// 所以需要將繪制比例放大
ctx.scale(dpr,dpr);
// **************************
for(let i = 0; i < listSubPercent.length; i++) {
ctx.beginPath();
let startAngle = listSubPercent[i] * Math.PI / 180;
let endAngle = listSubPercent[i + 1] * Math.PI / 180;
ctx.arc(pointX, pointY, sectorR, startAngle, endAngle, false);
ctx.lineTo(pointX, pointY); //轉折點
ctx.fillStyle = colorList[i];
ctx.fill(); //扇形填充
}
//繪制圓形
ctx.beginPath();
ctx.moveTo(pointX, pointY); //起始點中心坐標點
let startAngle = 0; // 開始點
let endAngle = 2 * Math.PI;
ctx.arc(pointX, pointY, circleR, startAngle, endAngle, true);
ctx.fillStyle = fillColor; //作對比 暫用此顏色 需要改為黑色才能和三角拼接 rgba(255,165,0,1)類也可以
ctx.fill(); //圓形填充
//中間位置的度數
let middleDegree = (listSubPercent[num + 1] - listSubPercent[num]) / 2 + listSubPercent[num];
middleDegree = 90 - middleDegree;
//三角形底邊的另一個點的位置
let X1 = Math.sin((middleDegree + 90) * Math.PI / 180) * 30 + pointX;
let Y1 = Math.cos((middleDegree + 90) * Math.PI / 180) * 30 + pointY;
//三角形底邊的一個點的位置
let X = Math.sin((middleDegree + 270) * Math.PI / 180) * 30 + pointX;
let Y = Math.cos((middleDegree + 270) * Math.PI / 180) * 30 + pointY;
//三角形頂點的位置
let RX = Math.sin(middleDegree * Math.PI / 180) * 60 + pointX;
let RY = Math.cos(middleDegree * Math.PI / 180) * 60 + pointY;
ctx.beginPath();
ctx.moveTo(X1, Y1); //起始點
ctx.lineTo(X, Y); //轉折點
ctx.lineTo(RX, RY); //結束點
ctx.fillStyle = fillColor;
ctx.fill(); //三角形填充
// 繪制文字
ctx.font = "16px sans-serif";
ctx.fillStyle = textColor;
ctx.fillText(data[0].ratingagency, 70, 95);
ctx.font = "14px sans-serif";
ctx.fillStyle = textColor;
ctx.fillText(`${data[0].percentage}%`, 80, 115);
}