Помощь в реализации ползунка на svg

210
12 мая 2019, 20:30

нужна помощь в реализации элемента интерфейса. Решил я реализовать 1 элемент, скрин его вы увидите ниже, и что-то понятия не имею как такое реализуется. Начал писать на и в принципе что-то получилось, но далекое от идеала. Итак, нужно чтобы в месте где стоит синий ползунок и пересекаются обе линии, стоял ползунок способный двигать их, т.е. если повернуть им влево, то красная полоса уменьшиться, а если вправо, то уменьшиться зеленая, и увеличится другая, думаю вы поняли, и мне этот момент совершенно непонятен, как такое можно реализовать? Объединить 2 ? Но как это сделать, чтобы на них можно было воздействовать ползунком?

Если это можно как-то реализовать не прибегая к svg буду очень рад помощи, если в примере будет js и вам будет не сложно, то очень хотелось бы увидеть нативный, т.к. изучаю его параллельно.

Мой код:

.grey-cricle { 
	-webkit-transform: rotate(-85deg) translate(-282px, 16px); 
	-ms-transform: rotate(-85deg) translate(-282px, 16px); 
	-o-transform: rotate(-85deg) translate(-282px, 16px); 
	transform: rotate(-85deg) translate(-282px, 16px); 
} 
.new-game { 
	width: 400px; 
}
<svg  viewBox="0 0 330 275" class="new-game"> 
	<text x="135" y="20" font-family="Arial" font-size="10" text-anchor="middle" fill="#A9A8A8">100</text> 
	<text x="165" y="20" font-family="Arial" font-size="10" text-anchor="middle" fill="#A9A8A8">0</text> 
	<circle r="120" cx="150" cy="145" fill="none" stroke-width="3" stroke="#ccc" stroke-dasharray="740" class="grey-cricle"  /> 
	<circle r="100" cx="150" cy="145" fill="none" stroke-width="14" stroke="#ccc" /> 
	<circle r="100" cx="150" cy="145" stroke-width="8" stroke="red"  stroke-dasharray="250 502"  
					 stroke-dashoffset="0" fill="none" stroke-linecap="butt"/> 
	<circle r="30" cx="150" cy="145" fill="#006EFA" stroke="url(#gradient-game)" /> 
	<text x="150" y="150" fill="#fff" font-size="16" text-anchor="middle">99.99</text> 
</svg>

Answer 1

На мой взгляд такие вещи нужно писать на фреймворках, но можно и на ванильном JS. Остались мелкие баги, но они устраняются уже элементарно и больше подвязаны к разметке svg.

У нас есть 5 эвентов: на нажатие клавиши(только движок), на отжатие клавиши (вся область svg), покидание мышью области svg, и движение мыши.

При клике мы вычисляем абсолютный центр надписи (нужно для определения угла).

При движении мыши мы определяем текущий угол поворота регулятора и отрисовываем его в svg.

Сделал еще параллельный event для обработки скрола мышкой или тачпадом - тоже удобно пользователю.

Если есть вопросы по коду - спрашивайте.

function bind(func, context) { 
  return function() { // (*) 
    return func.apply(context, arguments); 
  }; 
} 
 
function getPos(el) { 
  var rect = el.getBoundingClientRect(); 
  console.log(rect); 
  return { 
    x: rect.left + rect.width / 2.0, 
    y: rect.top + rect.height / 2.0 
  }; 
} 
 
var radius = parseFloat(stroker.getAttribute("r")); 
 
var curObj = { 
  draggin: false, 
  dragX: 0, 
  dragY: 0, 
  cent: { 
    x: 0, 
    y: 0 
  }, 
  onMDTasker: function(evt) { 
    this.draggin = true; 
    this.cent = getPos(this.text); 
    console.log(this.cent); 
    this.dragX = evt.clientX; 
    this.dragY = evt.clientY; 
    this.dragFi = 2 * Math.PI / 100.0 * this.value * 0.98; 
    return false; 
  }, 
  onMUTasker: function(evt) { 
    this.draggin = false; 
    return false; 
  }, 
  onMMTasker: function(evt) { 
    if (this.draggin) { 
      var curX = evt.clientX; 
      var curY = evt.clientY; 
 
      var deltaX = curX - this.cent.x; 
      var deltaY = curY - this.cent.y; 
 
      var Ksi = Math.atan2(deltaY, deltaX); 
      var correctedKsi = Ksi + Math.PI / 2.0 * 0.98; 
      if (correctedKsi < 0) { correctedKsi = correctedKsi + 2 * Math.PI; } 
      var newVal = correctedKsi / 2 / Math.PI * 100.0 / 0.98; 
      if (newVal < 0) { newVal = 0.0; } 
      if (newVal > 100) { newVal = 100.0; } 
      this.value = newVal; 
      this.applyOutState(); 
    } 
    return false; 
  }, 
  onWheelEvent: function(evt) { 
    var deltaY = evt.deltaY; 
    var oldValue = parseFloat(this.text.innerHTML); 
    var newValue = oldValue + deltaY / 10.0; 
    if (newValue < 0) { newValue = 0.0; } 
    if (newValue > 100) { newValue = 100.0; } 
    this.value = newValue; 
    this.applyOutState(); 
    return false; 
  }, 
  svgE: document.getElementsByClassName("new-game")[0], 
  value: 50.00, 
  circLen: radius * 2 * Math.PI, 
  applyOutState: function() { 
    this.text.innerHTML = this.value.toFixed(2); 
    var fi = 2 * Math.PI / 100.0 * this.value * 0.98; 
    var circleArcLen = fi * radius; 
    var empteArcLen = this.circLen - circleArcLen; 
    this.stroker.setAttribute("stroke-dashoffset", "148"); 
    this.stroker.setAttribute("stroke-dasharray", circleArcLen + " " + empteArcLen); 
    this.tasker.setAttribute("cx", 150 + 95 * Math.cos(fi - Math.PI / 2.0 * 0.98)) 
    this.tasker.setAttribute("cy", 150 + 95 * Math.sin(fi - Math.PI / 2.0 * 0.98)) 
  } 
} 
 
curObj.onWheelEvent = bind(curObj.onWheelEvent, curObj); 
curObj.onMUTasker = bind(curObj.onMUTasker, curObj); 
curObj.onMDTasker = bind(curObj.onMDTasker, curObj); 
curObj.onMMTasker = bind(curObj.onMMTasker, curObj); 
curObj.svgE.addEventListener("wheel", curObj.onWheelEvent); 
curObj.text = document.getElementById("textVal"); 
curObj.stroker = document.getElementById("stroker"); 
curObj.tasker = document.getElementById("tasker"); 
curObj.tasker.addEventListener("mousedown", curObj.onMDTasker); 
curObj.svgE.addEventListener("mouseup", curObj.onMUTasker); 
curObj.svgE.addEventListener("mousemove", curObj.onMMTasker); 
curObj.svgE.addEventListener("mouseleave", curObj.onMUTasker); 
curObj.applyOutState();
<svg viewBox="0 0 330 275" class="new-game"> 
  <text x="135" y="20" font-family="Arial" font-size="10" text-anchor="middle" fill="#A9A8A8">100</text> 
  <text x="165" y="20" font-family="Arial" font-size="10" text-anchor="middle" fill="#A9A8A8">0</text> 
  <circle r="120" cx="150" cy="145" fill="none" stroke-width="3" stroke="#ccc" stroke-dasharray="740" class="grey-cricle" /> 
  <circle r="100" cx="150" cy="145" fill="none" stroke-width="14" stroke="#ccc" /> 
  <circle r="100" cx="150" cy="145" stroke-width="8" stroke="green" stroke-dasharray="614 14" stroke-dashoffset="148" fill="none" stroke-linecap="butt" /> 
  <circle id="stroker" r="100" cx="150" cy="145" stroke-width="8" stroke="red" stroke-dasharray="250 502" stroke-dashoffset="0" fill="none" stroke-linecap="butt" /> 
  <circle r="30" cx="150" cy="145" fill="#006EFA" stroke="url(#gradient-game)" /> 
  <text x="150" y="150" fill="#fff" font-size="16" text-anchor="middle" id="textVal">99.99</text> 
  <circle id="tasker" r="10" cx="200" cy="200" fill="#0000aa" stroke="gold" stroke-width="2" /> 
</svg>

Answer 2

Используетсья плагин RoundSlider

Цвета правильные не смог подобрать но думаю такого примера впольне хватит.

let fn1 = $.fn.roundSlider.prototype._setProperties; 
$.fn.roundSlider.prototype._setProperties = function () { 
  fn1.apply(this); 
   
  let o = this.options, r = o.radius, d = r * 2, 
      r1 = r - (o.width / 2) - this._border(true), 
      svgNS = "http://www.w3.org/2000/svg"; 
       
  this._circum = Math.PI * (r1 * 2); 
  let $svg = $(document.createElementNS(svgNS, "svg")); 
  $svg.attr({ "height": d, "width": d }); 
   
  this.$circle = $(document.createElementNS(svgNS, 'circle')).attr({ 
    "fill": "transparent", "class": "rs-transition",  
    "cx": r,  
    "cy": r,  
    "r": r1, 
    "stroke-width": o.width - 2,  
    "stroke-dasharray": this._circum 
  }); 
  let $path = this.$circle.clone().addClass("path-bg"); 
  this._setDashOffset($path, this._end); 
   
  // ####---- Добавление border ----#### 
  let $border = this.$circle.clone().addClass("path-border").attr({ 
  	"stroke-width": o.width 
  }); 
  this._setDashOffset($border, this._end + 1.5); 
  let $border_wrapper = $(document.createElementNS(svgNS, 'g')).css({ 
    "transform-origin": "50% 50%", 
    "transform": "rotate(-1deg)" 
  }); 
  $border_wrapper.append($border); 
   
	$svg.append($border_wrapper, $path, this.$circle.addClass("range-bg")); 
   
  this.$svg_box = $(document.createElement("div")).addClass("rs-transition      rs-svg").append($svg).css({ 
    "height": d,  
    "width": d,  
    "transform-origin": "50% 50%", 
    "transform": "rotate(" + (o.startAngle + 180) + "deg)" 
  }).appendTo(this.innerContainer); 
} 
 
$.fn.roundSlider.prototype._setDashOffset = function ($ele, deg) { 
	let pct = (1 - (deg / 360)) * this._circum; 
  $ele.css({ strokeDashoffset: pct }); 
} 
let fn2 = $.fn.roundSlider.prototype._changeSliderValue; 
$.fn.roundSlider.prototype._changeSliderValue = function (val, deg) { 
  fn2.apply(this, arguments); 
  deg = deg - this.options.startAngle; 
 
  if (this._rangeSlider) { 
    this.$svg_box.rsRotate(this._handle1.angle + 180); 
    deg = this._handle2.angle - this._handle1.angle; 
  } 
  this._setDashOffset(this.$circle, deg); 
} 
/// настройки самого плагина /// 
 
$("#slider").roundSlider({ 
	sliderType: "min-range", 
  handleShape: "dot", 
  radius: 110, 
  startAngle: 95, 
  endAngle: "+350", 
  min: 3, 
  max: 97, 
  width: 16, 
  handleSize: "+10", 
    create: function (event) { 
    this.control.find(".rs-handle").addClass("rs-transition").eq(0).rsRotate(-this._handle1.angle); 
  } 
}).on("change drag", function (event) { 
	$(event.handle.element).rsRotate(-event.handle.angle); 
}); 
 
 
$("#slider").roundSlider("option", "value", 50);
.rs-border { 
  border: none !important; 
} 
.rs-control .rs-range-color, 
.rs-control .rs-path-color, 
.rs-control .rs-bg-color { 
  background-color: transparent; 
} 
.rs-control circle.path-bg { 
  stroke: red; 
} 
.rs-control circle.range-bg { 
  stroke: #81ce00; 
} 
.rs-control circle.path-border { 
  stroke: #aaa; 
} 
.full .rs-tooltip { 
    top: 50%; 
    left: 50%; 
    background: skyblue !important; 
    color:#fff !important; 
    border-radius:50%; 
} 
.rs-handle-dot:after { 
     display: flex !important; 
    content: "\f104 \f105" !important; 
    font-family: FontAwesome !important; 
    height: 100% !important; 
    justify-content: space-around; 
    padding: 5px !important; 
    align-items: center !important; 
    width: 100% !important; 
    border-radius: 1000px; 
    color: #fff !important; 
} 
.rs-handle-dot { 
    background-color: skyblue !important; 
} 
.rs-handle-dot:after { 
    background-color: skyblue !important; 
    border:none !important; 
} 
.rs-handle-dot { 
    border: none !important; 
    padding: 0px !important; 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.js"></script> 
<link href="https://cdnjs.cloudflare.com/ajax/libs/roundSlider/1.3.2/roundslider.min.css" rel="stylesheet"/> 
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet"/> 
<div id="slider"></div>

READ ALSO
Как использовать scrollTop?

Как использовать scrollTop?

Как использовать scrollTop,? Объясните принцип работы пожалуйста

233
Тайминг анимации css

Тайминг анимации css

Есть 4 текста и 8 картинокПо одному тексту на 2 картинки

227
Можно ли вставить в поле input file картинку с этой же html стр не скачивая ее?

Можно ли вставить в поле input file картинку с этой же html стр не скачивая ее?

Можно ли вставить в поле input file картинку с этой же html стр не скачивая ее?

179