猿问

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

我正在尝试制作一个宽度取决于百分比的矩形,直到我用 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>


小唯快跑啊
浏览 98回答 1
1回答

跃然一笑

当你处于 时,你实现它的方式总是有一些“问题” 0%。如果您希望什么都没有,0%并且在百分比增长时保持一致,那么您不想使用ctx.lineJoin = "round"作为解决方法,您可以使用arc()方法来绘制圆角。关于arc(x, y, radius, startAngle, endAngle),我们知道x = r,y = r并且radius = rz我们只需要一些几何计算就可以得到所需的值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 /2当p < 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>现在,如果我们想要这种外观(红色部分,我们想要骑上灰色部分)。该方法包括执行相同的操作,但只进行一半的进度,然后对称地重复相同的图形(阴影区域)。为了绘制对称形状,我们将使用ctx.scale(-1, 1)和 方法save() restore()。第二个圆弧中心的 x 位置将是- (r - p)⇔ -((h / 2) - p),就像我们将在水平对称中工作一样,它最终将是(h / 2) - pctx.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 barsprogressbar2 = new progressBar({x: 10, y: 10, width: 400, height: 35}, "#FF1700", 50);// progressbar2.draw(); ---> No need coz we draw them laterprogressbar = 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();

开心每一天1111

我开始修复你的代码,最后重写了它。以下是有关如何更好地解决此问题的一些重要事项。您确实应该创建一个函数来在特定位置和长度处绘制一条线。然后你就有了空间一次可以干净地完成一行所需的所有数学运算。function drawLine(x, y, length) { /* ... */ }如果您希望线条长 40 像素,但角半径为 40,那么您想描画长度和宽度为零的单个点,半径为 20,这样整体宽度为 40,圆更小。&nbsp; // Get length of line that will be stroked&nbsp; let innerLength = length - cornerRadius * 2&nbsp; // If the line would have a length less than zero, set the length to zero.&nbsp; if (innerLength < 0) innerLength = 0&nbsp; // If the innerLength is less than the corner diameter, reduce the corner radius to fit.&nbsp; let actualCornerRadius = cornerRadius&nbsp; if (length < cornerRadius * 2) {&nbsp; &nbsp; actualCornerRadius = length / 2&nbsp; }由于您正在绘制一条描边线,而不是一个矩形,因此它简化了一些数学运算,以便能够仅从起点到终点绘制一条线。&nbsp; // Find the left and right endpoints of the inner line.&nbsp; const leftX = x + actualCornerRadius&nbsp; const rightX = leftX + innerLength&nbsp; // Draw the path and then stroke it.&nbsp; ctx.beginPath()&nbsp; ctx.moveTo(leftX, y)&nbsp; ctx.lineTo(rightX, y)&nbsp; ctx.stroke()最后,要在开放路径笔划上放置圆角笔划,只需将lineCap上下文的属性设置为'round'。&nbsp; ctx.lineCap = "round";单击此处查看工作演示。
随时随地看视频慕课网APP

相关分类

Html5
我要回答