更新SVG二次贝塞尔曲线或三次曲线以拖动路径上的点

问题描述 投票:0回答:1

我想实现拖动点应该始终保持在路径上应该更新命令

Q
C
S
来更新曲线通过点,这个可拖动点不应该是控制点,它应该随之拉动曲线。

此处图像显示 (15,5) 指针充当控制指针。但我希望这个可拖动的圆圈留在路径上,当用户拖动时,它应该随之拉动曲线。

目前我正在通过

x
命令使用可拖动指针的
y
Q
位置。

const d = `M10,0 Q ${draggable.x} ${draggable.y} 400 390`;

预期的行为如下图所示,可拖动指针应始终停留在路径顶部,并且应使用其

Q
x
更新
y
命令或任何贝塞尔命令。

javascript svg
1个回答
0
投票

您可以根据二次曲线的 De Casteljau 方程计算拖动手柄的坐标

function getPointAtQuadraticT(p0, cp1, p, t = 0.5) {
  let t1 = 1 - t;
  return {
    x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t ** 2 * p.x,
    y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t ** 2 * p.y
  };
}

我们还可以根据移动的拖动手柄的坐标来求解修改后的控制点的方程

function quadraticControlPointAtT(p0, ptMid, p, t = 0.5) {
    let t1 = 1 - t;
    let cp1x = (ptMid.x - t1 * t1 * p0.x - t * t * p.x) / (2 * t1 * t);
    let cp1y = (ptMid.y - t1 * t1 * p0.y - t * t * p.y) / (2 * t1 * t);
    return { x: cp1x, y: cp1y };
}

//initial quadratic command points
let p0 = {
  x: 0,
  y: 0
} // M starting point
let cp1 = {
  x: 100,
  y: 0
} // quadratic bezier control point
let p = {
  x: 100,
  y: 100
} // final on-path point

// align drag handle
let ptDrag = document.getElementById('ptDrag')

// calculate point a t=0.5 based on De Calejau equation
let ptMid = getPointAtQuadraticT(p0, cp1, p, 0.5)
ptDrag.setAttribute('cx', ptMid.x)
ptDrag.setAttribute('cy', ptMid.y)


function getPointAtQuadraticT(p0, cp1, p, t = 0.5) {
  let t1 = 1 - t;
  return {
    x: t1 * t1 * p0.x + 2 * t1 * t * cp1.x + t ** 2 * p.x,
    y: t1 * t1 * p0.y + 2 * t1 * t * cp1.y + t ** 2 * p.y
  };
}

function quadraticControlPointAtT(p0, ptMid, p, t = 0.5) {
  let t1 = 1 - t;
  let cp1x = (ptMid.x - t1 * t1 * p0.x - t * t * p.x) / (2 * t1 * t);
  let cp1y = (ptMid.y - t1 * t1 * p0.y - t * t * p.y) / (2 * t1 * t);
  return {
    x: cp1x,
    y: cp1y
  };
}

function updateQuadraticBezier(ptDrag) {
  let cp1_2 = quadraticControlPointAtT(p0, ptDrag, p, 0.5)
  let d = `M ${p0.x} ${p0.y} Q ${cp1_2.x} ${cp1_2.y} ${p.x} ${p.y}`
  path.setAttribute('d', d)
  newD.textContent = d
}



/**
 * drag handler
 * based on Peter Collingridge's tutorial:
 * https://www.petercollingridge.co.uk/tutorials/svg/interactive/dragging/
 */

svg.addEventListener('load', e => {
  makeDraggable(e)
})


function makeDraggable(evt) {
  let svg = evt.target;
  svg.addEventListener('mousedown', startDrag);
  svg.addEventListener('mousemove', drag);
  svg.addEventListener('mouseup', endDrag);
  svg.addEventListener('mouseleave', endDrag);

  let selectedElement = null;

  function startDrag(evt) {
    if (evt.target.classList.contains('draggable')) {
      selectedElement = evt.target;
    }
  }

  function drag(evt) {
    if (selectedElement) {
      evt.preventDefault();
      let CTM = svg.getScreenCTM();
      let ptDrag = {
        x: (evt.clientX - CTM.e) / CTM.a,
        y: (evt.clientY - CTM.f) / CTM.d
      }
      selectedElement.setAttribute("cx", ptDrag.x);
      selectedElement.setAttribute("cy", ptDrag.y);

      // update bezier
      updateQuadraticBezier(ptDrag)
    }
  }

  function endDrag(evt) {
    selectedElement = null;
  }
}
body {
  padding: 5em;
}

svg {
  border: 1px solid #ccc;
  overflow: visible;
  margin: 1em
}

pre{
background:#ccc
}
<code>
<pre id="newD">M0 0 Q 100 0 100 100</pre>
</code>

<svg id="svg" viewBox="0 0 100 100">
  <path id="path" d="M0 0 Q 100 0 100 100" stroke="#ccc" fill="none" />
  <circle class="draggable" opacity="1" id="ptDrag" cx="0" cy="0" r="5" fill="red">
  </circle>
</svg>

© www.soinside.com 2019 - 2024. All rights reserved.