进度圆圈 - 在圆圈的末端画一个小圆弧 + 更多

Progress circle - draw a small arc at the end tip of the circle + more

如何在进度圆圈的末端绘制一个小圆圈,并在其下方/上方添加一个小文本块?

示例图片:

<div class="radial-progress" data-progress="0">
    <div class="circle">
    <div class="img"></div>
        <div class="mask full">
            <div class="fill"></div>
        </div>
        <div class="mask half">
            <div class="fill"></div>
            <div class="fill fix"></div>
        </div>
        <div class="shadow"></div>
    </div>
    <div class="inset">
        <div class="percentage">
            <div class="numbers"><span>-</span><span>0%</span><span>1%</span> <!--- lots of spans --->
        </div>
    </div>
</div>

感谢 Andre 的 medium.com 文章,我已经对他的版本进行了一些编辑 - 更新版本说明了我希望针对给定的 % 值动态实现的目标:http://codepen.io/Inlesco/pen/pgKXeG

然而,编译后的CSS实在太多了。对于 100 中的每 %,将有太多 CSS 用于定位事物。当前的 CSS(@codepen 示例)已经重约 50 KB。不是最佳做法。

我已经在基于 JS Canvas 的变体上取得了一些进展,将 vert&horz 居中的 img 定位在 canvas 之上。但它真的是一个好看的响应式网站的唯一方法和最佳实践吗?

CSS 绝对不是执行此操作的正确工具,我强烈建议您放弃该想法。 Canvas 绝对是一个不错的选择,但对于响应式网站,我建议您使用 SVG。使用 SVG,可以很容易地根据用户输入绘制进度圈,并在其尖端添加 circle/dot。

以下是首先创建进度圈必须执行的步骤:

  • 获取进度圆(0 到 100)的用户输入,然后根据它计算圆弧的角度。公式为 (input * 360/100) 因为圆有 360 度。
  • SVG 圆弧通常从时钟中的 3 位置开始,因此计算出的圆弧角度必须在负方向上偏移 90 度。也就是说,弧度必须是从-90度到270度。
  • 使用公式 -(角度(度数)* PI / 180)将计算出的角度转换为弧度。
  • 一旦计算出以弧度为单位的角度,使用简单的三角函数根据角度在圆上找到一个点:

    • X 坐标 = Cos(弧度角)* 半径 + 中心点的 X 坐标。
    • Y 坐标 = Sin(角度弧度) * 半径 + 中心点的 Y 坐标。
  • 找到点后,我们需要创建路径,从上一步找到的点开始,到圆心,然后到起点圆弧并从该点再次绘制圆弧到原点。以这种方式创建路径是因为我们需要路径在所需点结束(因为我们将标记附加到终点)。

  • 创建圆弧需要注意的一点是,任何 > 180 度的角度都需要两个圆弧命令来创建。第一个弧将是时钟中 12 的位置到 6 的位置,下一个弧将用于其余部分。因此,我们使用 if/else 循环。

要在其顶端添加dot/circle,需要做以下工作:

  • 一个 marker 元素被添加到 SVG 中,并且使用 circle 元素创建了一个圆圈形式的标记。 circle 元素具有 3 个属性 - cx、cy 是圆点的中心点,r 是圆点的半径。
  • 然后使用 marker-end 属性将此标记添加到 path。设置此属性将意味着创建的任何路径都会在其结束点自动附加此点。不需要为此进行其他编码,因为 SVG 会自动处理定位。

也可以使用 text 元素添加文本,然后可以使用 xy 属性设置其位置。 (这个位仍然需要在下面的代码片段中进行调整。

演示:

下面是一个非常粗略的实现演示。

window.onload = function() {
  var btn = document.querySelector('button'),
    inp = document.querySelector('#progress'),
    path = document.querySelector('#p'),
    text = document.querySelector('#val'),
    rect = document.querySelector('rect'),
    output = document.querySelector('#path-output');
  var x = 0,
    y = 0,
    r = 30,
    cx = 50,
    cy = 50,
    d = '',
    fill = 'yellowgreen',
    stroke = 'transparent';
  btn.addEventListener('click', function() {
    progress = (inp.value == '') ? 0 : inp.value;
    arcRad = ((progress * 360 / 100) - 90) * Math.PI / 180;

    x = Math.cos(arcRad) * r + cx;
    y = Math.sin(arcRad) * r + cy;

    if (progress > 0 && progress <= 50) {
      d = 'M' + x + ',' + y + ' L' + cx + ',' + cy + ' L' + cx + ',' + (cy - r) + ' A' + r + ',' + r + ' 0 0,1' + x + ',' + y;
      setColors(fill, stroke);
      setOutput();
    } else if (progress > 50 && progress <= 100) {
      d = 'M' + x + ',' + y + ' L' + cx + ',' + cy + ' L' + cx + ',' + (cy - r) + ' A' + r + ',' + r + ' 0 0,1' + cx + ',' + (cy + r) + ' A' + r + ',' + r + ' 0 0,1' + x + ',' + y;
      setColors(fill, stroke);
      setOutput();
    } else {
      text.innerHTML = '';
      path.setAttribute('d', '');
      output.innerHTML = 'Enter a value between 0 and 100';
      setColors('transparent', 'transparent');
    }

    if (progress > 0 && progress <= 10) {
      rect.setAttribute('x', x);
      rect.setAttribute('y', y + 7.5);
      text.setAttribute('x', x + 2);
      text.setAttribute('y', y + 15);
    } else if (progress > 10 && progress <= 62) {
      rect.setAttribute('x', x - 5);
      rect.setAttribute('y', y + 7.5);
      text.setAttribute('x', x - 3.5);
      text.setAttribute('y', y + 15);
    } else if (progress > 62 && progress <= 100) {
      rect.setAttribute('x', x - 5);
      rect.setAttribute('y', y - 15);
      text.setAttribute('x', x - 3.5);
      text.setAttribute('y', y - 7.5);
    }

  });

  function setColors(fill, stroke) {
    rect.setAttribute('fill', fill);
    rect.setAttribute('stroke', stroke);
    path.setAttribute('fill', fill);
    path.setAttribute('stroke', stroke);
  }

  function setOutput() {
    path.setAttribute('d', d);
    text.innerHTML = progress;
    output.innerHTML = 'Angle in Radians: ' + arcRad + '<br/>';
    output.innerHTML += 'Point in Circle: ' + x + ',' + y + '<br/>';
    output.innerHTML += 'Path d attribute: ' + d;
  }
}
svg {
  width: 200px;
  height: 200px;
}
.output {
  min-height: 20px;
}
h4 {
  border-bottom: 1px solid;
}
<input id='progress' type='number' />
<button>Generate</button>
<br/>
<svg viewBox='0 0 100 100'>
  <defs>
    <marker id='dot' viewBox='0 0 10 10' markerHeight='10' markerWidth='10' refX='5' refY='5'>
      <circle cx='5' cy='5' r='2' />
    </marker>
  </defs>
  <path d='' marker-end='url(#dot)' id='p' stroke='transparent' fill='transparent' />
  <rect height='10' width='10' x='10' y='10' stroke='transparent' fill='transparent' />
  <text x='10' y='10' id='val' font-family='Arial' font-size='6'></text>
</svg>
<div class='output'>
  <h4>Output:</h4>
  <output id='path-output'></output>
</div>

进一步阅读:

您可以在以下链接中阅读有关 SVG 及其元素和属性的更多信息:

SVG Paths | Marker element | Text element | Circle element