布朗运动模拟:用动态网页直观理解微观粒子运动
布朗运动是一个经典的物理现象,描述了悬浮在液体或气体中的微粒由于分子热运动的随机碰撞而产生的不规则运动。这种现象最早由植物学家罗伯特·布朗在1827年观察到,并且在统计物理学和分子运动理论中具有重要意义。
为了帮助大家更好地理解布朗运动的随机性和统计特性,我开发了一个交互式的网页模拟工具。通过这个工具,用户可以动态观察多个粒子的布朗运动轨迹,并通过调整参数探索不同条件下的运动规律。
功能介绍
主要功能
-
动态显示多个粒子的布朗运动
- 模拟多个粒子在二维平面上的随机运动。
- 每个粒子具有独立的随机轨迹,展现布朗运动的核心特性。
-
可调节粒子数量
- 用户可以通过滑块选择粒子的数量,范围为 1 到 50。
- 粒子数量越多,模拟的效果越接近真实的布朗运动。
-
可调节运动速度
- 通过滑块调整粒子的运动速度,范围为 1 到 10。
- 速度越高,粒子的运动越快,轨迹变化越剧烈。
-
可调节轨迹长度
- 用户可以设置粒子的轨迹长度,范围为 0 到 100。
- 短轨迹适合观察粒子当前的运动状态,长轨迹则能展示粒子的历史路径。
-
轨迹显示开关
- 用户可以选择是否显示粒子的运动轨迹。
- 关闭轨迹后,仅显示粒子的位置,适合观察粒子的瞬时分布。
-
实时显示统计信息
- 平均位移:显示所有粒子的平均位移,帮助量化粒子的扩散程度。
- 模拟时长:记录模拟的持续时间,方便观察长时间运动的趋势。
交互控制
-
开始/暂停按钮
- 点击“开始模拟”按钮启动粒子的运动。
- 模拟过程中可以随时点击“暂停模拟”按钮停止动画。
-
重置按钮
- 点击“重置”按钮清空画布,重新初始化粒子的位置和轨迹。
-
滑块调节参数
- 粒子数量、速度和轨迹长度均可通过滑块实时调整,观察不同参数对布朗运动的影响。
-
轨迹显示开关
- 勾选或取消“显示运动轨迹”选项,切换轨迹的可见性。
视觉效果
-
随机彩色粒子
- 每个粒子随机分配颜色,便于区分不同粒子的运动轨迹。
-
轨迹渐变消失效果
- 粒子的轨迹逐渐消失,模拟现实中粒子运动轨迹的时间衰减。
-
平滑动画
- 使用Canvas绘制动画,确保粒子运动流畅,轨迹清晰。
- 使用Canvas绘制动画,确保粒子运动流畅,轨迹清晰。
特色功能
-
边界碰撞检测
- 当粒子接触画布边界时,会反弹回画布范围内,确保所有运动都在可视区域内进行。
-
位移统计计算
- 实时计算每个粒子的位移,并显示所有粒子的平均位移,帮助量化布朗运动的随机性。
-
时间记录
- 模拟过程中实时更新运行时间,便于观察长时间运动的统计特征。
使用说明
-
调整参数
- 使用滑块设置粒子数量、速度和轨迹长度。
- 勾选或取消“显示运动轨迹”选项,切换轨迹的显示状态。
-
开始模拟
- 点击“开始模拟”按钮启动粒子的布朗运动。
- 粒子会随机移动,其轨迹会实时绘制在画布上。
-
观察运动轨迹和统计数据
- 观察粒子的运动轨迹,注意不同粒子的随机性。
- 查看实时更新的平均位移和模拟时长,探索粒子运动的统计规律。
-
暂停或重置
- 点击“暂停模拟”按钮停止动画。
- 点击“重置”按钮清空画布并重新初始化粒子位置。
完整代码
以下是实现布朗运动模拟的完整HTML代码:
<!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>
</head>
<body class="bg-gray-100 p-8">
<div class="max-w-5xl mx-auto bg-white rounded-lg shadow-lg p-6">
<h1 class="text-3xl font-bold text-center mb-4">布朗运动模拟演示</h1>
<div class="mb-6">
<p class="text-gray-700 mb-4">
布朗运动是指悬浮在流体中的微粒在流体分子的碰撞下产生的持续不规则运动。
这个现象最早由植物学家罗伯特·布朗在1827年发现。本演示模拟了多个粒子的布朗运动轨迹。
</p>
</div>
<div class="flex gap-4 mb-6">
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700">粒子数量</label>
<input type="range" id="particleCount" min="1" max="50" value="10"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="text-sm text-gray-600" id="particleCountValue">当前粒子数:10</div>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700">运动速度</label>
<input type="range" id="speed" min="1" max="10" value="5"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="text-sm text-gray-600" id="speedValue">当前速度:5</div>
</div>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-700">轨迹长度</label>
<input type="range" id="trailLength" min="0" max="100" value="50"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="text-sm text-gray-600" id="trailLengthValue">轨迹长度:50</div>
</div>
</div>
<div class="flex gap-4 mb-6">
<button id="startBtn" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
开始模拟
</button>
<button id="resetBtn" class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600">
重置
</button>
<label class="flex items-center">
<input type="checkbox" id="showTrails" checked class="mr-2">
显示运动轨迹
</label>
</div>
<canvas id="canvas" class="w-full border border-gray-200 rounded bg-black"
width="800" height="600"></canvas>
<div class="mt-4 grid grid-cols-2 gap-4 text-center">
<div class="bg-gray-50 p-3 rounded">
<div class="text-sm text-gray-600">平均位移</div>
<div id="avgDisplacement" class="font-bold">0</div>
</div>
<div class="bg-gray-50 p-3 rounded">
<div class="text-sm text-gray-600">模拟时长</div>
<div id="simulationTime" class="font-bold">0s</div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let particles = [];
let isRunning = false;
let animationId;
let startTime = 0;
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.positions = [[x, y]];
this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
}
move(speed) {
// 随机运动
const angle = Math.random() * Math.PI * 2;
this.x += Math.cos(angle) * speed;
this.y += Math.sin(angle) * speed;
// 边界碰撞检测
if (this.x < 0) this.x = 0;
if (this.x > canvas.width) this.x = canvas.width;
if (this.y < 0) this.y = 0;
if (this.y > canvas.height) this.y = canvas.height;
// 记录轨迹
this.positions.push([this.x, this.y]);
const maxTrail = parseInt(document.getElementById('trailLength').value);
if (this.positions.length > maxTrail) {
this.positions.shift();
}
}
draw(showTrails) {
ctx.beginPath();
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
if (showTrails && this.positions.length > 1) {
ctx.beginPath();
ctx.moveTo(this.positions[0][0], this.positions[0][1]);
for (let i = 1; i < this.positions.length; i++) {
ctx.lineTo(this.positions[i][0], this.positions[i][1]);
}
ctx.strokeStyle = this.color;
ctx.lineWidth = 1;
ctx.stroke();
}
}
getDisplacement() {
const start = this.positions[0];
const end = this.positions[this.positions.length - 1];
return Math.sqrt(
Math.pow(end[0] - start[0], 2) +
Math.pow(end[1] - start[1], 2)
);
}
}
function initParticles() {
particles = [];
const count = parseInt(document.getElementById('particleCount').value);
for (let i = 0; i < count; i++) {
particles.push(new Particle(
Math.random() * canvas.width,
Math.random() * canvas.height
));
}
}
function updateStats() {
// 更新平均位移
const totalDisplacement = particles.reduce((sum, p) => sum + p.getDisplacement(), 0);
const avgDisplacement = totalDisplacement / particles.length;
document.getElementById('avgDisplacement').textContent =
Math.round(avgDisplacement * 100) / 100;
// 更新模拟时长
const currentTime = (Date.now() - startTime) / 1000;
document.getElementById('simulationTime').textContent =
`${Math.round(currentTime * 10) / 10}s`;
}
function animate() {
if (!isRunning) return;
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const speed = parseInt(document.getElementById('speed').value);
const showTrails = document.getElementById('showTrails').checked;
particles.forEach(particle => {
particle.move(speed);
particle.draw(showTrails);
});
updateStats();
animationId = requestAnimationFrame(animate);
}
// 事件监听器
document.getElementById('startBtn').addEventListener('click', () => {
isRunning = !isRunning;
document.getElementById('startBtn').textContent =
isRunning ? '暂停模拟' : '开始模拟';
if (isRunning) {
startTime = Date.now();
animate();
} else {
cancelAnimationFrame(animationId);
}
});
document.getElementById('resetBtn').addEventListener('click', () => {
isRunning = false;
cancelAnimationFrame(animationId);
document.getElementById('startBtn').textContent = '开始模拟';
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
initParticles();
startTime = Date.now();
document.getElementById('avgDisplacement').textContent = '0';
document.getElementById('simulationTime').textContent = '0s';
});
document.getElementById('particleCount').addEventListener('input', function() {
document.getElementById('particleCountValue').textContent =
`当前粒子数:${this.value}`;
});
document.getElementById('speed').addEventListener('input', function() {
document.getElementById('speedValue').textContent =
`当前速度:${this.value}`;
});
document.getElementById('trailLength').addEventListener('input', function() {
document.getElementById('trailLengthValue').textContent =
`轨迹长度:${this.value}`;
});
// 初始化
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
initParticles();
</script>
</body>
</html>
总结
通过这个布朗运动模拟工具,用户可以动态观察和探索布朗运动的随机性和统计特性。它不仅适合物理和数学领域的学习者,也为任何对随机运动感兴趣的人提供了一个有趣的交互式体验。如果你有任何问题或建议,欢迎留言交流!