SVGで円形のプログレスバー

(アニメーションあり)

DEMO

  • 入力欄に入れた0100の値に応じて、SVGの円形プログレスバー表示が切り替わるサンプル
  • svg 要素内にある path 要素の stroke-dashoffset 属性値をJavaScriptで変更することで、動的に変更が可能
  • 値を変更した際のアニメーションは、CSSで transition: stroke-dashoffset 1s; を指定
  • progress要素を隠して値だけを更新することで、セマンティックなコードに対応

%

コード (HTML/CSS/JS)

HTML

<!-- ↓SVGの円形プログレスバー ====================== -->
<div class="progress_circle" data-percent="0">
  <svg viewBox="-10 -10 220 220">
    <g fill="none" stroke-width="10" transform="translate(100,100)">
      <path d="M 0,-100 A 100,100 0 0,1 86.6,-50" stroke="url(#cl1)" />
      <path d="M 86.6,-50 A 100,100 0 0,1 86.6,50" stroke="url(#cl2)" />
      <path d="M 86.6,50 A 100,100 0 0,1 0,100" stroke="url(#cl3)" />
      <path d="M 0,100 A 100,100 0 0,1 -86.6,50" stroke="url(#cl4)" />
      <path d="M -86.6,50 A 100,100 0 0,1 -86.6,-50" stroke="url(#cl5)" />
      <path d="M -86.6,-50 A 100,100 0 0,1 0,-100" stroke="url(#cl6)" />
    </g>
  </svg>
  <svg viewBox="-10 -10 220 220">
    <path
      d="M200,100 C200,44.771525 155.228475,0 100,0 C44.771525,0 0,44.771525 0,100 C0,155.228475 44.771525,200 100,200 C155.228475,200 200,155.228475 200,100 Z"
      stroke-dashoffset="0"></path>
  </svg>
  <progress value="0" min="0" max="100"></progress>
</div>
<!-- ↑SVGの円形プログレスバー ====================== -->
<!-- ↓SVGのグラデーション色定義 ====================== -->
<svg width="0" height="0">
  <defs>
    <linearGradient id="cl1" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="1">
      <stop stop-color="#33ABDB"/>
      <stop offset="100%" stop-color="#0F9AE5"/>
    </linearGradient>
    <linearGradient id="cl2" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="0" y2="1">
      <stop stop-color="#0F9AE5"/>
      <stop offset="100%" stop-color="#33ABDB"/>
    </linearGradient>
    <linearGradient id="cl3" gradientUnits="objectBoundingBox" x1="1" y1="0" x2="0" y2="1">
      <stop stop-color="#33ABDB"/>
      <stop offset="100%" stop-color="#4EB8D4"/>
    </linearGradient>
    <linearGradient id="cl4" gradientUnits="objectBoundingBox" x1="1" y1="1" x2="0" y2="0">
      <stop stop-color="#4EB8D4"/>
      <stop offset="100%" stop-color="#7FCFC7"/>
    </linearGradient>
    <linearGradient id="cl5" gradientUnits="objectBoundingBox" x1="0" y1="1" x2="0" y2="0">
      <stop stop-color="#7FCFC7"/>
      <stop offset="100%" stop-color="#4EB8D4"/>
    </linearGradient>
    <linearGradient id="cl6" gradientUnits="objectBoundingBox" x1="0" y1="1" x2="1" y2="0">
      <stop stop-color="#4EB8D4"/>
      <stop offset="100%" stop-color="#33ABDB"/>
    </linearGradient>
  </defs>
</svg>
<!-- ↑SVGの色定義 ====================== -->
<!-- ↓数値の入力欄 -->
<input id="percent_input" type="number" min="0" max="100" value="0" /> %
<!-- ↑数値の入力欄 -->

CSS

@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400&display=swap');
.progress_circle {
  margin-bottom: 20px;
  display: inline-block;
  position: relative;
  text-align: center;
  font-family: "Lato", sans-serif;
  font-weight: 400;
  color: #BFBDC8;
}
.progress_circle::before {
  content: "Loading...";
  bottom: -15px;
  font-size: 14px;
  animation: blink 1s ease-in-out infinite alternate;
}
.progress_circle::after {
  content: attr(data-percent) "%";
  top: 50%;
  transform: translateY(-50%);
  font-size: 20px;
}
.progress_circle::before,
.progress_circle::after {
  width: 100%;
  position: absolute;
  left: 0;
  text-align: center;
}
.progress_circle svg {
  width: 220px;
  height: 220px;
}
.progress_circle svg:nth-child(2) {
  position: absolute;
  left: 0;
  top: 0;
  transform: rotate(-90deg);
}
.progress_circle svg:nth-child(2) path {
  fill: none;
  stroke-width: 25;
  stroke-dasharray: 629;
  stroke: #fff;
  opacity:.9;
  transition: stroke-dashoffset 1s;
}
.progress_circle progress {
  height: 0;
  width: 0;
  visibility: hidden;
}
@keyframes blink {
  0% { opacity: 0.6; }
  100% {opacity: 1; }
}

JavaScript

/**
  * SVG円形プログレスバーの更新
  * @type {HTMLElement} progressCircleElem プログレスバーの枠要素
  * @type {HTMLElement} pathElem svgのpath要素
  * @type {string} value 文字列の数値 (0〜100)
  */
function updateProgressCircle(progressCircleElem, pathElem, value) {
  const STROKE_DASHARRAY_VALUE = 629;
  progressCircleElem.querySelector('progress').value = value;
  progressCircleElem.dataset.percent = value;
  const dashoffset = Math.round(STROKE_DASHARRAY_VALUE * 100 / 100 * Number(value) / 100);
  pathElem.setAttribute('stroke-dashoffset', dashoffset);
}
window.addEventListener('load', function () {
  // 初期化
  const INIT_VALUE = 0;
  const progressCircleElem = document.querySelector('.progress_circle');
  const pathElem = progressCircleElem.querySelector('svg path[stroke-dashoffset]');
  updateProgressCircle(progressCircleElem, pathElem, INIT_VALUE);

  // 擬似的に、入力欄に入れた値をプログレスバーへ反映
  const percentInputElem = document.getElementById('percent_input');
  percentInputElem.addEventListener('input', (event) => {
    updateProgressCircle(progressCircleElem, pathElem, event.target.value);
  });
});