我尝试使用 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 和谷歌幻灯片中的效果。
您可以从此答案中适应@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>