等腰直角三角形旋转
上传时间:2026-04-08 08:59
等腰直角三角形旋转:双向探究
调节滑块,观察三角形在 ±90° 范围内的全等变换
* 细灰线 = 三角形底边
* 粗色线 = 证明用的直角边
* 虚橙线 = C到BD的垂线(H)
* 粗色线 = 证明用的直角边
* 虚橙线 = C到BD的垂线(H)
顺时针 -90°
图① (0°)
逆时针 90°
结论:$\sqrt{2} CF = AF + BF$
AF + BF
0.00
=
√2 × CF
0.00
提示: 当你把角度调回 0° 时,你会发现这就是题目里的图①;调到 45° 左右,就是第一问的平行状态。
查看源码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>几何旋转全向探究</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { background-color: #f1f5f9; }
canvas { background: white; border-radius: 12px; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1); touch-action: none; }
</style>
</head>
<body class="p-4 flex flex-col items-center">
<div class="max-w-5xl w-full space-y-4">
<div class="text-center space-y-1">
<h1 class="text-2xl font-bold text-slate-800">等腰直角三角形旋转:双向探究</h1>
<p class="text-slate-500 text-sm italic">调节滑块,观察三角形在 ±90° 范围内的全等变换</p>
</div>
<div class="bg-white p-6 rounded-2xl shadow-sm flex flex-col lg:flex-row gap-8">
<div class="relative flex-shrink-0">
<canvas id="geometryCanvas" width="500" height="500" class="w-full max-w-[500px]"></canvas>
<div class="absolute bottom-4 left-4 bg-white/80 p-2 rounded text-[10px] text-slate-400 border shadow-sm">
* 细灰线 = 三角形底边<br>
* 粗色线 = 证明用的直角边<br>
* 虚橙线 = C到BD的垂线(H)
</div>
</div>
<div class="flex-1 space-y-6">
<div class="bg-slate-50 p-5 rounded-xl border border-slate-200 space-y-5">
<div>
<div class="flex justify-between mb-2">
<label class="font-bold text-slate-700">旋转角 α (当前: <span id="alphaValue" class="text-blue-600 font-mono">0</span>°)</label>
</div>
<input type="range" id="alphaSlider" min="-90" max="90" step="1" value="30" class="w-full h-3 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600">
<div class="flex justify-between text-[10px] text-slate-400 mt-1">
<span>顺时针 -90°</span>
<span>图① (0°)</span>
<span>逆时针 90°</span>
</div>
</div>
<div class="grid grid-cols-1 gap-2">
<label class="flex items-center gap-3 p-2 hover:bg-white rounded cursor-pointer transition">
<input type="checkbox" id="showGhost" class="w-4 h-4" checked>
<span class="text-sm">保留图①虚线 (初始状态)</span>
</label>
<label class="flex items-center gap-3 p-2 hover:bg-white rounded cursor-pointer transition">
<input type="checkbox" id="showCongruent" class="w-4 h-4" checked>
<span class="text-sm font-bold text-blue-600">高亮显示全等翅膀 (ACE & BCD)</span>
</label>
<label class="flex items-center gap-3 p-2 hover:bg-white rounded cursor-pointer transition border border-orange-200 bg-orange-50/50">
<input type="checkbox" id="showPerpC" class="w-4 h-4 text-orange-500 accent-orange-500">
<span class="text-sm font-bold text-orange-600">显示过 C 作 BD 的垂线 (辅助线)</span>
</label>
</div>
</div>
<div class="p-5 bg-blue-600 rounded-xl text-white shadow-lg">
<h3 class="text-xs font-bold opacity-80 mb-2 uppercase tracking-widest">结论:$\sqrt{2} CF = AF + BF$</h3>
<div class="flex justify-around items-end">
<div class="text-center">
<div class="text-[10px] opacity-70">AF + BF</div>
<div id="valSum" class="text-2xl font-mono font-bold">0.00</div>
</div>
<div class="text-xl pb-1 opacity-50">=</div>
<div class="text-center">
<div class="text-[10px] opacity-70">√2 × CF</div>
<div id="valTarget" class="text-2xl font-mono font-bold text-yellow-300">0.00</div>
</div>
</div>
</div>
<div class="text-xs text-slate-500 bg-slate-100 p-3 rounded">
<strong>提示:</strong> 当你把角度调回 <strong>0°</strong> 时,你会发现这就是题目里的<strong>图①</strong>;调到 <strong>45°</strong> 左右,就是第一问的平行状态。
</div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('geometryCanvas');
const ctx = canvas.getContext('2d');
const alphaSlider = document.getElementById('alphaSlider');
const showCongruent = document.getElementById('showCongruent');
const showGhost = document.getElementById('showGhost');
const showPerpC = document.getElementById('showPerpC');
const SIZE_ABC = 220;
const SIZE_CDE = 130;
const cx = 130; // 稍微右移一点
const cy = 350;
function draw() {
const alphaDeg = parseFloat(alphaSlider.value);
const alpha = alphaDeg * Math.PI / 180;
document.getElementById('alphaValue').innerText = alphaDeg;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const C = { x: cx, y: cy };
const A = { x: cx, y: cy - SIZE_ABC };
const B = { x: cx + SIZE_ABC, y: cy };
// 初始位置参考
const E0 = { x: cx, y: cy - SIZE_CDE };
const D0 = { x: cx + SIZE_CDE, y: cy };
// 旋转后位置
const E = {
x: cx + SIZE_CDE * Math.sin(alpha),
y: cy - SIZE_CDE * Math.cos(alpha)
};
const D = {
x: cx + SIZE_CDE * Math.cos(alpha),
y: cy + SIZE_CDE * Math.sin(alpha)
};
// 交点 F (AE 和 BD 的交点)
function getIntersection(p1, p2, p3, p4) {
const x1 = p1.x, y1 = p1.y, x2 = p2.x, y2 = p2.y;
const x3 = p3.x, y3 = p3.y, x4 = p4.x, y4 = p4.y;
const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (Math.abs(denom) < 0.1) return null;
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
return { x: x1 + ua * (x2 - x1), y: y1 + ua * (y2 - y1) };
}
const F = getIntersection(A, E, B, D) || {x:0, y:0};
// 1. 绘制图①虚线背景
if (showGhost.checked) {
ctx.beginPath();
ctx.setLineDash([5, 5]);
ctx.moveTo(E0.x, E0.y); ctx.lineTo(C.x, C.y); ctx.lineTo(D0.x, D0.y); ctx.closePath();
ctx.strokeStyle = '#cbd5e1'; ctx.stroke();
ctx.setLineDash([]);
}
// 2. 绘制全等色块 (ACE & BCD)
if (showCongruent.checked) {
ctx.fillStyle = 'rgba(59, 130, 246, 0.1)';
ctx.beginPath(); ctx.moveTo(A.x, A.y); ctx.lineTo(C.x, C.y); ctx.lineTo(E.x, E.y); ctx.closePath(); ctx.fill();
ctx.fillStyle = 'rgba(239, 68, 68, 0.08)';
ctx.beginPath(); ctx.moveTo(B.x, B.y); ctx.lineTo(C.x, C.y); ctx.lineTo(D.x, D.y); ctx.closePath(); ctx.fill();
}
// 3. 绘制完整的三角形实体 (细灰线边框)
// ABC
ctx.beginPath(); ctx.moveTo(A.x, A.y); ctx.lineTo(C.x, C.y); ctx.lineTo(B.x, B.y); ctx.closePath();
ctx.strokeStyle = '#e2e8f0'; ctx.lineWidth = 1; ctx.stroke();
// CDE
ctx.beginPath(); ctx.moveTo(E.x, E.y); ctx.lineTo(C.x, C.y); ctx.lineTo(D.x, D.y); ctx.closePath();
ctx.strokeStyle = '#e2e8f0'; ctx.lineWidth = 1; ctx.stroke();
// 4. 突出证明的关键边
ctx.lineWidth = 3;
ctx.lineCap = 'round';
// AC, BC
ctx.strokeStyle = '#3b82f6'; ctx.beginPath(); ctx.moveTo(A.x, A.y); ctx.lineTo(C.x, C.y); ctx.stroke();
ctx.beginPath(); ctx.moveTo(B.x, B.y); ctx.lineTo(C.x, C.y); ctx.stroke();
// CE, CD
ctx.strokeStyle = '#ef4444'; ctx.beginPath(); ctx.moveTo(E.x, E.y); ctx.lineTo(C.x, C.y); ctx.stroke();
ctx.beginPath(); ctx.moveTo(D.x, D.y); ctx.lineTo(C.x, C.y); ctx.stroke();
// AE, BD (AE是紫色, BD是深紫)
ctx.lineWidth = 1.5;
ctx.strokeStyle = '#8b5cf6';
ctx.beginPath(); ctx.moveTo(A.x, A.y); ctx.lineTo(F.x, F.y); ctx.stroke();
ctx.beginPath(); ctx.moveTo(B.x, B.y); ctx.lineTo(F.x, F.y); ctx.stroke();
// CF (结论的核心)
ctx.strokeStyle = '#10b981'; ctx.lineWidth = 3;
ctx.beginPath(); ctx.moveTo(C.x, C.y); ctx.lineTo(F.x, F.y); ctx.stroke();
// 5. 绘制 C 到 BD 的垂线 (新增辅助线)
if (showPerpC.checked) {
// 计算点 C 在线段/直线 BD 上的投影 H
const vecBD = { x: D.x - B.x, y: D.y - B.y };
const vecBC = { x: C.x - B.x, y: C.y - B.y };
const dotProduct = vecBC.x * vecBD.x + vecBC.y * vecBD.y;
const lenBDSq = vecBD.x * vecBD.x + vecBD.y * vecBD.y;
if (lenBDSq > 0.001) {
const t = dotProduct / lenBDSq;
const H = {
x: B.x + t * vecBD.x,
y: B.y + t * vecBD.y
};
// 画虚线 CH
ctx.beginPath();
ctx.setLineDash([5, 5]);
ctx.moveTo(C.x, C.y);
ctx.lineTo(H.x, H.y);
ctx.strokeStyle = '#f97316'; // orange-500
ctx.lineWidth = 2;
ctx.stroke();
ctx.setLineDash([]);
// 绘制直角标记
const lenHC = Math.sqrt((C.x - H.x)**2 + (C.y - H.y)**2);
if (lenHC > 0.1) {
const lenBD = Math.sqrt(lenBDSq);
const uBD = { x: vecBD.x / lenBD, y: vecBD.y / lenBD };
const uHC = { x: (C.x - H.x) / lenHC, y: (C.y - H.y) / lenHC };
const s = 12; // 直角边长
ctx.beginPath();
ctx.moveTo(H.x + uBD.x * s, H.y + uBD.y * s);
ctx.lineTo(H.x + uBD.x * s + uHC.x * s, H.y + uBD.y * s + uHC.y * s);
ctx.lineTo(H.x + uHC.x * s, H.y + uHC.y * s);
ctx.strokeStyle = '#f97316';
ctx.lineWidth = 1;
ctx.stroke();
}
// 标注 H 点
ctx.beginPath();
ctx.arc(H.x, H.y, 4, 0, Math.PI * 2);
ctx.fillStyle = '#f97316';
ctx.fill();
ctx.font = 'bold 15px sans-serif';
ctx.fillStyle = '#c2410c'; // darker orange
// 稍微偏移动避开线条
ctx.fillText('H', H.x + 8, H.y + 16);
}
}
// 6. 标注基本点
const pts = [{p:A, n:'A'}, {p:B, n:'B'}, {p:C, n:'C'}, {p:D, n:'D'}, {p:E, n:'E'}, {p:F, n:'F'}];
pts.forEach(pt => {
ctx.beginPath(); ctx.arc(pt.p.x, pt.p.y, 4, 0, Math.PI*2);
ctx.fillStyle = pt.n === 'F' ? '#8b5cf6' : '#1e293b';
ctx.fill();
ctx.font = 'bold 15px sans-serif';
ctx.fillStyle = pt.n === 'F' ? '#8b5cf6' : '#1e293b';
ctx.fillText(pt.n, pt.p.x + 8, pt.p.y - 8);
});
// 7. 数值
const dist = (p1, p2) => Math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2);
const dAF = dist(A, F) / 10;
const dBF = dist(B, F) / 10;
const dCF = dist(C, F) / 10;
document.getElementById('valSum').innerText = (dAF + dBF).toFixed(2);
document.getElementById('valTarget').innerText = (Math.sqrt(2) * dCF).toFixed(2);
}
alphaSlider.addEventListener('input', draw);
[showCongruent, showGhost, showPerpC].forEach(el => el.addEventListener('change', draw));
window.onload = draw;
</script>
</body>
</html>