CSSで進捗サークルチャートを描いてJavaScriptで可変

[**background**](https://developer.mozilla.org/ja/docs/Web/CSS/background)に[**radial-gradient**](https://developer.mozilla.org/ja/docs/Web/CSS/radial-gradient)と[**conic-gradient**](https://developer.mozilla.org/ja/docs/Web/CSS/conic-gradient)を組み合わせて適用すればできそうな感じだったので試してみました。

```html
<div style='
  width: 200px;
  height: 200px;
  border-radius: 50%;
  background:
    radial-gradient(#fff 80px, transparent 80px),
    conic-gradient(orange 120deg, gray 0);'>
```

![test.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618708/4b096540-ca32-4feb-a9fe-684b4e93a678.jpeg)

白背景ならこのままでも良さそうですが、背景色が穴の部分を透過できるよういくつか要素を組み合わせてJavaScriptでスタイルを変更するサンプルを作ってみました。

[**動作デモ**](https://akebi.jp/temp/circle_progress_chart_test.html)

```html
<html>
<head>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<style>
    .circle {
        position: relative;
        width: 200px;
        height: 200px;
        background:
            radial-gradient(#0000 89px, #fff 90px, #fff 99px, #0000 100px);
    }
    .circle .graph {
        position: absolute;
        mix-blend-mode: multiply;
        width: 200px;
        height: 200px;
        background:
            radial-gradient(#fff 89px, #0000 90px, #0000 99px, #fff 100px),
            conic-gradient(red 180deg, darkred 0);
    }
    .circle .percent {
        position: absolute;
        mix-blend-mode: difference;
        width: 100%;
        text-align: center;
        top: 50%;
        color: #fff;
        font-size: 25px;
    }
</style>
</head>
<body style='background-color: #ccc'>

    <div class='circle' id='graph01'>
        <div class='graph'></div>
        <div class='percent'>**.*%</div>
    </div>

    <script>
        'use strict';

        const per = 45.6; // パーセント
        const size = 200; // 直径

        window.addEventListener('DOMContentLoaded', function() {
            const d = document.getElementById('graph01');
            const graph = d.querySelector('.graph');
            const percent = d.querySelector('.percent');

            const deg = per * 3.6;
            const outerRadius = size / 2;
            const innerRadius = outerRadius * 0.9;

            // 各要素のstyle変更
            d.style.width = `${size}px`;
            d.style.height = `${size}px`;
            d.style.background = `radial-gradient(#0000 ${innerRadius - 1}px, #fff ${innerRadius}px, #fff ${outerRadius - 1}px, #0000 ${outerRadius}px)`;

            graph.style.width = `${size}px`;
            graph.style.height = `${size}px`;
            graph.style.background =
                `radial-gradient(#fff ${innerRadius - 1}px, #0000 ${innerRadius}px, #0000  ${outerRadius - 1}px, #fff ${outerRadius}px),
                 conic-gradient(red ${deg}deg, darkred 0)`;

            percent.style.fontSize = `${(outerRadius / 4).toFixed(1)}px`;
            percent.textContent = `${per.toFixed(1)}%`;
        });
    </script>
</body>
</html>
```

####サイズや色を動的に可変させるサンプル####
[**動作デモ**](https://akebi.jp/temp/circle_progress_chart.html)

```html
<!DOCTYPE html>
<html lang='ja'>
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='user-scalable=no,width=device-width,initial-scale=1'>
        <title>circle progress chart test</title>
        <style>
        .circle {
            position: relative;
            width: 200px;
            height: 200px;
            background:
                radial-gradient(#0000 88px, #fff 90px, #fff 98px, #0000 100px);
        }
        .circle .graph {
            position: absolute;
            mix-blend-mode: multiply;
            width: 200px;
            height: 200px;
            background:
                radial-gradient(#fff 88px, #0000 90px, #0000 98px, #fff 100px),
                conic-gradient(#4e4 324deg, #ded 0);
        }
        .circle .percent {
            position: absolute;
            mix-blend-mode: difference;
            width: 100%;
            text-align: center;
            top: 50%;
            color: #fff;
            font-size: 25px;
        }

        .slider {
            position: fixed;
            width: 100%;
            left: 0;
            bottom: 0px;
            background-color: #fffa;
            padding: 10px;
        }
        input[type=range] {
            width: 60%;
            max-width: 400px;
        }
        #css {
            width: 80%;
            height: 20px;
            background-color: #0000;
        }
        </style>
    </head>
    <body>
        <div class='circle' id='graph01'>
            <div class='graph'></div>
            <div class='percent'>**.*%</div>
        </div>

        <div class='slider'>
            <textarea id='css'></textarea><br>
            <input type='range' id='outerSize' min='50' max='300' value='200'>
            outer size<br>
            <input type='range' id='innerSize' min='5' max='50' value='10'>
            inner size<br>
            <input type='range' id='percent' min='0' max='100' value='90' step='0.1'>
            value<br>
            <input type='color' id='backColor' value='#ffffff'>
            background color<br>
            <input type='color' id='graphColor0' value='#ddeedd'>
            graph base color<br>
            <input type='color' id='graphColor1' value='#44ee44'>
            graph value color<br>
        </div>

        <script>
            'use strict';
            const
                sliders = {},
                graph   = document.getElementById('graph01');

            window.addEventListener('DOMContentLoaded', function(){
                for(const e of document.querySelectorAll('input')) {
                    sliders[e.id] = e;
                    e.addEventListener('input', setValue);
                }
                setValue();

                const
                    d  = document.getElementById('css'),
                    mf = window.ontouchstart !== undefined ? true : false;
                let ocf;
                d.addEventListener(mf ? 'touchstart' : 'mousedown', function() {
                    ocf = 1;
                });
                d.addEventListener(mf ? 'touchmove' : 'mousemove', function() {
                    ocf = 0;
                });
                d.addEventListener(mf ? 'touchend' : 'mouseup', function() {
                    if(ocf === 0) return;
                    this.style.height = (this.style.height === '250px' ? 20 : 250) + 'px';
                });
            });

            function setValue() {
                const
                    outerSize  = +sliders.outerSize.value,
                    innerSize  = +sliders.innerSize.value,
                    percent    = +sliders.percent.value,
                    color0     = sliders.graphColor0.value,
                    color1     = sliders.graphColor1.value,
                    c          = graph.querySelector('div[class=graph]'),
                    c2         = graph.querySelector('div[class=percent]'),
                    innerRatio = Math.floor(outerSize / 2 - outerSize / 200 * innerSize),
                    deg        = Math.floor(percent * 3.6);

                const css = {};
                let t;

                document.querySelector('body').style.backgroundColor =
                    sliders.backColor.value;

                graph.style.width  =
                graph.style.height =
                c.style.width      =
                c.style.height     = t = `${outerSize}px`;

                css.circle  = `  position: relative;\n  width: ${t};\n  height: ${t};\n`;
                css.graph   = `  position: absolute;\n  mix-blend-mode: multiply;\n  width: ${t};\n  height: ${t};\n`;
                css.percent = `  position: absolute;\n  mix-blend-mode: difference;\n  width: 100%;\n  text-align: center;\n  top: 50%;\n  color: #fff;\n`;

                graph.style.background = t =
                    `radial-gradient(#0000 ${innerRatio - 2}px, #fff ${innerRatio}px, #fff ${outerSize / 2 - 2}px, #0000 ${outerSize / 2}px)`;

                css.circle += `  background:\n    ${t};\n`;

                c.style.background = t =
                    `radial-gradient(#fff ${innerRatio - 2}px, #0000 ${innerRatio}px, #0000 ${outerSize / 2 - 2}px, #fff ${outerSize / 2}px), conic-gradient(${color1} ${deg}deg, ${color0} 0)`;

                css.graph += `  background:\n    ${t.replace(/(\) *, *)/, '$1\n    ')};\n`;

                c2.textContent = `${percent.toFixed(1)}%`;
                c2.style.fontSize = t = `${(outerSize / 8).toFixed(1)}px`;

                css.percent += `  font-size: ${t};\n`;

                document.getElementById('css').value = `.circle {\n${css.circle}}\n.circle .graph {\n${css.graph}}\n.circle .percent {\n${css.percent}}\n`;
            }
        </script>
    </body>
</html>
```