Divide semi circle with stroke-dasharray and on active color fill

孤者浪人 提交于 2021-02-12 11:17:08

问题


I'm working on an SVG gauge meter here I want to divide the semi-circle into parts when the range slider move on active stroke fill with gradient color. And want to add one more black color running track when the meter needle moves. I had tried using stroke-dasharray but after adding this all colors are coming at a time meter is not working properly.

  1. I want to divide the semi-circle into parts when the range slider move on active stroke fill with gradient color
  2. And I want to add one more black color running track when the meter needle moves.

My code here

/* set radius for all circles */
var r = 400;
var circles = document.querySelectorAll('.circle');
var total_circles = circles.length;
for (var i = 0; i < total_circles; i++) {
  circles[i].setAttribute('r', r);
}
/* set meter's wrapper dimension */
var meter_dimension = (r * 2) + 100;
var wrapper = document.querySelector('#wrapper');
wrapper.style.width = meter_dimension + 'px';
wrapper.style.height = meter_dimension + 'px';
/* add strokes to circles  */
var cf = 2 * Math.PI * r;
var semi_cf = cf / 2;
var semi_cf_1by3 = semi_cf / 3;
var semi_cf_2by3 = semi_cf_1by3 * 2;

document.querySelector('#outline_curves').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
document.querySelector('#high').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
document.querySelector('#avg').setAttribute('stroke-dasharray', semi_cf_2by3 + ',' + cf);
document.querySelector('#low').setAttribute('stroke-dasharray', semi_cf_1by3 + ',' + cf);
document.querySelector('#outline_ends').setAttribute('stroke-dasharray', 2 + ',' + (semi_cf - 2));
document.querySelector('#mask').setAttribute('stroke-dasharray', semi_cf + ',' + cf);
/*bind range slider event*/
var slider = document.querySelector('#slider');
var lbl = document.querySelector("#lbl");
var mask = document.querySelector('#mask');
var meter_needle = document.querySelector('#meter_needle');

function range_change_event() {
  var percent = slider.value
  var meter_value = semi_cf - ((percent * semi_cf) / 100);
  mask.setAttribute('stroke-dasharray', meter_value + ',' + cf);
  meter_needle.style.transform = 'rotate(' + (450 - ((percent * 180) / 100)) + 'deg)';
  lbl.textContent = percent + '%';
}
slider.addEventListener('input', range_change_event);
#wrapper {
  position: relative;
  margin: auto;
}

#meter {
  width: 100%;
  height: 100%;
  transform: rotate(180deg);
}

.circle {
  fill: none;
}

.outline,
#mask {
  stroke: #F1F1F1;
  stroke-width: 65;
}

.range {
  stroke-width: 60;
}

#slider,
#lbl {
  position: absolute;
}

#slider {
  position: absolute;
  transform: rotate(180deg);
  cursor: pointer;
  left: 0;
  margin: auto;
  right: 0;
  top: 58%;
  width: 94%;
}

#lbl {
  background-color: #4B4C51;
  border-radius: 2px;
  color: white;
  font-family: 'courier new';
  font-size: 15pt;
  font-weight: bold;
  padding: 4px 4px 2px 4px;
  right: -48px;
  top: 57%;
}

#meter_needle {
  height: 40%;
  left: 0;
  margin: auto;
  position: absolute;
  right: 0;
  top: 10%;
  transform-origin: bottom center;
  /*orientation fix*/
  transform: rotate(450deg);
  width: 5px;
  background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<body>
  <div id="wrapper">
    <svg id="meter">
        <circle id="outline_curves" class="circle outline"  cx="50%" cy="50%">
        </circle>
        <circle id="high" class="circle range" cx="50%" cy="50%" stroke="#FDE47F">
        </circle>
        <circle id="avg" class="circle range" cx="50%" cy="50%" stroke="#7CCCE5">
        </circle>
        <circle id="low" class="circle range" cx="50%" cy="50%" stroke="#E04644">
        </circle>
    
        <circle id="mask" class="circle" cx="50%" cy="50%" >
        </circle>
        <circle id="outline_ends" class="circle outline" cx="50%" cy="50%">
        </circle>
    </svg>
    <svg version="1.1" id="meter_needle" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16.721px" height="93.834px" viewBox="0 0 16.721 93.834" enable-background="new 0 0 16.721 93.834" xml:space="preserve">
            <path fill="#464646" d="M13.886,84.243L2.83,83.875c0,0,3.648-70.77,3.956-74.981C7.104,4.562,7.832,0,8.528,0
                c0.695,0,1.752,4.268,2.053,8.894C10.883,13.521,13.886,84.243,13.886,84.243z"/>
            <path fill="#464646" d="M16.721,85.475c0,4.615-3.743,8.359-8.36,8.359S0,90.09,0,85.475c0-4.62,3.743-8.363,8.36-8.363
                S16.721,80.855,16.721,85.475z"/>
            <circle fill="#EEEEEE" cx="8.426" cy="85.471" r="2.691"/>
</svg>
    <input id="slider" type="range" min="25" max="100" step="25" value="0" />
    <label id="lbl" id="value" for="">0%</label>
  </div>
  <script src="script.js"></script>
</body>

Can anyone suggest to me how to achieve this output. Any help will be appreciate.


回答1:


Here's a working SVG example, with a little bit of javascript.

It's not clear from your question what exactly you want the coloured divisions to look like. For now I've just made them solid red. But you can make them look however you'd like by changing the fill of the <rect id="meter-colours" .../>.

let METER_DIVISIONS = 10;
let METER_DIVISIONS_GAP = 4;

// Initialise meter
let meterDivisionsElem = document.getElementById("meter-divisions");
// Calculate size of each meter division
let totalLen = meterDivisionsElem.getAttribute("r") * Math.PI;
let numGaps = METER_DIVISIONS - 1;
let divLen = (totalLen - METER_DIVISIONS_GAP * numGaps) / METER_DIVISIONS;
// Set meter divisions dash pattern
let pat = divLen + ' ' + METER_DIVISIONS_GAP + ' ';
meterDivisionsElem.setAttribute("stroke-dasharray", pat.repeat(numGaps) + ' ' + divLen + ' ' + totalLen);

// The slider form element
let sliderElem = document.getElementById("slider");
// The slider label element
let sliderLabelElem = document.getElementById("lbl");
// The needle element
let needleElem = document.getElementById("needle");
// The black bar element
let blackBarElem = document.getElementById("black-bar");
let blackBarLen = blackBarElem.getAttribute("r") * Math.PI;
// The meter colours element
let meterColoursElem = document.getElementById("meter-colours");

function setMeterTo(value)
{
  // Update slider label
  sliderLabelElem.textContent = value + '%';

  // Update needle position
  let rotation = value * 180 / 100;
  needleElem.setAttribute("transform", "rotate(" + rotation + ")");

  // Update black bar length
  let barLen = value * blackBarLen / 100;
  blackBarElem.setAttribute("stroke-dasharray", barLen + ' ' + (2 * blackBarLen));

  // Update coloured division section of the meter
  meterColoursElem.setAttribute("transform", "rotate(" + rotation + ")");
}

// Add an input change handler to the slider
sliderElem.addEventListener("input", function(evt) {
  setMeterTo(evt.target.value);
});

// Initialise meter to start value
setMeterTo(25);
#wrapper {
  width: 600px;
}

#slider {
  width: 100%;
}

#needle {
  stroke-linecap: round;
}
<div id="wrapper">
  <svg id="meter" viewBox="0 0 200 110">
    <defs>
      <circle id="meter-divisions" r="90" stroke-width="20" stroke-dasharray="282.7"/>
      
      <mask id="meter-divisions-mask">
        <use xlink:href="#meter-divisions" stroke="white"/>
      </mask>
    </defs>
    <g transform="translate(100,100) scale(1,-1)" fill="none">
      <!-- The grey meter divisions -->
      <use xlink:href="#meter-divisions" stroke="lightgrey"/>
      <!-- The inner semicircular black bar -->
      <circle id="black-bar" r="74" stroke="black" stroke-width="4" stroke-dasharray="232.5"/>
      <!-- the needle -->
      <line id="needle" x2="70" y2="0" stroke="black" stroke-width="2" stroke-dasharray="282.7"/>
      <!-- The coloured form of the ,meter divisions that gets rotated into view with the needle.
           It is initially off screen but gets revealed as it rotates. It is masked by using
           a shape matching the gray divisions -->
      <g mask="url(#meter-divisions-mask">
        <rect id="meter-colours" x="-100" y="-100" width="200" height="100" fill="red"/>
      </g>
    </g>
  </svg>

  <input id="slider" type="range" min="25" max="100" step="25" value="25" />
  <label id="lbl" id="value" for="">0%</label>
</div>



回答2:


My approach here is a pure CSS one ,i didn't use SVG and implementing JS code in this case is lot easier.

Output

var black = document.getElementsByClassName('black')[0];
var gradient = document.getElementsByClassName('gradient')[0]
var tick = document.getElementsByClassName('tick')[0];
var m = document.getElementById('m');
m.addEventListener('input', function() {
  black.style.transform = gradient.style.transform = tick.style.transform = "translate(-50%,-50%)rotateZ(-" + (180 / 100) * m.value + "deg)"
});
* {
  margin: 0px;
  padding: 0px;
  font-family: "arial";
}

.gauge_main,
.white,
.black,
.tick,
.gradient,
.chamber,
.meter {
  position: absolute;
  top: 50%;
  left: 50%;
  height: 280px;
  width: 280px;
  transform: translate(-50%, -50%);
  transition: 0.4s;
}

.meter {
  background-color: white;
  height: 281px;
  width: 281px;
  border-radius: 50%;
  clip-path: polygon(0 50%, 100% 50%, 100% 100%, 0 100%);
  z-index: 1000;
}

.tick,
.chamber {
  width: 160px;
  height: 5px;
  background: linear-gradient(to right, white 50%, black 50%);
  z-index: 110;
  transform-origin: 50%;
}

.tick {
  z-index: 1001;
}

.chamber {
  left: 50%;
  background: white;
  width: 280px;
  height: 5px;
  transform-origin: 50%;
  transform: translate(-50%, -50%)rotateZ(calc(var(--i)*-20deg));
  z-index: 20;
}

.white,
.black,
.gradient {
  position: absolute;
  background-color: black;
  border-radius: 50%;
}

.white {
  height: 180px;
  width: 180px;
  background-color: white;
  z-index: 30;
}

.gradient {
  height: 280px;
  width: 280px;
  background: linear-gradient(0deg, rgba(29, 216, 255, 1) 0%, rgba(4, 106, 255, 1) 50%, rgba(208, 212, 255, 1) 50%);
}

.black {
  height: 200px;
  width: 200px;
  background: linear-gradient(to top, black 50%, rgba(208, 212, 255, 1) 50%);
  box-shadow: 0px 0px 0px 5px white;
  z-index: 25;
}

@keyframes load {
  from {
    transform: translate(-50%, -50%)rotateZ(0deg);
  }
  to {
    transform: translate(-50%, -50%)rotateZ(-180deg);
  }
}

input {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 280px;
  transform: translate(-50%, 50%);
}
<div class="gauge_main">
  <div class="gradient"></div>
  <div class="white"></div>
  <div class="black"></div>
  <div class="tick"></div>
  <div style="--i:1" class="chamber"></div>
  <div style="--i:2" class="chamber"></div>
  <div style="--i:3" class="chamber"></div>
  <div style="--i:4" class="chamber"></div>
  <div style="--i:5" class="chamber"></div>
  <div style="--i:6" class="chamber"></div>
  <div style="--i:7" class="chamber"></div>
  <div style="--i:8" class="chamber"></div>
  <div style="--i:9" class="chamber"></div>
  <div class="meter"></div>

</div>
<input type="range" id="m" name="meter" min="0" max="100" value="0">


来源:https://stackoverflow.com/questions/66025363/divide-semi-circle-with-stroke-dasharray-and-on-active-color-fill

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!