为了账号安全,请及时绑定邮箱和手机立即绑定

带有圆角末端的画布矩形(进度条) - 值较低的问题

带有圆角末端的画布矩形(进度条) - 值较低的问题

小唯快跑啊 2023-09-25 16:56:10
我正在尝试制作一个宽度取决于百分比的矩形,直到我用 0% 测试某些内容为止,它都可以正常工作。我希望它在 0% 时消失,但由于我选择了圆角,所以存在最小宽度。对于较低的百分比数字,同样的问题也很明显,根据我所收集的信息,如果百分比低于 6%,它会以相反的方式推动对象,此时矩形会变成圆形,并且不能再变小。有解决方法吗?我一心只想着它,目前只需要解决这个问题。const canvas = $("#progressBar");const ctx = canvas.get(0).getContext("2d");// rectWidth = 630 * percent / 100 (in this case 100%)const rectX = 60;const rectY = 10;const rectWidth = 630 * 100 / 100;const rectHeight = 38;const cornerRadius = 37;ctx.lineJoin = "round";ctx.lineWidth = cornerRadius;ctx.strokeStyle = '#FF1700';ctx.fillStyle = '#FF1700';ctx.strokeRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);ctx.fillRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);// rectWidth = 630 * percent / 100 (in this case 0%)const rectX2 = 60;const rectY2 = 60;const rectWidth2 = 630 * 0 / 100;const rectHeight2 = 38;const cornerRadius2 = 37;ctx.lineJoin = "round";ctx.lineWidth = cornerRadius;ctx.strokeStyle = '#FF1700';ctx.fillStyle = '#FF1700';ctx.strokeRect(rectX2 + (cornerRadius2 / 2), rectY2 + (cornerRadius2 / 2), rectWidth2 - cornerRadius2, rectHeight2 - cornerRadius2);ctx.fillRect(rectX2 + (cornerRadius2 / 2), rectY2 + (cornerRadius2 / 2), rectWidth2 - cornerRadius2, rectHeight2 - cornerRadius2);<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><canvas id="progressBar" width="750" height="120"></canvas>
查看完整描述

1 回答

?
跃然一笑

TA贡献1826条经验 获得超6个赞

当你处于 时,你实现它的方式总是有一些“问题” 0%。如果您希望什么都没有,0%并且在百分比增长时保持一致,那么您不想使用ctx.lineJoin = "round"

作为解决方法,您可以使用arc()方法来绘制圆角。

关于arc(x, y, radius, startAngle, endAngle),我们知道x = ry = r并且radius = r

https://img1.sycdn.imooc.com/652953a400015c6805000372.jpgz

我们只需要一些几何计算就可以得到所需的值startAngle(α)和endAngle(α+Δ)。

利用三角函数余弦我们有Math.cos(θ) = (r - p) / r⇒ θ = Math.acos((r - p) / r)

我们有并且 α = Math.PI - θ 我们知道 Δ = 2 * θ(α+Δ) = Math.PI + θ

所以最后:

  • startAngle α = Math.PI - Math.acos((r - p) / r)

  • endAngle (α+Δ) = Math.PI + Math.acos((r - p) / r)

在我们的例子中,r = h /2p < r⇔时p < h / 2,我们得到:

ctx.arc(h / 2, h / 2, h / 2, Math.PI - Math.acos((h - 2 * p) / h), Math.PI + Math.acos((h - 2 * p) / h))
ctx.fillStyle = '#FF1700';
ctx.fill();

const canvas = $("#progressBar");

const ctx = canvas.get(0).getContext("2d");


const h = 100;

const p = 30;


/* To visalize ------------------------------------------------------*/

ctx.beginPath();

ctx.arc(h / 2, h / 2, h / 2, Math.PI / 2, 3 / 2 *Math.PI);

ctx.lineTo(500, 0);

ctx.arc((h / 2) + 500, h / 2, h / 2, 3 / 2 *Math.PI,Math.PI / 2);

ctx.lineTo(h / 2, h);

ctx.strokeStyle = '#000000';

ctx.stroke();

ctx.closePath();

/* ------------------------------------------------------------------*/


ctx.beginPath();

ctx.arc(h / 2, h / 2, h / 2, Math.PI - Math.acos((h - 2 * p) / h), Math.PI + Math.acos((h - 2 * p) / h));

ctx.fillStyle = '#FF1700';

ctx.fill();

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<canvas id="progressBar" width="750" height="120">

</canvas>

现在,如果我们想要这种外观(红色部分,我们想要骑上灰色部分)。该方法包括执行相同的操作,但只进行一半的进度,然后对称地重复相同的图形(阴影区域)。

https://img1.sycdn.imooc.com/65114b760001eb0b05100387.jpg

为了绘制对称形状,我们将使用ctx.scale(-1, 1)和 方法save() restore()。第二个圆弧中心的 x 位置将是- (r - p)⇔ -((h / 2) - p),就像我们将在水平对称中工作一样,它最终将是(h / 2) - p


ctx.beginPath();

ctx.arc(h / 2, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

ctx.save();

ctx.scale(-1, 1);

ctx.arc((h / 2) - p, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

ctx.restore();

ctx.fillStyle = '#FF1700';

ctx.fill();

const canvas = $("#progressBar");

const ctx = canvas.get(0).getContext("2d");


const h = 100;

const p = 25;


/* To visalize ------------------------------------------------------*/

ctx.beginPath();

ctx.arc(h / 2, h / 2, h / 2, Math.PI / 2, 3 / 2 *Math.PI);

ctx.lineTo(500, 0);

ctx.arc((h / 2) + 500, h / 2, h / 2, 3 / 2 *Math.PI,Math.PI / 2);

ctx.lineTo(h / 2, h);

ctx.strokeStyle = '#000000';

ctx.stroke();

ctx.closePath();

/* ------------------------------------------------------------------*/


ctx.beginPath();

ctx.arc(h / 2, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

ctx.save();

ctx.scale(-1, 1);

ctx.arc((h / 2) - p, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

ctx.restore();

ctx.fillStyle = '#FF1700';

ctx.fill();

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<canvas id="progressBar" width="750" height="120">

</canvas>

p <= h在我们需要更改代码以考虑矩形部分之前,情况都是如此。我们将使用 if...else 来做到这一点。


if(p <= h){

  ctx.beginPath();

  ctx.arc(h / 2, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

  ctx.save();

  ctx.scale(-1, 1);

  ctx.arc((h / 2) - p, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

  ctx.restore();

  ctx.fillStyle = '#FF1700';

  ctx.fill();

} else {

  ctx.beginPath();

  ctx.arc(h / 2, h / 2, h / 2, Math.PI / 2, 3 / 2 *Math.PI);

  ctx.lineTo(p - 2 * h, 0);

  ctx.arc(p - (h / 2), h / 2, h / 2, 3 / 2 *Math.PI,Math.PI / 2);

  ctx.lineTo(h / 2, h);

  ctx.fillStyle = '#FF1700';

  ctx.fill();

}

const canvas = $("#progressBar");

const ctx = canvas.get(0).getContext("2d");


const h = 100;

const p = 350;


/* To visalize ------------------------------------------------------*/

ctx.beginPath();

ctx.arc(h / 2, h / 2, h / 2, Math.PI / 2, 3 / 2 *Math.PI);

ctx.lineTo(500, 0);

ctx.arc((h / 2) + 500, h / 2, h / 2, 3 / 2 *Math.PI,Math.PI / 2);

ctx.lineTo(h / 2, h);

ctx.strokeStyle = '#000000';

ctx.stroke();

ctx.closePath();

/* ------------------------------------------------------------------*/


if(p <= h){

  ctx.beginPath();

  ctx.arc(h / 2, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

  ctx.save();

  ctx.scale(-1, 1);

  ctx.arc((h / 2) - p, h / 2, h / 2, Math.PI - Math.acos((h - p) / h), Math.PI + Math.acos((h - p) / h));

  ctx.restore();

  ctx.fillStyle = '#FF1700';

  ctx.fill();

} else {

  ctx.beginPath();

  ctx.arc(h / 2, h / 2, h / 2, Math.PI / 2, 3 / 2 *Math.PI);

  ctx.lineTo(p - 2 * h, 0);

  ctx.arc(p - (h / 2), h / 2, h / 2, 3 / 2 *Math.PI,Math.PI / 2);

  ctx.lineTo(h / 2, h);

  ctx.fillStyle = '#FF1700';

  ctx.fill();

}

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<canvas id="progressBar" width="750" height="120">

</canvas>

现在,我们可以总结一下了:


const canvas = $("#progressBar");

const ctx = canvas.get(0).getContext("2d");

const canvasWidth = ctx.canvas.width;

const canvasHeight = ctx.canvas.height;


class progressBar {


  constructor(dimension, color, percentage){

    ({x: this.x, y: this.y, width: this.w, height: this.h} = dimension);

    this.color = color;

    this.percentage = percentage / 100;

    this.p;

  }

  

  static clear(){

    ctx.clearRect(0, 0, canvasWidth, canvasHeight);  

  }

  

  draw(){

    // Visualize -------

    this.visualize();

    // -----------------

    this.p = this.percentage * this.w;

    if(this.p <= this.h){

      ctx.beginPath();

      ctx.arc(this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, Math.PI - Math.acos((this.h - this.p) / this.h), Math.PI + Math.acos((this.h - this.p) / this.h));

      ctx.save();

      ctx.scale(-1, 1);

      ctx.arc((this.h / 2) - this.p - this.x, this.h / 2 + this.y, this.h / 2, Math.PI - Math.acos((this.h - this.p) / this.h), Math.PI + Math.acos((this.h - this.p) / this.h));

      ctx.restore();

      ctx.closePath();

    } else {

      ctx.beginPath();

      ctx.arc(this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, Math.PI / 2, 3 / 2 *Math.PI);

      ctx.lineTo(this.p - this.h + this.x, 0 + this.y);

      ctx.arc(this.p - (this.h / 2) + this.x, this.h / 2 + this.y, this.h / 2, 3 / 2 * Math.PI, Math.PI / 2);

      ctx.lineTo(this.h / 2 + this.x, this.h + this.y);

      ctx.closePath();

    }

    ctx.fillStyle = this.color;

    ctx.fill();

  }

  

  visualize(){

    if (wholeprogressbar.checked === true){

      this.showWholeProgressBar();

    }

  }


  showWholeProgressBar(){

    ctx.beginPath();

    ctx.arc(this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, Math.PI / 2, 3 / 2 * Math.PI);

    ctx.lineTo(this.w - this.h + this.x, 0 + this.y);

    ctx.arc(this.w - this.h / 2 + this.x, this.h / 2 + this.y, this.h / 2, 3 / 2 *Math.PI, Math.PI / 2);

    ctx.lineTo(this.h / 2 + this.x, this.h + this.y);

    ctx.strokeStyle = '#000000';

    ctx.stroke();

    ctx.closePath();

  }

  

  get PPercentage(){

    return this.percentage * 100;

  }

  

  set PPercentage(x){

    this.percentage = x / 100;

  }

  

}


// We create new progress bars


progressbar2 = new progressBar({x: 10, y: 10, width: 400, height: 35}, "#FF1700", 50);

// progressbar2.draw(); ---> No need coz we draw them later


progressbar = new progressBar({x: 10, y: 60, width: 400, height: 35}, "#FF1700", 0);

// progressbar.draw(); ---> No need coz we draw them later


// For showing the current percentage (just for example)

setInterval(function() {

  let currentPercentage = progressbar.PPercentage;

    document.getElementById("percentage").innerHTML = `${Math.round(currentPercentage)} %`;

}, 20);


// We draw the progress-bars (just for example, one fix at 50% and one moving on a range from 0 to 100 %)


let i=0;

setInterval(function() {

  const start = 0;

  const end = 100;

  const step = 0.3;  

  progressbar.PPercentage = i * step;

  progressBar.clear();

  progressbar.draw();

  progressbar2.draw();

  i++;

  if(progressbar.PPercentage > end){

    i = start;

  }

}, 20);

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


<canvas id="progressBar" width="420" height="100"></canvas>


<div>

    <p> Progression at <span id="percentage"></span></p>

    <input type="checkbox" id="wholeprogressbar" name="wholeprogressbar" onclick="progressbar.draw()">

    <label for="wholeprogressbar">Visualize all the progress bar (100%)</label>

</div>

编辑 :

要创建进度条,您只需创建一个新实例

progressbar = new progressBar({x: PositionXinTheCanvas, y: PositionYinTheCanvas, width: WidthOfTheProgressBar, height: HeightOfTheProgressBar}, "ColorOfTheProgressBar", CurrentProgression);

..然后画它

progressbar.draw();

如果需要清除画布,请调用该clear()方法。如果您想要为进度条设置动画,您将需要它。由于它是静态方法,因此您需要在类上调用它progressBar

progressBar.clear();


查看完整回答
反对 回复 2023-09-25
?
开心每一天1111

TA贡献1836条经验 获得超13个赞

我开始修复你的代码,最后重写了它。以下是有关如何更好地解决此问题的一些重要事项。


您确实应该创建一个函数来在特定位置和长度处绘制一条线。然后你就有了空间一次可以干净地完成一行所需的所有数学运算。

function drawLine(x, y, length) { /* ... */ }

如果您希望线条长 40 像素,但角半径为 40,那么您想描画长度和宽度为零的单个点,半径为 20,这样整体宽度为 40,圆更小。

  // Get length of line that will be stroked

  let innerLength = length - cornerRadius * 2


  // If the line would have a length less than zero, set the length to zero.

  if (innerLength < 0) innerLength = 0


  // If the innerLength is less than the corner diameter, reduce the corner radius to fit.

  let actualCornerRadius = cornerRadius

  if (length < cornerRadius * 2) {

    actualCornerRadius = length / 2

  }

由于您正在绘制一条描边线,而不是一个矩形,因此它简化了一些数学运算,以便能够仅从起点到终点绘制一条线。

  // Find the left and right endpoints of the inner line.

  const leftX = x + actualCornerRadius

  const rightX = leftX + innerLength


  // Draw the path and then stroke it.

  ctx.beginPath()

  ctx.moveTo(leftX, y)

  ctx.lineTo(rightX, y)

  ctx.stroke()

最后,要在开放路径笔划上放置圆角笔划,只需将lineCap上下文的属性设置为'round'。

  ctx.lineCap = "round";

单击此处查看工作演示



查看完整回答
反对 回复 2023-09-25
  • 1 回答
  • 0 关注
  • 97 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信