效果图

源代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Multi-Axis Motion with Canvas</title>
<style>
body {
margin: 0;
}
#controls {
position: absolute;
top: 10px;
right: 10px;
z-index: 1;
background: rgba(255, 255, 255, 0.8);
padding: 10px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div id="controls">
<h4>直线插补</h4>
<label>起始点 X: <input type="number" id="startX" value="10"></label><br>
<label>起始点 Y: <input type="number" id="startY" value="1"></label><br>
<label>结束点 X: <input type="number" id="endX" value="20"></label><br>
<label>结束点 Y: <input type="number" id="endY" value="10"></label><br>
<label>进给速率: <input type="number" id="feedRateLine" value="15"></label><br>
<button id="lineButton">执行直线插补</button><br><br>
<h4>圆弧插补</h4>
<label>圆心 X: <input type="number" id="centerX" value="250"></label><br>
<label>圆心 Y: <input type="number" id="centerY" value="250"></label><br>
<label>半径: <input type="number" id="radius" value="100"></label><br>
<label>起始角度: <input type="number" id="startAngle" value="0" step="0.01"></label><br>
<label>结束角度: <input type="number" id="endAngle" value="1.57" step="0.01"></label><br>
<label>进给速率: <input type="number" id="feedRateArc" value="10"></label><br>
<button id="arcButton">执行圆弧插补</button><br><br>
<button id="clearButton">清空画布</button>
</div>
<canvas id="myCanvas" width="500" height="500"></canvas>
<script>
class Axis {
constructor() {
this.position = 0;
this.velocity = 0;
this.acceleration = 0;
this.targetPosition = 0;
}
update(dt) {
const distance = this.targetPosition - this.position;
const absDistance = Math.abs(distance);
const decelDistance = Math.pow(this.velocity, 2) / (2 * this.acceleration);
if (absDistance <= decelDistance) {
if (Math.abs(this.velocity) < 0.001) {
this.velocity = 0;
this.position = this.targetPosition;
} else {
const sign = distance > 0 ? 1 : -1;
this.velocity -= sign * this.acceleration * dt;
this.position += this.velocity * dt;
}
} else {
if (Math.abs(this.velocity) < this.targetVelocity) {
this.velocity += this.acceleration * dt;
}
this.position += this.velocity * dt;
}
}
}
class MultiAxisSystem {
constructor(numAxes) {
this.axes = new Array(numAxes).fill().map(() => new Axis());
this.canvas = document.getElementById('myCanvas');
this.ctx = this.canvas.getContext('2d');
this.ctx2 = this.canvas.getContext('2d');
this.historyPoint=[];
}
setTargetPositions(targetPositions) {
if (targetPositions.length !== this.axes.length) {
console.error("Invalid target positions.");
return;
}
for (let i = 0; i < this.axes.length; ++i) {
this.axes[i].targetPosition = targetPositions[i];
}
}
update(dt) {
this.axes.forEach(axis => axis.update(dt));
}
linearInterpolation(startX, startY, endX, endY, feedRate, dt) {
const distance = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));
const totalTime = distance / feedRate;
const numSteps = Math.floor(totalTime / dt);
const xStep = (endX - startX) / numSteps;
const yStep = (endY - startY) / numSteps;
let currentStep = 0;
const animate = () => {
if (currentStep < numSteps) {
const x = startX + currentStep * xStep;
const y = startY + currentStep * yStep;
this.setTargetPositions([x, y]);
this.update(dt);
this.draw(x, y);
currentStep++;
setTimeout(animate, dt * 1000);
}
};
animate();
}
circularInterpolation(centerX, centerY, radius, startAngle, endAngle, feedRate, dt) {
const arcLength = Math.abs(endAngle - startAngle) * radius;
const totalTime = arcLength / feedRate;
const numSteps = Math.floor(totalTime / dt);
const angleStep = (endAngle - startAngle) / numSteps;
let currentStep = 0;
const animate = () => {
if (currentStep < numSteps) {
const currentAngle = startAngle + currentStep * angleStep;
const x = centerX + radius * Math.cos(currentAngle);
const y = centerY + radius * Math.sin(currentAngle);
this.setTargetPositions([x, y]);
this.update(dt);
this.draw(x, y);
currentStep++;
setTimeout(animate, dt * 1000);
}
};
animate();
}
draw(x, y) {
let ctx=this.ctx
let canvas=this.canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制网格
const gridSize = 20;
ctx.strokeStyle = '#e0e0e0';
ctx.beginPath();
for (let i = 0; i <= canvas.width; i += gridSize) {
ctx.moveTo(i, 0);
ctx.lineTo(i, canvas.height);
}
for (let j = 0; j <= canvas.height; j += gridSize) {
ctx.moveTo(0, j);
ctx.lineTo(canvas.width, j);
}
ctx.stroke();
// 绘制坐标轴
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.moveTo(canvas.width / 2, 0);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.moveTo(0, canvas.height / 2);
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
let ctx2=this.ctx2
if(this.historyPoint.length>0){
for (let i = 0; i < this.historyPoint.length; i++) {
ctx2.beginPath();
ctx2.arc(this.historyPoint[i].x, this.historyPoint[i].y, 5, 0, 2 * Math.PI);
ctx2.fillStyle = 'green';
ctx2.fill();
}
}
this.historyPoint.push({x,y})
// 绘制运动点
ctx2.beginPath();
ctx2.arc(x, y, 5, 0, 2 * Math.PI);
ctx2.fillStyle = 'blue';
ctx2.fill();
// 在左上角绘制坐标
ctx.fillStyle = 'red';
ctx.font = '12px Arial';
ctx.fillText(`(${x.toFixed(2)}, ${y.toFixed(2)})`, 10, 20);
}
clearCanvas() {
this.historyPoint=[]
const ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
const multiAxisSystem = new MultiAxisSystem(2);
const timeStep = 0.01;
document.getElementById('lineButton').addEventListener('click', () => {
const startX = parseFloat(document.getElementById('startX').value);
const startY = parseFloat(document.getElementById('startY').value);
const endX = parseFloat(document.getElementById('endX').value);
const endY = parseFloat(document.getElementById('endY').value);
const feedRate = parseFloat(document.getElementById('feedRateLine').value);
multiAxisSystem.historyPoint=[];
multiAxisSystem.linearInterpolation(startX, startY, endX, endY, feedRate, timeStep);
});
document.getElementById('arcButton').addEventListener('click', () => {
const centerX = parseFloat(document.getElementById('centerX').value);
const centerY = parseFloat(document.getElementById('centerY').value);
const radius = parseFloat(document.getElementById('radius').value);
const startAngle = parseFloat(document.getElementById('startAngle').value);
const endAngle = parseFloat(document.getElementById('endAngle').value);
const feedRate = parseFloat(document.getElementById('feedRateArc').value);
multiAxisSystem.historyPoint=[];
multiAxisSystem.circularInterpolation(centerX, centerY, radius, startAngle, endAngle, feedRate, timeStep);
});
document.getElementById('clearButton').addEventListener('click', () => {
multiAxisSystem.clearCanvas();
});
</script>
</body>
</html>