Search code examples
htmlcssborderlinear-gradients

How to create a curved gauge having linear-gradient as background?


I want to make a 0-100% responsive gauge with pure css, with a color gradient from green to red. I found some examples but couldn't get a gradient working right in any of them. After some testing I managed to get it kinda working. It's basically a background div with a linear gradient background, and a white foreground div with half transparent borders, using border-radius to make them round. When I rotate the foreground div it reveals or obscure parts of the gradient background div.

But I have this visual glitch that I don't know how to fix:

CSS gauge with gradient

The white borders of the foreground div are not completely obscuring the gradient div.

This is my test code (it may contain unnecessary css rules from all the previous tests I did):

https://jsfiddle.net/fLtzrg3w/

HTML:

  <div class="c">
    <div class="go">
      <div class="g"></div>
      <div class="gbg"></div>
    </div>
  </div>

CSS:

.c{
  position: relative;
  float:left;
  text-align: center;
  width: 50%;
  padding: 25% 5px 0 5px;
  height: 1rem;
  overflow:hidden;
}

.go{
  position: relative;
  width: 100%;
  overflow: hidden;
  padding-top:100%;
  margin-top: -50%;
}

.g{
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  border-radius: 50%;
  box-sizing: border-box;
  border: 40px solid transparent;
  border-bottom-color: #fff;
  border-right-color: #fff;
  transform: rotate(20deg);
  background: white;
  background-clip: padding-box;
  z-index: 2;
}

.gbg{
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  border-radius: 50%;
  box-sizing: border-box;
  background: linear-gradient(to right, green 0%, yellow 50%, red 100%);
  z-index: 1;
}

How can I make the white div completely cover the background gradient div?


Solution

  • I would do this differently using multiple background:

    .box {
      width:250px;
      border-radius:500px 500px 0 0;
      background:
         /* a linear gradient to control the progress. Adjust the angle from 0deg to 180deg*/
         linear-gradient(160deg,transparent 50%,#fff 0) top/100% 200%,
         /* a radial gradient to show only a part of the gradient (20px here)*/
         radial-gradient(farthest-side at bottom,#fff calc(100% - 20px),transparent 0),
         /* the main gradient */
         linear-gradient(to right, green , yellow , red);
    }
    .box::before {
      content:"";
      display:block;
      padding-top:50%;
    }
    <div class="box"></div>

    That you can optimize with CSS variables:

    .box {
      --p:160deg;
      --b:20px;
      
      width:250px;
      display:inline-block;
      border-radius:500px 500px 0 0;
      background:
         /* a linear gradient to control the progress. Adjust the angle from 0deg to 180deg*/
         linear-gradient(var(--p),transparent 50%,#fff 0) top/100% 200%,
         /* a radial gradient to show only a part of the gradient (20px here)*/
         radial-gradient(farthest-side at bottom,#fff calc(100% - var(--b) - 1px),transparent calc(100% - var(--b))),
         /* the main gradient */
         linear-gradient(to right, green , yellow , red);
    }
    .box::before {
      content:"";
      display:block;
      padding-top:50%;
    }
    <div class="box"></div>
    <div class="box" style="--b:30px;--p:90deg"></div>
    <div class="box" style="--b:10px;--p:40deg"></div>

    Another syntax:

    .box {
      --p:160deg;
      --b:20px;
      
      width:250px;
      display:inline-block;
      border-radius:500px 500px 0 0;
      padding:var(--b) var(--b) 0;
      background:
         linear-gradient(var(--p),transparent 50%,#fff 0) top/100% 200%,
         linear-gradient(#fff,#fff) content-box,
         linear-gradient(to right, green , yellow , red);
    }
    .box::before {
      content:"";
      display:block;
      padding-top:50%;
    }
    <div class="box"></div>
    <div class="box" style="--b:30px;--p:90deg"></div>
    <div class="box" style="--b:10px;--p:40deg"></div>

    And using mask to have transparency:

    .box {
      --p:160deg;
      --b:20px;
      
      width:250px;
      display:inline-block;
      border-radius:500px 500px 0 0;
      background: linear-gradient(to right, green , yellow , red);
      -webkit-mask:
         radial-gradient(farthest-side at bottom,transparent calc(100% - var(--b) - 1px),#fff calc(100% - var(--b))),
         linear-gradient(var(--p),#fff 50%,transparent 0) top/100% 200%;
      -webkit-mask-composite:destination-in;
              mask-composite:intersect;
    }
    .box::before {
      content:"";
      display:block;
      padding-top:50%;
    }
    
    body {
      background:#f3f3f3;
    }
    <div class="box"></div>
    <div class="box" style="--b:30px;--p:90deg"></div>
    <div class="box" style="--b:10px;--p:40deg"></div>