我正在尝试创建在HiDPI设备上看起来不模糊的动画弧。

这就是我的弧线在iPhone 5s上的样子:

javascript - 视网膜上的 Canvas 弧线在某些点上太锐利-LMLPHP

您会看到0、90、180度弧附近变得太尖锐。我该如何预防?

这是我的代码:



// Canvas arc progress
const can = document.getElementById('canvas');
const ctx = can.getContext('2d');
const circ = Math.PI * 2;
const quart = Math.PI / 2;
const canvasSize = can.offsetWidth;
const halfCanvasSize = canvasSize / 2;

let start = 0,
  finish = 70,
  animRequestId = null;

// Get pixel ratio
const ratio = (function() {
  const dpr = window.devicePixelRatio || 1,
    bsr = ctx.webkitBackingStorePixelRatio ||
    ctx.mozBackingStorePixelRatio ||
    ctx.msBackingStorePixelRatio ||
    ctx.oBackingStorePixelRatio ||
    ctx.backingStorePixelRatio || 1;

  return dpr / bsr;
})();

// Set canvas h & w
can.width = can.height = canvasSize * ratio;
can.style.width = can.style.height = canvasSize + 'px';
ctx.scale(ratio, ratio)

ctx.beginPath();
ctx.strokeStyle = 'rgb(120,159,194)';
ctx.lineCap = 'square';
ctx.lineWidth = 8.0;
ctx.arc(halfCanvasSize, halfCanvasSize, halfCanvasSize - 4, 0, circ, false);
ctx.stroke();
ctx.closePath();

ctx.beginPath();
ctx.strokeStyle = 'rgb(244,247,255)';
ctx.lineCap = 'round';
ctx.lineWidth = 8.0;
ctx.closePath();

let imd = ctx.getImageData(0, 0, canvasSize, canvasSize);

const draw = (current) => {
  ctx.putImageData(imd, 0, 0);
  ctx.beginPath();
  ctx.arc(halfCanvasSize, halfCanvasSize, halfCanvasSize - 4, -(quart), ((circ) * current) - quart, false);
  ctx.stroke();
};

(function animateArcProgress() {
  animRequestId = requestAnimationFrame(animateArcProgress);

  if (start <= finish) {
    draw(start / 100);
    start += 2;
  } else {
    cancelAnimationFrame(animRequestId);
  }
})();

body {
  margin: 0;
}
div {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 300px;
  widht: 300px;
  background: #85b1d7;
}
canvas {
  height: 250px;
  width: 250px;
}

<div>
  <canvas id='canvas'></canvas>
</div>

最佳答案

您可以通过在内部代码中绘制1/2像素来柔化边缘,如下面的代码所示。

我分别在像素宽度分别为8、7.5和7的alpha颜色值0.25、0.5和1下渲染了3次弧。

您可以根据需要使其柔软。

BTW使用putImageData的速度非常慢,为什么不直接将背景渲染到另一个画布上并通过ctx.drawImage(offScreencanvas,0,0)进行绘制,那样您将使用GPU渲染背景,而不是通过图形端口总线来渲染CPU 。

我添加了更多代码以显示可以获得的不同柔化效果,并添加了鼠标缩放,以便可以更好地看到像素。



const can = document.getElementById('canvas');
const can2 = document.createElement("canvas"); // off screen canvas
const can3 = document.createElement("canvas"); // off screen canvas

const ctx = can.getContext('2d');
const ctx2 = can2.getContext('2d');
const ctx3 = can3.getContext('2d');
const circ = Math.PI * 2;
const quart = Math.PI / 2;
const canvasSize = can.offsetWidth;
const halfCanvasSize = canvasSize / 2;
const mouse = {x : null, y : null};
can.addEventListener("mousemove",function(e){
    var bounds = can.getBoundingClientRect();
    mouse.x = e.clientX - bounds.left;
    mouse.y = e.clientY - bounds.top;
});
let start = 0,
  finish = 70,
  animRequestId = null;

// Get pixel ratio
const ratio = (function() {
  const dpr = window.devicePixelRatio || 1,
    bsr = ctx.webkitBackingStorePixelRatio ||
    ctx.mozBackingStorePixelRatio ||
    ctx.msBackingStorePixelRatio ||
    ctx.oBackingStorePixelRatio ||
    ctx.backingStorePixelRatio || 1;

  return dpr / bsr;
})();

// Set canvas h & w
can2.height = can3.height = can2.width = can3.width = can.width = can.height = canvasSize * ratio;
can.style.width = can.style.height = canvasSize + 'px';

ctx.scale(ratio, ratio)
ctx2.scale(ratio, ratio)
ctx3.scale(ratio, ratio)

ctx2.beginPath();
ctx2.strokeStyle = 'rgb(120,159,194)';
ctx2.lineCap = 'square';
ctx2.lineWidth = 8.0;
ctx2.arc(halfCanvasSize, halfCanvasSize, halfCanvasSize - 4, 0, circ, false);
ctx2.stroke();
ctx2.closePath();

ctx2.beginPath();
ctx2.strokeStyle = 'rgb(244,247,255)';
ctx2.lineCap = 'round';
ctx2.lineWidth = 8.0;
ctx2.closePath();


const draw = (current) => {
  ctx3.clearRect(0,0,canvas.width,canvas.height);
  ctx3.drawImage(can2,0,0);
  var rad = halfCanvasSize - 4;
  const drawArc = () => {
      ctx3.beginPath();
      ctx3.arc(halfCanvasSize, halfCanvasSize, rad, -(quart), ((circ) * current) - quart, false);
      ctx3.stroke();
  }
  // draw soft
  ctx3.strokeStyle = 'rgb(244,247,255)';
  ctx3.lineWidth = 8.5;
  ctx3.globalAlpha = 0.25;
  drawArc();;

  ctx3.lineWidth = 7.0;
  ctx3.globalAlpha  = 0.5;
  drawArc();;

  ctx3.lineWidth = 6.5;
  ctx3.globalAlpha  = 1;
  drawArc();

  // draw normal
  rad -= 12;
  ctx3.lineWidth = 8.0;
  ctx3.globalAlpha = 1;
  drawArc();;



  // draw ultra soft
  rad -= 12;
  ctx3.strokeStyle = 'rgb(244,247,255)';
  ctx3.lineWidth = 9.0;
  ctx3.globalAlpha = 0.1;
  drawArc();

  ctx3.lineWidth = 8.0;
  ctx3.globalAlpha  = 0.2;
  drawArc();;

  ctx3.lineWidth = 7.5;
  ctx3.globalAlpha  = 0.5;
  drawArc();

  ctx3.lineWidth = 6;
  ctx3.globalAlpha  = 1;
  drawArc();




};

const zoomW = 30;
const zoomAmount = 5;
const drawZoom = () => {
    ctx.drawImage(can3,0,0);
    var width = zoomW * zoomAmount;
    var cx = mouse.x -  width / 2;
    var cy = mouse.y -  width / 2;
    var c1x = mouse.x -  zoomW / 2;
    var c1y = mouse.y -  zoomW / 2;
    ctx.strokeStyle = 'rgb(244,247,255)';
    ctx.lineWidth = 4;
    ctx.strokeRect(cx,cy,width,width);
    ctx.clearRect(cx,cy,width,width);
    ctx.imageSmoothingEnabled = false;
    ctx.mozImageSmoothingEnabled = false;
    ctx.drawImage(can3,c1x,c1y,zoomW,zoomW,cx,cy,width,width);
    ctx.imageSmoothingEnabled = true;
    ctx.mozImageSmoothingEnabled = true;
}
function keepUpdating(){
    ctx.clearRect(0,0,can.width,can.height);
    drawZoom();
    requestAnimationFrame(keepUpdating);
}

(function animateArcProgress() {
  ctx.clearRect(0,0,can.width,can.height);

  draw(start / 100);
  drawZoom();


  if (start <= finish) {
    start += 0.5;
    requestAnimationFrame(animateArcProgress);
  } else {
    requestAnimationFrame(keepUpdating);
  }
})();

body {
  margin: 0;
}
div {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 300px;
  widht: 300px;
  background: #85b1d7;
}
canvas {
  height: 250px;
  width: 250px;
}

<div>
  <canvas id='canvas'></canvas>
</div>

10-06 15:42