You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();

关键修改说明

  1. 修复旋转角度转换错误:直接使用angle函数返回的弧度值计算cos和sin,不再做多余的角度转弧度操作。
  2. 重构椭圆点计算逻辑:先生成原点处的标准椭圆点,再应用旋转变换,最后平移到中点,确保高度缩放随旋转同步生效。
  3. 优化椭圆绘制:将循环范围改为0到2π绘制完整椭圆,增加点数让椭圆边缘更平滑,同时添加closePath()闭合路径。
  4. 调整滑块范围:将height滑块的范围改为0.1到2,更符合椭圆高度的实际使用场景。

内容的提问来源于stack exchange,提问作者Dr. Pontchartrain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 11:10:51