Best way to add conic gradient to the circle in SVG

1.2k Views Asked by At

I have a donut-like circle (progress circle) in React component and I want to add conic gradient to it. How to do that?

I know that in SVG we can not use conic gradients. I thought it can be done by using mask and usual block with added css with gradient but not sure how to do it correctly.

Now it looks like this:

Image

React component:

import React from 'react';

import { Box, Text } from '@chakra-ui/react';

const GradientProgress = ({ modifier, score, size, strokeWidth }) => {
  const DIAMETER = 51;
  const WIDTH = DIAMETER + strokeWidth;

  const RADIUS = DIAMETER / 2;
  const CIRC = 2 * Math.PI * RADIUS;

  const foregroundCirc = (CIRC * score) / 100;
  const frontCirc = (CIRC * modifier) / 100;

  return (
    <Box
      position='relative'
      style={{ width: `${size}px`, height: `${size}px` }}
      sx={{
        circle: {
          background:
            'conic-gradient(from 270deg, #ff4800 10%, #dfd902 35%, #20dc68, #0092f4, #da54d8 72% 75%, #ff4800 95%)',
        },
      }}
    >
      <svg
        className='donut'
        transform='rotate(-90)'
        viewBox={`0 0 ${WIDTH} ${WIDTH}`}
      >
        <circle
          className='donut-ring'
          cx={RADIUS + strokeWidth / 2}
          cy={RADIUS + strokeWidth / 2}
          fill='transparent'
          pathLength={CIRC}
          r={RADIUS}
          stroke='#d2d3d4'
          strokeWidth={strokeWidth}
        />

        <circle
          className='donut-segment'
          cx={RADIUS + strokeWidth / 2}
          cy={RADIUS + strokeWidth / 2}
          fill='transparent'
          opacity={0.5}
          pathLength={CIRC}
          r={RADIUS}
          stroke='green'
          strokeDasharray={`${frontCirc} ${CIRC - frontCirc}`}
          strokeDashoffset={0}
          strokeLinecap='round'
          strokeWidth={strokeWidth}
        />

        <circle
          className='donut-segment'
          cx={RADIUS + strokeWidth / 2}
          cy={RADIUS + strokeWidth / 2}
          fill='transparent'
          pathLength={CIRC}
          r={RADIUS}
          stroke='red'
          strokeDasharray={`${foregroundCirc} ${CIRC - foregroundCirc}`}
          strokeDashoffset={0}
          strokeLinecap='round'
          strokeWidth={strokeWidth}
        />
      </svg>
      <Text>{modifier || score}</Text>
    </Box>
  );
};

export default GradientProgress;
1

There are 1 best solutions below

0
On

You can achieve this by converting your circle to a mask and assign it to a foreignObject, this object contains a div with the conic-gradient style.

Here is an example how it works:

const control = document.getElementById('control');
const circle = document.getElementsByClassName('circle')[0];
const bg = document.getElementsByClassName('bg')[0];

control.addEventListener('input', function(event) {
  circle.style.setProperty('--progress', event.target.valueAsNumber);
  const deg = (event.target.valueAsNumber/100) * 360;
  bg.style.setProperty('background', `conic-gradient(#00bcd4, #ffeb3b ${deg}deg)`);
});
.root {
  width: 400px;
  height: 400px;
  text-align: center;
}

svg {

}

.circle {
  stroke: white;
  stroke-width: 3;
  stroke-linecap: round;
  stroke-dasharray: calc((2 * 3.14) * 45);
  stroke-dashoffset: calc((2 * 3.14 * 45) * (1 - calc(var(--progress, 50) / 100)));
  transform-origin: 50% 50%;
  transform: rotate(-87deg);
}

.bg {
  background: conic-gradient(#00bcd4, #ffeb3b 180deg);
  width: 100%;
  height: 100%;
}
<div class='wrap'>

  <div class='root'>
    <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <mask id="mask">
          <circle class='circle' cx="50" cy="50" r="45" stroke='white' stroke-width='3' fill='none' />
        </mask>
      </defs>
      <foreignObject x="0" y="0" width="100" height="100" mask="url(#mask)">
        <div class='bg'></div>
      </foreignObject>
    </svg>
    <input id="control" type="range" value="60" />
  </div>

</div>