JavaScript中如何旋转两点间椭圆的高度?
解决两点间椭圆高度的旋转问题
我正在尝试在两点间绘制椭圆,目前通过将线段角度添加到圆形函数的theta参数中,已实现大部分功能,但在设置椭圆高度(由height滑块控制的ellipse_h)时遇到问题,恳请协助实现椭圆高度的旋转。
问题分析
代码存在两处关键错误:
angle函数返回的是弧度值,但后续错误地执行了deg * Math.PI /180的转换,导致旋转变换完全失效。- 椭圆点的计算逻辑没有正确结合高度缩放与旋转变换,导致高度无法随线段角度同步旋转。
修复后的代码
HTML
<canvas id="canvas" width="300" height="300"></canvas> <div id="controls"> <h4>Point 1</h4> <div class="control"> <input type="range" min="0.001" max="300" step="0.001" value="100" id="x1_slider">X1:<text id="x1_readout" style="inline">100</text> </div> <div class="control"> <input type="range" min="0.001" max="300" step="0.001" value="150" id="y1_slider">Y1:<text id="y1_readout" style="inline">150</text> </div> <h4>Point 2</h4> <div class="control"> <input type="range" min="0.001" max="300" step="0.001" value="200" id="x2_slider">X2:<text id="x2_readout" style="inline">200</text> </div> <div class="control"> <input type="range" min="0.001" max="300" step="0.001" value="150" id="y2_slider">Y2:<text id="y2_readout" style="inline">150</text> </div> <div class="control" id="height"> <input type="range" min="0.1" max="2" step="0.001" value="1" id="height_slider">Height:<text id="height_readout" style="inline">1</text> </div> <button id="reset">Reset</button> </div>
CSS
#canvas { width: 300px; height: 300px; border: 1px solid grey; background-color: #272727; } #controls { font-family: Arial, sans; font-size: 0.5em; display: inline-flex; border: 1px solid grey; vertical-align: top; padding: 8px; width: 120px; flex-direction: column; justify-content: center; align-items: center; } .control { display: inline-block; } h4 { font-size: 1.3em; margin: 0; } .control input { width: 40px; } #height,#reset { display: block; margin-top: 10px; }
JavaScript
const height_slider = document.getElementById("height_slider"); const height_readout = document.getElementById("height_readout"); const x1_slider = document.getElementById("x1_slider"); const x1_readout = document.getElementById("x1_readout"); const y1_slider = document.getElementById("y1_slider"); const y1_readout = document.getElementById("y1_readout"); const x2_slider = document.getElementById("x2_slider"); const x2_readout = document.getElementById("x2_readout"); const y2_slider = document.getElementById("y2_slider"); const y2_readout = document.getElementById("y2_readout"); const reset = document.getElementById("reset"); const canvas = document.getElementById("canvas"); const ctx = canvas.getContext('2d'); function distance(x1, y1, x2, y2) { return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); } function midpoint(x1, y1, x2, y2) { return [(x1 + x2) / 2,(y1 + y2) / 2]; } function angle(x1, y1, x2, y2) { // 返回弧度值 return Math.atan2(y2 - y1, x2 - x1); } function draw_circle() { let x1 = parseFloat(x1_slider.value); let y1 = parseFloat(y1_slider.value); let x2 = parseFloat(x2_slider.value); let y2 = parseFloat(y2_slider.value); const p1 = [x1,y1]; const p2 = [x2,y2]; const mid = midpoint(p1[0], p1[1], p2[0], p2[1]); const step = 2*Math.PI/40; // 增加点数让椭圆更平滑 const cx = mid[0]; const cy = mid[1]; const r = distance(p1[0], p1[1], p2[0], p2[1]) * 0.5; const ellipse_h = parseFloat(height_slider.value); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.beginPath(); const rotationAngle = angle(x1, y1, x2, y2); const cos = Math.cos(rotationAngle); const sin = Math.sin(rotationAngle); for(let theta=0; theta <= 2*Math.PI; theta+=step) { // 1. 生成原点处的椭圆点 const ellipseX = r * Math.cos(theta); const ellipseY = ellipse_h * r * Math.sin(theta); // 2. 应用旋转变换 const rotatedX = ellipseX * cos - ellipseY * sin; const rotatedY = ellipseX * sin + ellipseY * cos; // 3. 平移到中点 const x = cx + rotatedX; const y = cy + rotatedY; ctx.lineTo(x,y); } ctx.closePath(); // 闭合路径让椭圆完整 ctx.strokeStyle = "#22FF00"; ctx.stroke(); // 绘制两个控制点 ctx.beginPath(); ctx.arc(p1[0], p1[1], 3, 0, 2 * Math.PI); ctx.fillStyle = "white"; ctx.fill(); ctx.beginPath(); ctx.arc(p2[0], p2[1], 3, 0, 2 * Math.PI); ctx.fillStyle = "white"; ctx.fill(); // 更新读数 x1_readout.innerText = (Math.round(x1_slider.value*100)/100).toFixed(2); x2_readout.innerText = (Math.round(x2_slider.value*100)/100).toFixed(2); y1_readout.innerText = (Math.round(y1_slider.value*100)/100).toFixed(2); y2_readout.innerText = (Math.round(y2_slider.value*100)/100).toFixed(2); height_readout.innerText = (Math.round(ellipse_h*100)/100).toFixed(2); } const default_x1 = 100; const default_y1 = 150; const default_x2 = 200; const default_y2 = 150; const default_height = 1; function reset_defaults() { x1_slider.value = default_x1; y1_slider.value = default_y1; x2_slider.value = default_x2; y2_slider.value = default_y2; height_slider.value = default_height; draw_circle(); } x1_slider.oninput = draw_circle; x2_slider.oninput = draw_circle; y1_slider.oninput = draw_circle; y2_slider.oninput = draw_circle; height_slider.oninput = draw_circle; reset.onclick = reset_defaults; reset_defaults();
关键修改说明
- 修复旋转角度转换错误:直接使用
angle函数返回的弧度值计算cos和sin,不再做多余的角度转弧度操作。 - 重构椭圆点计算逻辑:先生成原点处的标准椭圆点,再应用旋转变换,最后平移到中点,确保高度缩放随旋转同步生效。
- 优化椭圆绘制:将循环范围改为
0到2π绘制完整椭圆,增加点数让椭圆边缘更平滑,同时添加closePath()闭合路径。 - 调整滑块范围:将height滑块的范围改为
0.1到2,更符合椭圆高度的实际使用场景。
内容的提问来源于stack exchange,提问作者Dr. Pontchartrain
相关产品推荐
相关产品推荐

