分享|椭圆第二定义可视化
397
2026.01.24
2026.01.24
发布于 山东

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>椭圆第二定义可视化</title>

<style>

* {

margin: 0;

padding: 0;

box-sizing: border-box;

font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;

}

body {

background-color: #f0f2f5;

color: #333;

line-height: 1.6;

padding: 20px;

min-height: 100vh;

}

.container {

max-width: 1200px;

margin: 0 auto;

display: flex;

flex-direction: column;

gap: 25px;

}

header {

text-align: center;

padding: 20px;

background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);

color: white;

border-radius: 12px;

box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);

}

h1 {

font-size: 2.5rem;

margin-bottom: 10px;

}

.subtitle {

font-size: 1.2rem;

opacity: 0.9;

max-width: 800px;

margin: 0 auto;

}

.content {

display: flex;

flex-wrap: wrap;

gap: 25px;

}

.visualization-section {

flex: 2;

min-width: 300px;

background-color: white;

border-radius: 12px;

padding: 25px;

box-shadow: 0 6px 15px rgba(0, 0, 0, 0.08);

}

.explanation-section {

flex: 1;

min-width: 300px;

background-color: white;

border-radius: 12px;

padding: 25px;

box-shadow: 0 6px 15px rgba(0, 0, 0, 0.08);

}

.section-title {

font-size: 1.8rem;

color: #2c3e50;

margin-bottom: 20px;

padding-bottom: 10px;

border-bottom: 2px solid #3498db;

}

.canvas-container {

position: relative;

width: 100%;

height: 400px;

background-color: #f8f9fa;

border-radius: 8px;

overflow: hidden;

border: 1px solid #ddd;

}

#ellipseCanvas {

width: 100%;

height: 100%;

cursor: pointer;

}

.controls {

display: flex;

flex-wrap: wrap;

gap: 15px;

margin-top: 20px;

padding: 20px;

background-color: #f8f9fa;

border-radius: 8px;

}

.control-group {

flex: 1;

min-width: 200px;

}

.control-label {

display: block;

margin-bottom: 8px;

font-weight: 600;

color: #2c3e50;

}

.slider-container {

display: flex;

align-items: center;

gap: 10px;

}

.slider-value {

min-width: 40px;

text-align: center;

font-weight: 600;

color: #3498db;

}

input[type="range"] {

flex: 1;

height: 8px;

border-radius: 4px;

background: #ddd;

outline: none;

-webkit-appearance: none;

}

input[type="range"]::-webkit-slider-thumb {

-webkit-appearance: none;

width: 20px;

height: 20px;

border-radius: 50%;

background: #3498db;

cursor: pointer;

transition: all 0.2s;

}

input[type="range"]::-webkit-slider-thumb:hover {

background: #2980b9;

transform: scale(1.1);

}

.definition-box {

background-color: #e8f4fc;

border-left: 4px solid #3498db;

padding: 20px;

margin: 20px 0;

border-radius: 0 8px 8px 0;

}

.math-expression {

font-family: 'Cambria', 'Times New Roman', serif;

font-size: 1.4rem;

text-align: center;

margin: 15px 0;

color: #2c3e50;

}

.variables {

display: flex;

justify-content: space-around;

margin-top: 15px;

flex-wrap: wrap;

}

.variable {

text-align: center;

padding: 10px;

background-color: #f1f8ff;

border-radius: 6px;

min-width: 100px;

margin: 5px;

}

.var-name {

font-weight: bold;

color: #3498db;

font-size: 1.2rem;

}

.var-desc {

font-size: 0.9rem;

color: #666;

margin-top: 5px;

}

.real-app {

background-color: #fff8e1;

border-left: 4px solid #f39c12;

padding: 20px;

margin: 25px 0;

border-radius: 0 8px 8px 0;

}

.app-title {

color: #e67e22;

margin-bottom: 10px;

display: flex;

align-items: center;

gap: 10px;

}

ul {

padding-left: 20px;

margin: 15px 0;

}

li {

margin-bottom: 10px;

}

.highlight {

color: #e74c3c;

font-weight: 600;

}

.note {

font-size: 0.9rem;

color: #7f8c8d;

font-style: italic;

margin-top: 20px;

padding: 10px;

background-color: #f9f9f9;

border-radius: 6px;

}

.instructions {

background-color: #e8f7ee;

border-left: 4px solid #27ae60;

padding: 15px;

margin: 20px 0;

border-radius: 0 8px 8px 0;

}

.footer {

text-align: center;

margin-top: 20px;

padding-top: 20px;

border-top: 1px solid #ddd;

color: #7f8c8d;

font-size: 0.9rem;

}

@media (max-width: 768px) {

.content {

flex-direction: column;

}

h1 {

font-size: 2rem;

}

}

</style>

</head>

<body>

<div class="container">

<header>

<h1>椭圆第二定义可视化</h1>

<p class="subtitle">探索椭圆作为到定点(焦点)与定直线(准线)距离之比为常数的点的轨迹</p>

</header>

<div class="content">

<section class="visualization-section">

<h2 class="section-title">交互式可视化</h2>

<div class="instructions">

<p>💡 <strong>操作指南:</strong> 拖动椭圆上的红点可观察点P位置变化,拖动焦点(绿色)或准线(蓝色)可改变椭圆形状,调整离心率可控制椭圆扁平程度。</p>

</div>

<div class="canvas-container">

<canvas id="ellipseCanvas"></canvas>

</div>

<div class="controls">

<div class="control-group">

<label class="control-label">离心率 e</label>

<div class="slider-container">

<input type="range" id="eccentricitySlider" min="0.1" max="0.9" step="0.01" value="0.6">

<span id="eccentricityValue" class="slider-value">0.60</span>

</div>

</div>

<div class="control-group">

<label class="control-label">焦距 c</label>

<div class="slider-container">

<input type="range" id="focusSlider" min="50" max="200" step="1" value="120">

<span id="focusValue" class="slider-value">120</span>

</div>

</div>

<div class="control-group">

<label class="control-label">准线位置</label>

<div class="slider-container">

<input type="range" id="directrixSlider" min="300" max="500" step="1" value="400">

<span id="directrixValue" class="slider-value">400</span>

</div>

</div>

</div>

<div class="variables">

<div class="variable">

<div class="var-name" id="pfValue">PF = 200</div>

<div class="var-desc">点P到焦点F的距离</div>

</div>

<div class="variable">

<div class="var-name" id="pdValue">PD = 333</div>

<div class="var-desc">点P到准线D的距离</div>

</div>

<div class="variable">

<div class="var-name" id="ratioValue">PF/PD = 0.60</div>

<div class="var-desc">距离比 (离心率 e)</div>

</div>

</div>

</section>

<section class="explanation-section">

<h2 class="section-title">椭圆第二定义</h2>

<div class="definition-box">

<p>椭圆是平面内到定点(焦点)的距离与到定直线(准线)的距离之比为常数e (0 &lt; e &lt; 1) 的点的轨迹。</p>

<div class="math-expression">

\(\frac{|PF|}{|PD|} = e\)

</div>

<p>其中:

<ul>

<li>P为椭圆上的任意一点</li>

<li>F为椭圆的焦点</li>

<li>D为点P到准线的垂足</li>

<li>e为离心率,且0 &lt; e &lt; 1</li>

</ul>

</p>

</div>

<h3 class="section-title">数学推导</h3>

<p>从第二定义出发,设焦点坐标为F(c, 0),准线方程为x = d (d > c),离心率为e。</p>

<p>对任意点P(x, y),有:</p>

<div class="math-expression">

\(\frac{\sqrt{(x-c)^2 + y^2}}{|x-d|} = e\)

</div>

<p>两边平方并整理可得椭圆的标准方程:</p>

<div class="math-expression">

\(\frac{x^2}{a^2} + \frac{y^2}{b^2} = 1\)

</div>

<p>其中 \(a = \frac{ed}{\sqrt{1-e^2}}\),\(b = a\sqrt{1-e^2}\),且满足关系 \(c = ae\),\(d = \frac{a}{e}\)。</p>

<div class="real-app">

<h3 class="app-title">📡 实际应用案例</h3>

<ul>

<li><span class="highlight">光学性质:</span>从椭圆一个焦点发出的光线,经椭圆反射后必定通过另一个焦点。这一性质被应用于卫星天线、光学设备的设计中。</li>

<li><span class="highlight">天体力学:</span>行星轨道近似为椭圆,太阳位于一个焦点上。开普勒第一定律就是基于这一观察。</li>

<li><span class="highlight">建筑声学:</span>椭圆形的房间会产生"耳语廊"效应,在一个焦点处低声说话,在另一个焦点处可以清晰听到。</li>

<li><span class="highlight">医学成像:</span>某些超声波设备利用椭圆反射原理来聚焦能量。</li>

</ul>

</div>

<div class="note">

<p>注:椭圆的第一定义(到两焦点距离之和为常数)和第三定义(与两定点连线斜率积为常数)均可由第二定义推导出来,三个定义在数学上是等价的。</p>

</div>

</section>

</div>

<div class="footer">

<p>椭圆第二定义可视化 | 使用HTML5 Canvas实现 | 拖动元素以交互探索椭圆性质</p>

</div>

</div>

<!-- 引入MathJax以渲染数学公式 -->

<script src="https://polyfill.io/v3/polyfill.min.js?features=es6">\

<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">\

<script>

// 获取Canvas元素和上下文

const canvas = document.getElementById('ellipseCanvas');

const ctx = canvas.getContext('2d');

// 设置Canvas尺寸

function resizeCanvas() {

const container = canvas.parentElement;

canvas.width = container.clientWidth;

canvas.height = container.clientHeight;

drawEllipse();

}

// 初始参数

let params = {

e: 0.6, // 离心率

c: 120, // 焦距 (焦点到中心的距离)

directrixX: 400, // 准线的x坐标

centerX: 200, // 椭圆中心的x坐标

centerY: 200, // 椭圆中心的y坐标

a: 0, // 椭圆长半轴 (将根据参数计算)

b: 0, // 椭圆短半轴 (将根据参数计算)

pointP: { x: 0, y: 0 } // 椭圆上的点P

};

// 更新椭圆参数

function updateEllipseParams() {

// 计算椭圆参数

// 从第二定义推导:a = e * d / sqrt(1 - e^2)

// 其中d是焦点到准线的距离

const d = Math.abs(params.directrixX - (params.centerX + params.c));

params.a = params.e * d / Math.sqrt(1 - params.e * params.e);

params.b = params.a * Math.sqrt(1 - params.e * params.e);

// 设置点P的初始位置 (椭圆上角度为60°的点)

const angle = Math.PI / 3; // 60度

params.pointP.x = params.centerX + params.a * Math.cos(angle);

params.pointP.y = params.centerY + params.b * Math.sin(angle);

}

// 获取滑块元素

const eccentricitySlider = document.getElementById('eccentricitySlider');

const focusSlider = document.getElementById('focusSlider');

const directrixSlider = document.getElementById('directrixSlider');

const eccentricityValue = document.getElementById('eccentricityValue');

const focusValue = document.getElementById('focusValue');

const directrixValue = document.getElementById('directrixValue');

// 滑块事件监听

eccentricitySlider.addEventListener('input', function() {

params.e = parseFloat(this.value);

eccentricityValue.textContent = params.e.toFixed(2);

updateEllipseParams();

drawEllipse();

});

focusSlider.addEventListener('input', function() {

params.c = parseInt(this.value);

focusValue.textContent = params.c;

updateEllipseParams();

drawEllipse();

});

directrixSlider.addEventListener('input', function() {

params.directrixX = parseInt(this.value);

directrixValue.textContent = params.directrixX;

updateEllipseParams();

drawEllipse();

});

// 初始化滑块值

eccentricityValue.textContent = params.e.toFixed(2);

focusValue.textContent = params.c;

directrixValue.textContent = params.directrixX;

// 鼠标交互状态

let dragging = false;

let dragTarget = null; // 'focus', 'directrix', 'pointP'

// 检查点是否在可拖动元素上

function getDragTarget(x, y) {

// 检查焦点

const focusX = params.centerX + params.c;

const focusY = params.centerY;

const focusDist = Math.sqrt((x - focusX) ** 2 + (y - focusY) ** 2);

if (focusDist < 10) return 'focus';

// 检查准线

const directrixDist = Math.abs(x - params.directrixX);

if (directrixDist < 10 && y > 50 && y < canvas.height - 50) return 'directrix';

// 检查椭圆上的点P

const pointPDist = Math.sqrt((x - params.pointP.x) ** 2 + (y - params.pointP.y) ** 2);

if (pointPDist < 10) return 'pointP';

return null;

}

// 处理鼠标按下事件

canvas.addEventListener('mousedown', function(e) {

const rect = canvas.getBoundingClientRect();

const x = e.clientX - rect.left;

const y = e.clientY - rect.top;

dragTarget = getDragTarget(x, y);

if (dragTarget) {

dragging = true;

canvas.style.cursor = 'grabbing';

}

});

// 处理鼠标移动事件

canvas.addEventListener('mousemove', function(e) {

const rect = canvas.getBoundingClientRect();

const x = e.clientX - rect.left;

const y = e.clientY - rect.top;

// 更新鼠标光标

if (!dragging) {

const target = getDragTarget(x, y);

canvas.style.cursor = target ? 'grab' : 'default';

}

// 处理拖动

if (dragging && dragTarget) {

if (dragTarget === 'focus') {

// 限制焦点只能在x轴上移动,且不能超过准线

const newFocusX = Math.max(params.centerX - params.a + 20, Math.min(x, params.directrixX - 20));

params.c = newFocusX - params.centerX;

focusSlider.value = params.c;

focusValue.textContent = params.c;

} else if (dragTarget === 'directrix') {

// 限制准线必须在焦点右侧

const focusX = params.centerX + params.c;

const newDirectrixX = Math.max(focusX + 50, Math.min(x, canvas.width - 50));

params.directrixX = newDirectrixX;

directrixSlider.value = params.directrixX;

directrixValue.textContent = params.directrixX;

} else if (dragTarget === 'pointP') {

// 计算椭圆上离鼠标最近的点

const angle = Math.atan2((y - params.centerY) / params.b, (x - params.centerX) / params.a);

params.pointP.x = params.centerX + params.a * Math.cos(angle);

params.pointP.y = params.centerY + params.b * Math.sin(angle);

}

updateEllipseParams();

drawEllipse();

}

});

// 处理鼠标释放事件

canvas.addEventListener('mouseup', function() {

dragging = false;

dragTarget = null;

});

canvas.addEventListener('mouseleave', function() {

dragging = false;

dragTarget = null;

});

// 绘制椭圆和所有元素

function drawEllipse() {

// 清除画布

ctx.clearRect(0, 0, canvas.width, canvas.height);

// 更新椭圆参数

updateEllipseParams();

// 绘制坐标轴

drawAxes();

// 绘制椭圆

drawEllipseCurve();

// 绘制焦点

drawFocus();

// 绘制准线

drawDirectrix();

// 绘制点P及其与焦点和准线的关系

drawPointP();

// 更新显示的距离值

updateDistanceValues();

}

// 绘制坐标轴

function drawAxes() {

ctx.save();

ctx.strokeStyle = '#aaa';

ctx.lineWidth = 1;

ctx.setLineDash([5, 5]);

// x轴

ctx.beginPath();

ctx.moveTo(0, params.centerY);

ctx.lineTo(canvas.width, params.centerY);

ctx.stroke();

// y轴

ctx.beginPath();

ctx.moveTo(params.centerX, 0);

ctx.lineTo(params.centerX, canvas.height);

ctx.stroke();

ctx.restore();

// 坐标轴标签

ctx.fillStyle = '#666';

ctx.font = '14px Arial';

ctx.fillText('x', canvas.width - 10, params.centerY - 5);

ctx.fillText('y', params.centerX + 5, 15);

// 原点标记

ctx.fillStyle = '#666';

ctx.beginPath();

ctx.arc(params.centerX, params.centerY, 3, 0, Math.PI * 2);

ctx.fill();

ctx.fillText('O', params.centerX + 8, params.centerY - 5);

}

// 绘制椭圆曲线

function drawEllipseCurve() {

ctx.save();

ctx.strokeStyle = '#e74c3c';

ctx.lineWidth = 2;

ctx.beginPath();

// 使用参数方程绘制椭圆

const steps = 100;

for (let i = 0; i <= steps; i++) {

const angle = (i / steps) * Math.PI * 2;

const x = params.centerX + params.a * Math.cos(angle);

const y = params.centerY + params.b * Math.sin(angle);

if (i === 0) {

ctx.moveTo(x, y);

} else {

ctx.lineTo(x, y);

}

}

ctx.stroke();

ctx.restore();

}

// 绘制焦点

function drawFocus() {

const focusX = params.centerX + params.c;

const focusY = params.centerY;

ctx.save();

// 焦点圆

ctx.fillStyle = '#27ae60';

ctx.beginPath();

ctx.arc(focusX, focusY, 8, 0, Math.PI * 2);

ctx.fill();

// 焦点标签

ctx.fillStyle = '#27ae60';

ctx.font = 'bold 16px Arial';

ctx.fillText('F', focusX + 10, focusY - 10);

// 显示焦点坐标

ctx.font = '14px Arial';

ctx.fillText(`(${focusX}, ${focusY})`, focusX + 15, focusY + 20);

ctx.restore();

}

// 绘制准线

function drawDirectrix() {

ctx.save();

// 准线

ctx.strokeStyle = '#3498db';

ctx.lineWidth = 2;

ctx.setLineDash([5, 3]);

ctx.beginPath();

ctx.moveTo(params.directrixX, 20);

ctx.lineTo(params.directrixX, canvas.height - 20);

ctx.stroke();

// 准线标签

ctx.fillStyle = '#3498db';

ctx.font = 'bold 16px Arial';

ctx.fillText('准线', params.directrixX + 10, 40);

// 准线方程

ctx.font = '14px Arial';

ctx.fillText(`x = ${params.directrixX}`, params.directrixX + 10, 60);

ctx.restore();

}

// 绘制点P及其关系

function drawPointP() {

const focusX = params.centerX + params.c;

const focusY = params.centerY;

// 计算点P到准线的垂足D

const dX = params.directrixX;

const dY = params.pointP.y;

ctx.save();

// 绘制点P到焦点的连线

ctx.strokeStyle = '#f39c12';

ctx.lineWidth = 2;

ctx.beginPath();

ctx.moveTo(params.pointP.x, params.pointP.y);

ctx.lineTo(focusX, focusY);

ctx.stroke();

// 绘制点P到准线的垂线

ctx.strokeStyle = '#9b59b6';

ctx.lineWidth = 2;

ctx.setLineDash([3, 3]);

ctx.beginPath();

ctx.moveTo(params.pointP.x, params.pointP.y);

ctx.lineTo(dX, dY);

ctx.stroke();

ctx.setLineDash([]);

// 绘制点P

ctx.fillStyle = '#e74c3c';

ctx.beginPath();

ctx.arc(params.pointP.x, params.pointP.y, 8, 0, Math.PI * 2);

ctx.fill();

// 绘制垂足D

ctx.fillStyle = '#9b59b6';

ctx.beginPath();

ctx.arc(dX, dY, 6, 0, Math.PI * 2);

ctx.fill();

// 点P标签

ctx.fillStyle = '#e74c3c';

ctx.font = 'bold 16px Arial';

ctx.fillText('P', params.pointP.x + 10, params.pointP.y - 10);

// 垂足D标签

ctx.fillStyle = '#9b59b6';

ctx.fillText('D', dX + 10, dY - 10);

// 显示点P坐标

ctx.font = '14px Arial';

ctx.fillText(`(${Math.round(params.pointP.x)}, ${Math.round(params.pointP.y)})`,

params.pointP.x + 15, params.pointP.y + 20);

// 标记距离PF

const midPFx = (params.pointP.x + focusX) / 2;

const midPFy = (params.pointP.y + focusY) / 2;

ctx.fillStyle = '#f39c12';

ctx.font = '14px Arial';

ctx.fillText('PF', midPFx, midPFy - 5);

// 标记距离PD

const midPDx = (params.pointP.x + dX) / 2;

const midPDy = (params.pointP.y + dY) / 2;

ctx.fillStyle = '#9b59b6';

ctx.fillText('PD', midPDx, midPDy - 5);

ctx.restore();

}

// 更新显示的距离值

function updateDistanceValues() {

const focusX = params.centerX + params.c;

const focusY = params.centerY;

// 计算PF距离

const pf = Math.sqrt(

(params.pointP.x - focusX) ** 2 +

(params.pointP.y - focusY) ** 2

);

// 计算PD距离

const pd = Math.abs(params.pointP.x - params.directrixX);

// 计算比值

const ratio = pf / pd;

// 更新显示

document.getElementById('pfValue').innerHTML = `PF = ${pf.toFixed(1)}`;

document.getElementById('pdValue').innerHTML = `PD = ${pd.toFixed(1)}`;

document.getElementById('ratioValue').innerHTML = `PF/PD = ${ratio.toFixed(2)}`;

}

// 初始化

window.addEventListener('load', function() {

resizeCanvas();

updateEllipseParams();

drawEllipse();

});

window.addEventListener('resize', resizeCanvas);

</script>

</body>

</html>

评论 (1)