傅里叶变换互动演示HTML实现
这个演示将包括:
一个由多个正弦波组成的时域信号。
每个正弦波的频率、振幅和相位可以调整。
显示每个正弦波的图形以及它们的叠加结果。
通过傅里叶变换(使用FFT)将时域信号转换为频域,并显示频谱。

以下是实现代码( 由 deepseek 依据人工指令生成):
<!DOCTYPE html>
<html>
<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', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #0a1a3a, #1a3a5f, #2a5a8f);
color: #fff;
min-height: 100vh;
padding: 20px;
width: 100%;
overflow-x: hidden;
}
.container {
max-width: 95vw;
width: 100%;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 20px;
padding: 15px;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
h1 {
font-size: 2.2rem;
margin-bottom: 8px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.subtitle {
font-size: 1.1rem;
opacity: 0.9;
max-width: 800px;
margin: 0 auto;
line-height: 1.5;
}
.dashboard {
display: grid;
grid-template-columns: minmax(280px, 320px) 1fr;
gap: 20px;
height: calc(100vh - 180px);
width: 100%;
}
@media (max-width: 1100px) {
.dashboard {
grid-template-columns: 1fr;
height: auto;
}
}
.control-panel {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
overflow-y: auto;
overflow-x: hidden;
}
/* 美化滚动条 */
.control-panel::-webkit-scrollbar {
width: 10px;
}
.control-panel::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
}
.control-panel::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, #4d9fff, #2a5a8f);
border-radius: 10px;
border: 2px solid rgba(255, 255, 255, 0.1);
}
.control-panel::-webkit-scrollbar-thumb:hover {
background: linear-gradient(to bottom, #6babff, #3a6aaf);
}
.visualization-area {
display: grid;
grid-template-rows: 1fr 1fr;
gap: 20px;
width: 100%;
}
.visualization-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
width: 100%;
}
@media (max-width: 900px) {
.visualization-row {
grid-template-columns: 1fr;
}
}
.panel {
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
width: 100%;
}
.panel h2 {
margin-bottom: 12px;
font-size: 1.3rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
padding-bottom: 8px;
}
.canvas-container {
flex: 1;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
overflow: hidden;
margin-bottom: 10px;
width: 100%;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
.controls {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
}
.control-group {
background: rgba(255, 255, 255, 0.05);
padding: 12px;
border-radius: 8px;
width: 100%;
}
.control-group h3 {
margin-bottom: 8px;
font-size: 1.1rem;
color: #a8d1ff;
}
.control-row {
display: flex;
align-items: center;
margin-bottom: 8px;
width: 100%;
}
.control-row label {
width: 70px;
font-weight: 500;
font-size: 0.9rem;
}
.control-row input {
flex: 1;
}
.control-row span {
width: 50px;
text-align: right;
font-size: 0.9rem;
}
input[type="range"] {
-webkit-appearance: none;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: #4d9fff;
border-radius: 50%;
cursor: pointer;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 5px;
width: 100%;
}
.btn {
padding: 8px 12px;
background: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 6px;
color: white;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
flex: 1;
min-width: 80px;
}
.btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.btn.active {
background: #4d9fff;
color: #0a1a3a;
}
.presets {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
width: 100%;
}
.preset-btn {
padding: 8px;
background: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 6px;
color: white;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.3s ease;
}
.preset-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.tab-container {
margin-top: 20px;
width: 100%;
}
.tabs {
display: flex;
margin-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
width: 100%;
}
.tab {
padding: 8px 15px;
cursor: pointer;
opacity: 0.7;
transition: all 0.3s ease;
font-size: 0.9rem;
}
.tab.active {
opacity: 1;
border-bottom: 2px solid #4d9fff;
}
.tab-content {
display: none;
width: 100%;
}
.tab-content.active {
display: block;
}
.harmonic-controls-container {
max-height: 300px;
overflow-y: auto;
padding-right: 5px;
width: 100%;
}
/* 谐波控制区的滚动条样式 */
.harmonic-controls-container::-webkit-scrollbar {
width: 8px;
}
.harmonic-controls-container::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
}
.harmonic-controls-container::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, #4d9fff, #2a5a8f);
border-radius: 8px;
}
.harmonic-controls-container::-webkit-scrollbar-thumb:hover {
background: linear-gradient(to bottom, #6babff, #3a6aaf);
}
.harmonic-item {
margin-bottom: 12px;
padding: 10px;
background: rgba(255, 255, 255, 0.05);
border-radius: 6px;
width: 100%;
}
.harmonic-title {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-weight: 500;
font-size: 0.9rem;
width: 100%;
}
.harmonic-sliders {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
}
.harmonic-slider {
display: flex;
align-items: center;
width: 100%;
}
.harmonic-slider label {
width: 50px;
font-size: 0.85rem;
}
.harmonic-slider input {
flex: 1;
margin: 0 8px;
}
.harmonic-slider span {
width: 40px;
text-align: right;
font-size: 0.85rem;
}
footer {
text-align: center;
margin-top: 20px;
padding: 15px;
opacity: 0.7;
font-size: 0.85rem;
width: 100%;
}
.comparison-canvas {
height: 200px;
}
/* 响应式设计优化 */
@media (max-width: 768px) {
.container {
padding: 10px;
max-width: 100vw;
}
.dashboard {
grid-template-columns: 1fr;
gap: 15px;
}
.control-panel {
padding: 15px;
}
.visualization-area {
gap: 15px;
}
.visualization-row {
gap: 15px;
}
h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
}
@media (max-width: 480px) {
.container {
padding: 5px;
}
.control-panel {
padding: 10px;
}
.panel {
padding: 10px;
}
.control-group {
padding: 10px;
}
h1 {
font-size: 1.5rem;
}
.button-group {
flex-direction: column;
}
.presets {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div>
<header>
<h1>傅里叶变换互动演示</h1>
<p>探索复杂波形如何分解为简单正弦波的组合,直观理解频域分析</p>
</header>
<div>
<div>
<div>
<h3>波形参数</h3>
<div>
<label for="frequency">频率:</label>
<input type="range" id="frequency" min="1" max="10" step="0.1" value="2">
<span id="frequencyValue">2 Hz</span>
</div>
<div>
<label for="amplitude">振幅:</label>
<input type="range" id="amplitude" min="0.1" max="2" step="0.1" value="1">
<span id="amplitudeValue">1.0</span>
</div>
<div>
<label for="phase">相位:</label>
<input type="range" id="phase" min="0" max="6.28" step="0.01" value="0">
<span id="phaseValue">0 rad</span>
</div>
</div>
<div>
<h3>波形类型</h3>
<div>
<button id="sineBtn" class="btn active">正弦波</button>
<button id="squareBtn">方波</button>
<button id="sawtoothBtn">锯齿波</button>
<button id="triangleBtn">三角波</button>
</div>
</div>
<div>
<h3>傅里叶级数</h3>
<div>
<label for="harmonics">谐波数量:</label>
<input type="range" id="harmonics" min="1" max="20" step="1" value="5">
<span id="harmonicsValue">5</span>
</div>
</div>
<div>
<h3>预设信号</h3>
<div>
<button data-preset="simple">简单正弦波</button>
<button data-preset="complex">复杂波形</button>
<button data-preset="heartbeat">心跳信号</button>
<button data-preset="voice">语音模拟</button>
</div>
</div>
<div>
<h3>谐波分解与合成</h3>
<div>
<div>
<div class="tab active" data-tab="harmonics">谐波控制</div>
<div data-tab="synthesis">合成过程</div>
<div data-tab="comparison">合成对比</div>
</div>
<div class="tab-content active" id="harmonics-tab">
<div id="harmonicsControls">
<!-- 动态生成的谐波控制项 -->
</div>
</div>
<div id="synthesis-tab">
<div>
<label for="synthesisStep">显示步骤:</label>
<input type="range" id="synthesisStep" min="1" max="5" step="1" value="5">
<span id="synthesisStepValue">5</span>
</div>
<p style="font-size: 0.85rem; margin-top: 10px; opacity: 0.8;">
调整显示步骤,观察从基波开始逐步添加谐波形成最终波形的过程。
</p>
</div>
<div id="comparison-tab">
<div>
<label for="comparisonHarmonics">谐波数量:</label>
<input type="range" id="comparisonHarmonics" min="1" max="20" step="1" value="5">
<span id="comparisonHarmonicsValue">5</span>
</div>
<p style="font-size: 0.85rem; margin-top: 10px; opacity: 0.8;">
调整谐波数量,观察合成波形如何逐渐接近原始波形。
</p>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<h2>时域信号</h2>
<div>
<canvas id="timeDomain"></canvas>
</div>
</div>
<div>
<h2>频域分析</h2>
<div>
<canvas id="frequencyDomain"></canvas>
</div>
</div>
</div>
<div>
<div>
<h2>谐波分解</h2>
<div>
<canvas id="harmonicsCanvas"></canvas>
</div>
</div>
<div>
<h2>波形合成</h2>
<div>
<canvas id="synthesisCanvas"></canvas>
</div>
</div>
</div>
</div>
</div>
<footer>
<p>傅里叶变换互动演示 © 2023 | 使用HTML5 Canvas和JavaScript实现</p>
</footer>
</div>
<script>
// 获取Canvas元素和上下文
const timeCanvas = document.getElementById('timeDomain');
const freqCanvas = document.getElementById('frequencyDomain');
const harmonicsCanvas = document.getElementById('harmonicsCanvas');
const synthesisCanvas = document.getElementById('synthesisCanvas');
const timeCtx = timeCanvas.getContext('2d');
const freqCtx = freqCanvas.getContext('2d');
const harmonicsCtx = harmonicsCanvas.getContext('2d');
const synthesisCtx = synthesisCanvas.getContext('2d');
// 设置Canvas尺寸
function resizeCanvases() {
const containerWidth = document.querySelector('.canvas-container').clientWidth;
const containerHeight = document.querySelector('.canvas-container').clientHeight;
timeCanvas.width = containerWidth;
timeCanvas.height = containerHeight;
freqCanvas.width = containerWidth;
freqCanvas.height = containerHeight;
harmonicsCanvas.width = containerWidth;
harmonicsCanvas.height = containerHeight;
synthesisCanvas.width = containerWidth;
synthesisCanvas.height = containerHeight;
draw();
}
// 获取控制元素
const frequencySlider = document.getElementById('frequency');
const amplitudeSlider = document.getElementById('amplitude');
const phaseSlider = document.getElementById('phase');
const harmonicsSlider = document.getElementById('harmonics');
const synthesisStepSlider = document.getElementById('synthesisStep');
const comparisonHarmonicsSlider = document.getElementById('comparisonHarmonics');
const frequencyValue = document.getElementById('frequencyValue');
const amplitudeValue = document.getElementById('amplitudeValue');
const phaseValue = document.getElementById('phaseValue');
const harmonicsValue = document.getElementById('harmonicsValue');
const synthesisStepValue = document.getElementById('synthesisStepValue');
const comparisonHarmonicsValue = document.getElementById('comparisonHarmonicsValue');
const sineBtn = document.getElementById('sineBtn');
const squareBtn = document.getElementById('squareBtn');
const sawtoothBtn = document.getElementById('sawtoothBtn');
const triangleBtn = document.getElementById('triangleBtn');
const presetButtons = document.querySelectorAll('.preset-btn');
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
// 初始化变量
let waveType = 'sine';
let frequency = parseFloat(frequencySlider.value);
let amplitude = parseFloat(amplitudeSlider.value);
let phase = parseFloat(phaseSlider.value);
let harmonics = parseInt(harmonicsSlider.value);
let synthesisStep = parseInt(synthesisStepSlider.value);
let comparisonHarmonics = parseInt(comparisonHarmonicsSlider.value);
// 谐波数据
let harmonicAmplitudes = [];
let harmonicPhases = [];
// 初始化谐波数据
function initHarmonicsData() {
harmonicAmplitudes = [];
harmonicPhases = [];
for (let i = 0; i < harmonics; i++) {
harmonicAmplitudes.push(0);
harmonicPhases.push(0);
}
// 根据波形类型设置谐波振幅
updateHarmonicsFromWaveType();
}
// 根据波形类型更新谐波数据
function updateHarmonicsFromWaveType() {
if (waveType === 'sine') {
harmonicAmplitudes[0] = amplitude;
for (let i = 1; i < harmonics; i++) {
harmonicAmplitudes[i] = 0;
}
} else if (waveType === 'square') {
for (let i = 0; i < harmonics; i++) {
const n = i + 1;
if (n % 2 === 1) { // 奇数次谐波
harmonicAmplitudes[i] = (4 / (Math.PI * n)) * amplitude;
} else {
harmonicAmplitudes[i] = 0;
}
}
} else if (waveType === 'sawtooth') {
for (let i = 0; i < harmonics; i++) {
const n = i + 1;
harmonicAmplitudes[i] = (2 / (Math.PI * n)) * amplitude;
}
} else if (waveType === 'triangle') {
for (let i = 0; i < harmonics; i++) {
const n = i + 1;
if (n % 2 === 1) { // 奇数次谐波
harmonicAmplitudes[i] = (8 / (Math.PI * Math.PI * n * n)) * amplitude;
} else {
harmonicAmplitudes[i] = 0;
}
}
}
// 更新谐波控制界面
updateHarmonicsControls();
}
// 更新显示的值
function updateValues() {
frequencyValue.textContent = frequency.toFixed(1) + ' Hz';
amplitudeValue.textContent = amplitude.toFixed(1);
phaseValue.textContent = phase.toFixed(2) + ' rad';
harmonicsValue.textContent = harmonics;
synthesisStepValue.textContent = synthesisStep;
comparisonHarmonicsValue.textContent = comparisonHarmonics;
}
// 绘制时域波形
function drawTimeDomain() {
const width = timeCanvas.width;
const height = timeCanvas.height;
const centerY = height / 2;
// 清除画布
timeCtx.clearRect(0, 0, width, height);
// 绘制网格
timeCtx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
timeCtx.lineWidth = 1;
timeCtx.beginPath();
// 水平网格线
for (let y = 0; y < height; y += height / 4) {
timeCtx.moveTo(0, y);
timeCtx.lineTo(width, y);
}
// 垂直网格线
for (let x = 0; x < width; x += width / 8) {
timeCtx.moveTo(x, 0);
timeCtx.lineTo(x, height);
}
timeCtx.stroke();
// 绘制坐标轴
timeCtx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
timeCtx.lineWidth = 2;
timeCtx.beginPath();
timeCtx.moveTo(0, centerY);
timeCtx.lineTo(width, centerY);
timeCtx.stroke();
// 绘制波形
timeCtx.strokeStyle = '#4d9fff';
timeCtx.lineWidth = 2;
timeCtx.beginPath();
const samples = 300;
for (let i = 0; i <= samples; i++) {
const x = (i / samples) * width;
const t = (i / samples) * Math.PI * 4; // 时间范围
let y = centerY;
if (waveType === 'sine') {
y += amplitude * Math.sin(frequency * t + phase) * (height / 4);
} else if (waveType === 'square') {
y += amplitude * Math.sign(Math.sin(frequency * t + phase)) * (height / 4);
} else if (waveType === 'sawtooth') {
y += amplitude * (2 * ((frequency * t + phase) / (2 * Math.PI) - Math.floor(0.5 + (frequency * t + phase) / (2 * Math.PI)))) * (height / 4);
} else if (waveType === 'triangle') {
const period = 2 * Math.PI / frequency;
const normalizedTime = ((frequency * t + phase) % period) / period;
y += amplitude * (2 * Math.abs(2 * normalizedTime - 1) - 1) * (height / 4);
}
if (i === 0) {
timeCtx.moveTo(x, y);
} else {
timeCtx.lineTo(x, y);
}
}
timeCtx.stroke();
// 绘制标题
timeCtx.fillStyle = 'white';
timeCtx.font = '14px Arial';
timeCtx.fillText('时域信号', 10, 20);
}
// 绘制频域分析
function drawFrequencyDomain() {
const width = freqCanvas.width;
const height = freqCanvas.height;
// 清除画布
freqCtx.clearRect(0, 0, width, height);
// 绘制网格
freqCtx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
freqCtx.lineWidth = 1;
freqCtx.beginPath();
// 水平网格线
for (let y = 0; y < height; y += height / 4) {
freqCtx.moveTo(0, y);
freqCtx.lineTo(width, y);
}
// 垂直网格线
for (let x = 0; x < width; x += width / 8) {
freqCtx.moveTo(x, 0);
freqCtx.lineTo(x, height);
}
freqCtx.stroke();
// 绘制坐标轴
freqCtx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
freqCtx.lineWidth = 2;
freqCtx.beginPath();
freqCtx.moveTo(0, height);
freqCtx.lineTo(width, height);
freqCtx.stroke();
// 绘制频谱
const barWidth = width / (harmonics + 1);
for (let i = 0; i < harmonics; i++) {
const x = (i + 1) * barWidth;
let barHeight;
if (waveType === 'sine') {
// 正弦波只有基频
barHeight = i === 0 ? amplitude * (height / 2) : 0;
} else if (waveType === 'square') {
// 方波的傅里叶级数: 4/π * (1/n) * sin(nωt) for odd n
if (i % 2 === 0) { // 奇数次谐波
const n = i + 1;
barHeight = (4 / (Math.PI * n)) * amplitude * (height / 2);
} else {
barHeight = 0;
}
} else if (waveType === 'sawtooth') {
// 锯齿波的傅里叶级数: 2/π * (-1)^(n+1) * (1/n) * sin(nωt)
const n = i + 1;
barHeight = (2 / (Math.PI * n)) * amplitude * (height / 2);
} else if (waveType === 'triangle') {
// 三角波的傅里叶级数: 8/π² * (1/n²) * sin(nωt) for odd n
if (i % 2 === 0) { // 奇数次谐波
const n = i + 1;
barHeight = (8 / (Math.PI * Math.PI * n * n)) * amplitude * (height / 2);
} else {
barHeight = 0;
}
}
// 绘制频谱柱
freqCtx.fillStyle = '#2a5a8f';
freqCtx.fillRect(x - barWidth/2, height - barHeight, barWidth, barHeight);
// 绘制边框
freqCtx.strokeStyle = '#4d9fff';
freqCtx.lineWidth = 1;
freqCtx.strokeRect(x - barWidth/2, height - barHeight, barWidth, barHeight);
// 绘制频率标签
if (barHeight > 15) {
freqCtx.fillStyle = 'white';
freqCtx.font = '10px Arial';
freqCtx.textAlign = 'center';
freqCtx.fillText(`${(i + 1) * frequency} Hz`, x, height - 8);
}
}
// 绘制标题
freqCtx.fillStyle = 'white';
freqCtx.font = '14px Arial';
freqCtx.textAlign = 'left';
freqCtx.fillText('频域分析', 10, 20);
}
// 绘制谐波分解
function drawHarmonics() {
const width = harmonicsCanvas.width;
const height = harmonicsCanvas.height;
const centerY = height / 2;
const harmonicHeight = height / (harmonics + 1);
// 清除画布
harmonicsCtx.clearRect(0, 0, width, height);
// 绘制每个谐波
for (let i = 0; i < harmonics; i++) {
const yOffset = (i + 1) * harmonicHeight;
const harmonicAmplitude = harmonicAmplitudes[i];
const harmonicPhase = harmonicPhases[i];
// 绘制谐波标签
harmonicsCtx.fillStyle = 'white';
harmonicsCtx.font = '10px Arial';
harmonicsCtx.fillText(`谐波 ${i+1} (${(i+1)*frequency} Hz)`, 10, yOffset - harmonicHeight/2 + 5);
// 绘制谐波波形
harmonicsCtx.strokeStyle = `hsl(${210 + i * 30}, 70%, 60%)`;
harmonicsCtx.lineWidth = 1.5;
harmonicsCtx.beginPath();
const samples = 150;
for (let j = 0; j <= samples; j++) {
const x = (j / samples) * width;
const t = (j / samples) * Math.PI * 4;
const y = yOffset + harmonicAmplitude * Math.sin((i+1) * frequency * t + harmonicPhase) * (harmonicHeight / 3);
if (j === 0) {
harmonicsCtx.moveTo(x, y);
} else {
harmonicsCtx.lineTo(x, y);
}
}
harmonicsCtx.stroke();
}
// 绘制标题
harmonicsCtx.fillStyle = 'white';
harmonicsCtx.font = '14px Arial';
harmonicsCtx.fillText('谐波分解', 10, 20);
}
// 绘制波形合成
function drawSynthesis() {
const width = synthesisCanvas.width;
const height = synthesisCanvas.height;
const centerY = height / 2;
// 清除画布
synthesisCtx.clearRect(0, 0, width, height);
// 绘制网格
synthesisCtx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
synthesisCtx.lineWidth = 1;
synthesisCtx.beginPath();
// 水平网格线
for (let y = 0; y < height; y += height / 4) {
synthesisCtx.moveTo(0, y);
synthesisCtx.lineTo(width, y);
}
// 垂直网格线
for (let x = 0; x < width; x += width / 8) {
synthesisCtx.moveTo(x, 0);
synthesisCtx.lineTo(x, height);
}
synthesisCtx.stroke();
// 绘制坐标轴
synthesisCtx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
synthesisCtx.lineWidth = 2;
synthesisCtx.beginPath();
synthesisCtx.moveTo(0, centerY);
synthesisCtx.lineTo(width, centerY);
synthesisCtx.stroke();
// 绘制合成过程
const samples = 300;
// 绘制每个步骤的合成结果
for (let step = 1; step <= synthesisStep; step++) {
synthesisCtx.strokeStyle = `hsla(${200 + step * 10}, 70%, 60%, ${0.3 + 0.7 * step / synthesisStep})`;
synthesisCtx.lineWidth = 1 + step / synthesisStep;
synthesisCtx.beginPath();
for (let i = 0; i <= samples; i++) {
const x = (i / samples) * width;
const t = (i / samples) * Math.PI * 4;
let y = centerY;
// 累加前step个谐波
for (let h = 0; h < step; h++) {
y += harmonicAmplitudes[h] * Math.sin((h+1) * frequency * t + harmonicPhases[h]) * (height / 4);
}
if (i === 0) {
synthesisCtx.moveTo(x, y);
} else {
synthesisCtx.lineTo(x, y);
}
}
synthesisCtx.stroke();
}
// 绘制标题
synthesisCtx.fillStyle = 'white';
synthesisCtx.font = '14px Arial';
synthesisCtx.fillText('波形合成过程', 10, 20);
}
// 更新谐波控制界面
function updateHarmonicsControls() {
const harmonicsControls = document.getElementById('harmonicsControls');
harmonicsControls.innerHTML = '';
for (let i = 0; i < harmonics; i++) {
const harmonicItem = document.createElement('div');
harmonicItem.className = 'harmonic-item';
const harmonicTitle = document.createElement('div');
harmonicTitle.className = 'harmonic-title';
harmonicTitle.innerHTML = `谐波 ${i+1} <span>${(i+1)*frequency} Hz</span>`;
const harmonicSliders = document.createElement('div');
harmonicSliders.className = 'harmonic-sliders';
// 振幅控制
const amplitudeControl = document.createElement('div');
amplitudeControl.className = 'harmonic-slider';
amplitudeControl.innerHTML = `
<label>振幅:</label>
<input type="range" data-index="${i}" min="0" max="1" step="0.01" value="${harmonicAmplitudes[i]}">
<span>${harmonicAmplitudes[i].toFixed(2)}</span>
`;
// 相位控制
const phaseControl = document.createElement('div');
phaseControl.className = 'harmonic-slider';
phaseControl.innerHTML = `
<label>相位:</label>
<input type="range" data-index="${i}" min="0" max="6.28" step="0.01" value="${harmonicPhases[i]}">
<span>${harmonicPhases[i].toFixed(2)}</span>
`;
harmonicSliders.appendChild(amplitudeControl);
harmonicSliders.appendChild(phaseControl);
harmonicItem.appendChild(harmonicTitle);
harmonicItem.appendChild(harmonicSliders);
harmonicsControls.appendChild(harmonicItem);
}
// 添加事件监听器
document.querySelectorAll('.harmonic-amplitude').forEach(slider => {
slider.addEventListener('input', function() {
const index = parseInt(this.getAttribute('data-index'));
harmonicAmplitudes[index] = parseFloat(this.value);
this.parentElement.querySelector('.harmonic-amplitude-value').textContent = this.value;
draw();
});
});
document.querySelectorAll('.harmonic-phase').forEach(slider => {
slider.addEventListener('input', function() {
const index = parseInt(this.getAttribute('data-index'));
harmonicPhases[index] = parseFloat(this.value);
this.parentElement.querySelector('.harmonic-phase-value').textContent = this.value;
draw();
});
});
}
// 绘制函数
function draw() {
drawTimeDomain();
drawFrequencyDomain();
drawHarmonics();
drawSynthesis();
}
// 事件监听器
frequencySlider.addEventListener('input', function() {
frequency = parseFloat(this.value);
updateValues();
updateHarmonicsFromWaveType();
draw();
});
amplitudeSlider.addEventListener('input', function() {
amplitude = parseFloat(this.value);
updateValues();
updateHarmonicsFromWaveType();
draw();
});
phaseSlider.addEventListener('input', function() {
phase = parseFloat(this.value);
updateValues();
draw();
});
harmonicsSlider.addEventListener('input', function() {
harmonics = parseInt(this.value);
updateValues();
initHarmonicsData();
draw();
});
synthesisStepSlider.addEventListener('input', function() {
synthesisStep = parseInt(this.value);
updateValues();
draw();
});
comparisonHarmonicsSlider.addEventListener('input', function() {
comparisonHarmonics = parseInt(this.value);
updateValues();
draw();
});
// 波形类型按钮事件
sineBtn.addEventListener('click', function() {
waveType = 'sine';
setActiveWaveButton(this);
updateHarmonicsFromWaveType();
draw();
});
squareBtn.addEventListener('click', function() {
waveType = 'square';
setActiveWaveButton(this);
updateHarmonicsFromWaveType();
draw();
});
sawtoothBtn.addEventListener('click', function() {
waveType = 'sawtooth';
setActiveWaveButton(this);
updateHarmonicsFromWaveType();
draw();
});
triangleBtn.addEventListener('click', function() {
waveType = 'triangle';
setActiveWaveButton(this);
updateHarmonicsFromWaveType();
draw();
});
function setActiveWaveButton(button) {
document.querySelectorAll('.btn').forEach(btn => {
btn.classList.remove('active');
});
button.classList.add('active');
}
// 预设按钮事件
presetButtons.forEach(button => {
button.addEventListener('click', function() {
const preset = this.getAttribute('data-preset');
switch(preset) {
case 'simple':
frequency = 2;
amplitude = 1;
phase = 0;
waveType = 'sine';
setActiveWaveButton(sineBtn);
break;
case 'complex':
frequency = 1;
amplitude = 1.5;
phase = 0.5;
waveType = 'square';
setActiveWaveButton(squareBtn);
break;
case 'heartbeat':
frequency = 1.2;
amplitude = 1.8;
phase = 0;
waveType = 'sawtooth';
setActiveWaveButton(sawtoothBtn);
break;
case 'voice':
frequency = 3;
amplitude = 0.8;
phase = 1.2;
waveType = 'sine';
setActiveWaveButton(sineBtn);
break;
}
frequencySlider.value = frequency;
amplitudeSlider.value = amplitude;
phaseSlider.value = phase;
updateValues();
updateHarmonicsFromWaveType();
draw();
});
});
// 标签切换事件
tabs.forEach(tab => {
tab.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');
// 更新活动标签
tabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
// 更新活动内容
tabContents.forEach(content => content.classList.remove('active'));
document.getElementById(`${tabId}-tab`).classList.add('active');
});
});
// 初始化
window.addEventListener('load', function() {
resizeCanvases();
updateValues();
initHarmonicsData();
});
window.addEventListener('resize', resizeCanvases);
</script>
</body>
</html> 目录 返回
首页
