通过可拖动的SVG形状设置HTML输入值? (或画布)

时间:2012-06-20 21:57:09

标签: javascript html5 svg

我正在尝试整理一个用于设置定价的GUI 有三个类别以图形方式表示,以及两个我们想要设置买入/卖出价格的滑块。

作为一项练习,我将一些原型划分为here on jsFiddle 请快速浏览一下,它会比我的话曾经可以更有效地解释它 (请原谅我的JS ......还没有重构它)

由于我们的目标是让滑块在HTML输入中设置浮动,我倾向于SVG。

  1. 我的第一个请求只是对我选择推出SVG解决方案的同行评审...只是想确保没有一些我必然遇到的固有和痛苦的故障。我也愿意接受其他解决方案的争论。
  2. 第二个问题。有没有人有任何好的链接/示例发布,可以帮助我了解如何根据形状的位置设置输入值?
  3. (我预计该位置将代表定义的$$范围的百分比,因此我们可以推断出特定的$$值)

    非常感谢任何和所有帮助。

1 个答案:

答案 0 :(得分:0)

这是我制作的一个使用SVG设置四个输入的演示,反之亦然:
http://phrogz.net/svg/complex-plane-picker.xhtml

前两个输入是X和Y(实部和复杂分量);底部两个是极坐标(幅度和角度)。

那里可能存在的代码多于你需要的代码(当你靠近边缘/中间时自动缩放,绘制更新的轴刻度),这可能会使它作为演示而不是有用而更加混乱。

一般而言,您需要:

  • 在输入的keyupchangeinput事件中设置事件监听器,并更新SVG以进行相应匹配。
  • 添加拖动到SVG元素,并在拖动过程中计算相应的值并相应地为输入设置.value

作为提示,在拖动过程中不要尝试检测可拖动元素本身的mousemove。如果鼠标移到此外,您将停止获取拖动事件,它将无法跟上。相反,我通常使用这个逻辑:

  • mousedown上:
    1. 记录当前光标位置
    2. 在文档根目录
    3. 上注册mousemove处理程序
    4. 在文档根目录
    5. 上注册mouseup处理程序
  • mousemove上:
    1. 检测当前光标位置与拖动开始位置之间的屏幕空间增量,并使用此calculate the delta in the local space of the element being dragged更新其转换。
  • mouseup上:
    1. 取消注册mousemove处理程序。

以下是完整的源代码,万一我的网站出现故障:

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head> 
  <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
  <title>SVG Complex Plane Input</title>
  <style type="text/css" media="screen">
    html, body { background:#eee; margin:0 }
    p { margin:0.5em; text-align:center }
    .complex-plane-picker svg { width:200px; height:200px; display:block; margin:1em auto; stroke-linejoin:round; stroke-linecap:round; pointer-events:all; }
    .complex-plane-picker rect.bg { fill:#fff; }
    .complex-plane-picker .axes * { stroke:#ccc; fill:none }
    .complex-plane-picker .dot { fill:#090; fill-opacity:0.4; stroke:#000; stroke-opacity:0.6; cursor:move; }
    .complex-plane-picker input { width:6em; }
    .complex-plane-picker .labels { stroke:none; font-size:6px; font-family:'Verdana'; text-anchor:middle; alignment-baseline:middle; }
    .complex-plane-picker .scalers { pointer-events:all; }
  </style>
</head><body>
<p>Drag the dot to set the complex value.<br/> TODO: Hold down shift to affect only the magnitude. Hold down alt to affect only the angle. Hold down both to snap to the nearest real- or imaginary-only value.</p>
<div class="complex-plane-picker">
  <p class="rectangular"><input type="number" value="0" class='real' />+<input type="number" value="0" class='imaginary' />i</p>
  <svg viewBox="-50 -50 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full">
    <rect class="bg" x="-50" y="-50" width="100" height="100" />
    <g class="scalers">
      <rect x="-5" y="-5" width="10" height="10" fill="#ffc"/>
      <rect x="-45" y="-45" width="90" height="90" fill="none" stroke="#ffc" stroke-width="10" />
    </g>
    <g class="axes">
      <g>
        <line x1="-49" y1="0" x2="49" y2="0" />
        <polyline points="-44,5 -49,0 -44,-5" />
        <polyline points="44,5 49,0 44,-5" />
      </g>
      <g transform="rotate(90)">
        <line x1="-49" y1="0" x2="49" y2="0" />
        <polyline points="-44,5 -49,0 -44,-5" />
        <polyline points="44,5 49,0 44,-5" />
      </g>
    </g>
    <g class="labels" id="labels">
      <text x="0" y="0">0</text>
      <text x="0" y="0">0</text>
      <text x="0" y="0">0</text>
      <text x="0" y="0">0</text>
    </g>
    <use class="labels" xlink:href="#labels" transform="rotate(90)" />
    <circle class="dot" r="4" />
  </svg>
  <p class="polar"><input type="number" value="0" class='magnitude' />@<input type="number" value="0" class='angle'/>°</p>
</div>
<script type="text/javascript"><![CDATA[
  var svg   = document.getElementsByTagName('svg')[0];
  var svgNS = svg.getAttribute('xmlns');
  var pt    = svg.createSVGPoint();

  function mxy(evt){
    pt.x = evt.clientX;
    pt.y = evt.clientY;
    return pt.matrixTransform(svg.getScreenCTM().inverse());
  }

  var dot  = document.querySelector('.complex-plane-picker .dot');
  var real = document.querySelector('.real');
  var imag = document.querySelector('.imaginary');
  var size = document.querySelector('.magnitude');
  var angl = document.querySelector('.angle');

  var labels = svg.querySelectorAll('.complex-plane-picker #labels text');
  var scale, maxValue;
  var updateScale = function(newScale){
    scale = newScale;
    maxValue = 50*scale;
    var tickSize=1e-6;
    var e=-5;
    var f=-1;
    var fs = [1,2,5]
    while (tickSize/scale < 10){
      if (++f%fs.length==0) ++e;
      tickSize = fs[f%fs.length]*Math.pow(10,e);
    }
    for (var i=labels.length;i;--i){
      labels[i-1].firstChild.nodeValue = (tickSize*i).toString().replace(/(\.0*[^0]+)0{3,}.*/,'$1');
      labels[i-1].setAttribute('y', -tickSize*i/scale);
    }
    // updateRectangularFromDot();
    // updatePolarFromDot();
  };

  var rescaleAsNeeded = function(x,y,jump){
    var scaleFactor = 1.03;
    if (jump && (x>maxValue || y>maxValue || (x<maxValue*0.1 && y<maxValue*0.1))){
      updateScale(Math.max(x,y)*2/50);
    } else if (x>maxValue*0.8 || y>maxValue*0.8) updateScale(scale*scaleFactor);
    else if (x<maxValue*0.1 && y<maxValue*0.1) updateScale(scale/scaleFactor);
  };  

  var updateFromRectangular = function(){
    var x = real.value*1;
    var y = imag.value*1;
    rescaleAsNeeded(x,y,true);
    dot.cx.baseVal.value =  x/scale;
    dot.cy.baseVal.value = -y/scale;
    updatePolarFromDot();
  };
  real.addEventListener('input',updateFromRectangular,false);
  imag.addEventListener('input',updateFromRectangular,false);

  var updateFromPolar = function(){
    var hyp = size.value*1;
    var rad = angl.value*Math.PI/180;
    var x   = Math.cos(rad)*hyp;
    var y   = Math.sin(rad)*hyp;
    rescaleAsNeeded(x,y,true);
    dot.cx.baseVal.value =  x/scale;
    dot.cy.baseVal.value = -y/scale;
    updateRectangularFromDot();
  };
  size.addEventListener('input',updateFromPolar,false);
  angl.addEventListener('input',updateFromPolar,false);

  var updateRectangularFromDot = function(){
    real.value = ( dot.cx.baseVal.value*scale).toFixed(2);
    imag.value = (-dot.cy.baseVal.value*scale).toFixed(2);
  };
  var updatePolarFromDot = function(){
    var x =  dot.cx.baseVal.value*scale;
    var y = -dot.cy.baseVal.value*scale;
    size.value = Math.sqrt(x*x+y*y).toFixed(2);
    angl.value = (Math.atan2(y,x)*180/Math.PI).toFixed(1);
  }
  var dragging = false;
  dot.addEventListener('mousedown',function(evt){
    var offset = mxy(evt);
    dragging = true;
    offset.x = dot.cx.baseVal.value - offset.x;
    offset.y = dot.cy.baseVal.value - offset.y;
    var scaleTimer;
    var move = function(evt){
      clearTimeout(scaleTimer);
      var now = mxy(evt);
      var x = offset.x + now.x;
      var y = -(offset.y + now.y);
      dot.cx.baseVal.value = x;
      dot.cy.baseVal.value = -y;
      x = Math.abs(x)*scale, y=Math.abs(y)*scale;
      var oldScale = scale;
      rescaleAsNeeded(x,y);
      updatePolarFromDot();
      updateRectangularFromDot();
      if (oldScale != scale) scaleTimer = setTimeout(function(){move(evt)},1000/30);
    };
    document.documentElement.style.userSelect = 
    document.documentElement.style.MozUserSelect = 
    document.documentElement.style.webkitUserSelect = 'none';
    svg.addEventListener('mousemove',move,false);
    document.documentElement.addEventListener('mouseup',function(){
      dragging = false;
      clearTimeout(scaleTimer);
      svg.removeEventListener('mousemove',move,false);
      document.documentElement.style.userSelect = 
      document.documentElement.style.MozUserSelect = 
      document.documentElement.style.webkitUserSelect = '';
    },false);
  },false);

  dot.addEventListener('dblclick',function(){
    dot.cx.baseVal.value = dot.cy.baseVal.value = 0;
    updateScale(1.0);
    updatePolarFromDot();
    updateRectangularFromDot();
  },false);

  updateScale(1.0);

]]></script>
</body></html>