css 用于范围输入的SVG弧形滑块

vltsax25  于 2022-12-24  发布在  其他
关注(0)|答案(3)|浏览(159)

我的目标是设计一个弧形滑块,看起来像这样

我的模板结构如下

<svg width="500" height="300">
  <path id="track" stroke="lightgrey" fill="transparent" stroke-width="20" d="
     M 50 50
     A 90 90 0 0 0 300 50
  "/>
  <path id="trackFill" fill="cyan" stroke-width="20" d="
     M 50 50
     A 90 90 0 0 0 [some dynamic value?] [some dynamic value?]
  "/>
  <circle id="knob" fill="lightblue"  cx="[dynamic, initial - 50]" cy="[dynamic, initial - 50]" r="25"/>
</svg>

knob-用户应该拖动以更改值的控件
track-幻灯片的完整弧
trackFill-旋钮之前的滑块路径部分
当旋钮沿着滑块曲线拖动时,是否可以让trackFill覆盖旋钮之前的滑块部分?如果可以,哪些API或CSS规则可以帮助我实现这样的结果?

j8ag8udp

j8ag8udp1#

你在找这样的东西吗?

let svg = document.getElementById("slider");
let trackFill = document.getElementById("trackFill");
let knob = document.getElementById("knob");
let isDragging = false;
let sliderDragOffset = {dx: 0, dy: 0};
let ARC_CENTRE = {x: 175, y: 50};
let ARC_RADIUS = 125;

let sliderValue = 0;
setSliderValue(sliderValue);

function setSliderValue(value)
{
  // Limit value to (0..sliderMax)
  let sliderMax = track.getTotalLength();
  sliderValue = Math.max(0, Math.min(value, sliderMax));
  // Calculate new position of knob
  let knobRotation = sliderValue * Math.PI / sliderMax;
  let knobX = ARC_CENTRE.x - Math.cos(knobRotation) * ARC_RADIUS;
  let knobY = ARC_CENTRE.y + Math.sin(knobRotation) * ARC_RADIUS;
  // Adjust trackFill dash patter to only draw the portion up to the knob position
  trackFill.setAttribute("stroke-dasharray", sliderValue + " " + sliderMax);
  // Update the knob position
  knob.setAttribute("cx", knobX);
  knob.setAttribute("cy", knobY);
}

knob.addEventListener("mousedown", evt => {
  isDragging = true;
  // Remember where we clicked on knob in order to allow accurate dragging
  sliderDragOffset.dx = evt.offsetX - knob.cx.baseVal.value;
  sliderDragOffset.dy = evt.offsetY - knob.cy.baseVal.value;
  // Attach move event to svg, so that it works if you move outside knob circle
  svg.addEventListener("mousemove", knobMove);
  // Attach move event to window, so that it works if you move outside svg
  window.addEventListener("mouseup", knobRelease);
});

function knobMove(evt)
{
  // Calculate adjusted drag position
  let x = evt.offsetX + sliderDragOffset.dx;
  let y = evt.offsetY + sliderDragOffset.dy;
  // Position relative to centre of slider arc
  x -= ARC_CENTRE.x;
  y -= ARC_CENTRE.y;
  // Get angle of drag position relative to slider centre
  let angle = Math.atan2(y, -x);
  // Positions above arc centre will be negative, so handle them gracefully
  // by clamping angle to the nearest end of the arc
  angle = (angle < -Math.PI / 2) ? Math.PI : (angle < 0) ? 0 : angle;
  // Calculate new slider value from this angle (sliderMaxLength * angle / 180deg)
  setSliderValue(angle * track.getTotalLength() / Math.PI);
}

function knobRelease(evt)
{
  // Cancel event handlers
  svg.removeEventListener("mousemove", knobMove);
  window.removeEventListener("mouseup", knobRelease);
  isDragging = false;
}
<svg id="slider" width="500" height="300">
  <g stroke="lightgrey">
    <path id="track" fill="transparent" stroke-width="20" d="
       M 50 50
       A 125 125 0 0 0 300 50
    "/>
  </g>
  <use id="trackFill" xlink:href="#track" stroke="cyan"/>
  <circle id="knob" fill="lightblue" cx="50" cy="50" r="25"/>
</svg>

为了清晰起见,我保持了这段代码的简单性,但代价是一些限制。
它假设每个页面只有一个滑块。如果你需要更多的滑块,你将不得不保持特定于滑块的值(如sliderValue和isDragging)分开。你可以使用数据属性。你还需要从通过id属性访问SVG元素切换到另一种方式(如class属性),因为id属性在页面上必须是唯一的。

juud5qan

juud5qan2#

下面是一个简单的例子:

const radius = 50;
const offsetX = 10;
const offsetY = 10;

// 0 <= pos <= 1
const setSliderPos = (svg, pos) => {
  const angle = Math.PI * pos;
  const x = offsetX + radius - Math.cos(angle) * radius;
  const y = offsetY + Math.sin(angle) * radius;

  svg.select('.knob').attr('cx', x).attr('cy', y);
  svg.select('.first').attr('d', `M ${offsetX},${offsetY} A ${radius},${radius} 0 0 0 ${x},${y}`);
  svg.select('.second').attr('d', `M ${x},${y} A ${radius},${radius} 0 0 0 ${offsetX + radius * 2},${offsetY}`);
}

setSliderPos(d3.select('#svg-1'), 0.3);
setSliderPos(d3.select('#svg-2'), 0.6);
setSliderPos(d3.select('#svg-3'), 1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="svg-1" width="150" height="80">
  <path class="first" stroke-width="5" stroke="lightblue" fill="none"/> 
  <path class="second" stroke-width="5" stroke="cyan" fill="none"/> 
  <circle class="knob" r="10" fill="lightblue"/>
</svg>
   
<svg id="svg-2" width="150" height="80">
  <path class="first" stroke-width="5" stroke="lightblue" fill="none"/> 
  <path class="second" stroke-width="5" stroke="cyan" fill="none"/> 
  <circle class="knob" r="10" fill="lightblue"/>
</svg>

<svg id="svg-3" width="150" height="80">
  <path class="first" stroke-width="5" stroke="lightblue" fill="none"/> 
  <path class="second" stroke-width="5" stroke="cyan" fill="none"/> 
  <circle class="knob" r="10" fill="lightblue"/>
</svg>
4nkexdtk

4nkexdtk3#

要标记进度,可以使用带有百分比的stroke-dasharray;例如

<g stroke="lightgrey">
  <path id="track" fill="transparent" stroke-width="20"
   stroke-dasharray="40% 60%"
   d="M 50 50 A 125 125 0 0 0 300 50"/>
</g>

这将显示40%的弧,隐藏60%的弧。
如果你需要使用两种颜色,例如整个弧线是灰色的,进度是黑色的,你需要使用两个相互重叠的弧线;底部的代码是您已经拥有的代码,顶部的代码有一个黑色笔划,并使用stroke-dasharray,如图所示。

相关问题