如何在js中做svg激光笔。就像 exlidraw 或 google 幻灯片中的那样?

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

我尝试使用 svg 路径元素创建鼠标轨迹。

使用贝塞尔曲线进行平滑,但不知何故平滑效果不好。

有人可以帮我解决这个问题吗?

const smoothing = 0.10;

function line(pointA, pointB) {
  // Calculate the horizontal distance between pointA and pointB
  var lengthX = pointB[0] - pointA[0];
  // Calculate the vertical distance between pointA and pointB
  var lengthY = pointB[1] - pointA[1];

  // Calculate the length of the line segment using Pythagoras theorem
  var length = Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2));



  // Calculate the angle of the line segment relative to the x-axis
  var angle = Math.atan2(lengthY, lengthX);

  // Return an object containing the length and angle of the line segment
  return {
    length: length,
    angle: angle
  };
}



function controlPoint(current, previous, next, reverse) {
  // Use previous point as fallback if it doesn't exist
  var p = previous || current;
  // Use next point as fallback if it doesn't exist
  var n = next || current;

  // Calculate the line between previous and next points
  var o = line(p, n);

  // Calculate angle based on line's angle and whether to reverse
  var angle = o.angle + (reverse ? Math.PI : 0);
  // Calculate length with smoothing factor
  var length = o.length * smoothing;

  // Calculate new control point coordinates
  var x = current[0] + Math.cos(angle) * length;
  var y = current[1] + Math.sin(angle) * length;

  return [x, y]; // Return the control point coordinates
}


function bezierCommand(point, i, points) {
  // Get the control points
  var cps = controlPoint(points[i - 1], points[i - 2], point);
  var cpe = controlPoint(point, points[i - 1], points[i + 1], true);

  // Construct and return the Bezier command string
  var command = "C " + cps[0] + "," + cps[1] + " " + cpe[0] + "," + cpe[1] + " " + point[0] + "," + point[1];
  return command;
}



function svgPath(points, command) {
  // Initialize the 'd' attribute string
  var d = "";

  // Loop over the points
  for (var i = 0; i < points.length; i++) {
    var point = points[i];

    // If it's the first point, start with 'M'
    if (i === 0) {
      d += "M " + point[0] + "," + point[1];
    } else {
      // Otherwise, append the command for the current point
      d += " " + command(point, i, points);
    }
  }

  // Return the SVG path string
  return '<path d="' + d + '" fill="none" stroke="red" class="paths"/>';
}

var lastMousePoints = [];
const svg = document.querySelector('#mouse-trail')


function addPoint(x, y) {
  lastMousePoints.push([x, y]);
  if (lastMousePoints.length > 20) {
    lastMousePoints.shift();
    svg.innerHTML = svgPath(lastMousePoints, bezierCommand)
  } else if (lastMousePoints.length === 2) {
    svg.innerHTML = svgPath(lastMousePoints, bezierCommand)
  } else if (lastMousePoints.length > 2) {
    svg.innerHTML = svgPath(lastMousePoints, bezierCommand)

  }
}

document.addEventListener('mousemove', function(e) {
  addPoint(e.pageX, e.pageY);
});
.paths {
  stroke-width: 1.5;
}
<svg id="mouse-trail" width="100%" height="100vh"></svg>

我正在存储鼠标的最后 20 个点并使用坐标创建路径。

也欢迎任何其他方法。

注意:不应为此使用外部库,我正在尝试创建激光笔效果,如 excalidraw 和谷歌幻灯片中的效果。

javascript html svg path svg-animate
1个回答
0
投票

您可以从此答案中适应@ConnorsFan的折线平滑概念 SVG 流畅手绘.

基本上,您正在缓冲区中收集多个鼠标输入坐标。绘制的折线基于平均坐标 - 这将平滑原始点数组中的抖动。

更大的缓冲区大小会增加平滑度,但也会增加输入滞后。

我强烈建议将屏幕转换为SVG坐标。否则,当 SVG 的 viewBox 大小与渲染的布局大小不匹配时,您可能会遇到问题。

let svg = document.querySelector("#mouse-trail");

// collect coordinates
let lastMousePoints = [];
let maxVertices = 32;

// create buffer for polyline smoothing
let buffer = [];
let bufferSize = 8;


svg.addEventListener("mousemove", function(e) {
  addPoint(e);
});

function addPoint(e) {

  // get point in svg coordinate system
  let pt = getMouseOrTouchPos(svg, e);

  // append to buffer for smoothing
  buffer.push(pt);

  // start with lower buffer length if buffer not filled
  if (buffer.length < bufferSize && buffer.length > 4) {
    // get smoothed coordinates
    pt = getAveragePoint(buffer, 4);
    lastMousePoints.push(pt);
  }


  if (buffer.length > bufferSize) {

    // get smoothed coordinates
    pt = getAveragePoint(buffer, bufferSize);
    lastMousePoints.push(pt);

    // reset buffer
    buffer.shift();
  }


  // remove points
  if (lastMousePoints.length > maxVertices) {
    lastMousePoints.shift();
  }

  // update polyline
  polyline.setAttribute(
    "points",
    lastMousePoints
    .map((pt) => {
      return [pt.x, pt.y].join(' ');
    })
    .join(" ")
  );

}

/**
 * based on @ConnorsFan's answer
 * SVG smooth freehand drawing
 * https://stackoverflow.com/questions/40324313/svg-smooth-freehand-drawing/#40700068
 */
// Calculate the average point, starting at offset in the buffer
function getAveragePoint(buffer, bufferSize) {
  var len = buffer.length;
  let offset = Math.floor(len / 4);
  offset = len > 8 ? len - 2 : 0
  offset = 0;
  if (len % 2 === 1 || len >= bufferSize) {
    var totalX = 0;
    var totalY = 0;
    var pt, i;
    var count = 0;
    for (i = offset; i < len; i++) {
      count++;
      pt = buffer[i];
      totalX += pt.x;
      totalY += pt.y;
    }
    return {
      x: totalX / count,
      y: totalY / count
    };
  }
  return null;
}

/**
 * based on:
 * @Daniel Lavedonio de Lima
 * https://stackoverflow.com/a/61732450/3355076
 */
function getMouseOrTouchPos(svg, e) {
  let x, y;
  // touch cooordinates
  if (
    e.type == "touchstart" ||
    e.type == "touchmove" ||
    e.type == "touchend" ||
    e.type == "touchcancel"
  ) {
    let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
    let touch = evt.touches[0] || evt.changedTouches[0];
    x = touch.pageX;
    y = touch.pageY;
  } else if (
    e.type == "mousedown" ||
    e.type == "mouseup" ||
    e.type == "mousemove" ||
    e.type == "mouseover" ||
    e.type == "mouseout" ||
    e.type == "mouseenter" ||
    e.type == "mouseleave"
  ) {
    x = e.clientX;
    y = e.clientY;
  }

  // get svg user space coordinates
  let point = new DOMPoint(x, y);
  let ctm = svg.getScreenCTM().inverse();
  point = point.matrixTransform(ctm);
  return {
    x: point.x,
    y: point.y
  };
}
svg {
  border: 1px solid #ccc;
}

polyline {
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
  stroke: red;
  fill: none;
}
<svg id="mouse-trail" width="100%" height="100vh">
  <polyline id="polyline" pathLength="100" />
    </svg>

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