点击网址即可观看并下载
HTML代码
<!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>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #1a2980, #26d0ce);
color: #333;
min-height: 100vh;
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 300px 1fr;
grid-template-rows: auto 1fr auto;
grid-gap: 20px;
grid-template-areas:
"header header"
"controls particles"
"knowledge knowledge";
min-height: 95vh;
}
.header {
grid-area: header;
text-align: center;
background-color: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
}
h1 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 2.5rem;
background: linear-gradient(to right, #3498db, #e74c3c);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.controls {
grid-area: controls;
background-color: rgba(255, 255, 255, 0.95);
padding: 25px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
}
.particles {
grid-area: particles;
background-color: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
}
.knowledge {
grid-area: knowledge;
background-color: rgba(255, 255, 255, 0.95);
padding: 25px;
border-radius: 15px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
margin-top: 20px;
}
h2 {
font-size: 1.6rem;
color: #2c3e50;
margin-bottom: 20px;
border-bottom: 3px solid #3498db;
padding-bottom: 8px;
}
.btn-group {
display: flex;
flex-direction: column;
gap: 15px;
}
.btn {
background: linear-gradient(145deg, #3498db, #2980b9);
color: white;
border: none;
padding: 14px 18px;
border-radius: 10px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
text-align: center;
font-weight: bold;
letter-spacing: 0.5px;
box-shadow: 0 4px 10px rgba(52, 152, 219, 0.3);
}
.btn:hover {
background: linear-gradient(145deg, #2980b9, #3498db);
transform: translateY(-3px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.25);
}
.btn:active {
transform: translateY(1px);
}
.btn.active {
background: linear-gradient(145deg, #e74c3c, #c0392b);
box-shadow: 0 4px 10px rgba(231, 76, 60, 0.3);
}
.normal-btn {
background: linear-gradient(145deg, #27ae60, #219653);
}
.normal-btn:hover {
background: linear-gradient(145deg, #219653, #27ae60);
}
.particles-container {
width: 100%;
height: 550px;
position: relative;
border: 2px solid #3498db;
border-radius: 10px;
overflow: hidden;
background: linear-gradient(to bottom, #f1f2f6, #dfe4ea);
box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.1);
}
.particle {
position: absolute;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
color: white;
transition: all 10s ease;
animation: float 10s infinite ease-in-out;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
z-index: 5;
}
.O2 {
background: radial-gradient(circle at 30% 30%, #ffff00, #ff9900);
width: 42px;
height: 42px;
}
.N2 {
background: linear-gradient(145deg, #3498db, #2980b9);
width: 38px;
height: 38px;
}
.P {
background: radial-gradient(circle at 30% 30%, #ff0000, #b30000);
width: 36px;
height: 36px;
}
.P2O5 {
background: linear-gradient(145deg, #ffffff, #e0e0e0);
color: #333;
border: 2px solid #ccc;
animation: none !important;
z-index: 2;
width: 42px;
height: 42px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
}
.heating .particle {
animation: float 5s infinite ease-in-out, heat 10s infinite;
transform: scale(1.3);
}
@keyframes float {
0%, 100% { transform: translate(0, 0); }
25% { transform: translate(8px, 10px); }
50% { transform: translate(-8px, 6px); }
75% { transform: translate(9px, -6px); }
}
@keyframes heat {
0%, 100% { box-shadow: 0 0 15px rgba(255, 100, 0, 0.9); }
50% { box-shadow: 0 0 25px rgba(255, 50, 0, 1); }
}
.knowledge-content {
line-height: 1.8;
font-size: 1.15rem;
}
.knowledge-point {
margin: 12px 0;
padding: 15px;
background-color: rgba(236, 240, 241, 0.7);
border-radius: 8px;
transition: all 0.4s ease;
border-left: 3px solid transparent;
}
.knowledge-point.active {
background-color: rgba(52, 152, 219, 0.2);
border-left: 4px solid #2980b9;
padding-left: 18px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
}
.value-display {
position: absolute;
top: 25px;
right: 25px;
background: rgba(255, 255, 255, 0.95);
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
font-weight: bold;
text-align: center;
z-index: 10;
opacity: 0;
transition: opacity 1s ease;
border: 2px solid #3498db;
width: 160px;
}
.value-display.visible {
opacity: 1;
}
.value-display h3 {
color: #2c3e50;
font-size: 1.1rem;
margin-bottom: 8px;
}
.oxygen-value {
font-size: 1.8rem;
color: #e74c3c;
font-weight: bold;
}
.explanation-panel {
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.95);
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
z-index: 10;
max-height: 80px;
font-size: 1.1rem;
line-height: 1.5;
max-width: 80%;
text-align: center;
opacity: 0;
transition: opacity 0.5s ease;
border: 2px solid #e74c3c;
}
.explanation-panel.visible {
opacity: 1;
}
.status-bar {
position: absolute;
top: 0;
left: 0;
width: 100%;
background: linear-gradient(to right, #3498db, #2980b9);
padding: 12px;
text-align: center;
font-weight: bold;
color: white;
font-size: 1.2rem;
z-index: 15;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
letter-spacing: 0.5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.water-level {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 0;
background: linear-gradient(to top, rgba(52, 152, 219, 0.8), rgba(41, 128, 185, 0.9));
transition: height 10s ease;
z-index: 1;
}
.division-line {
position: absolute;
left: 0;
width: 100%;
height: 2px;
background-color: rgba(0, 0, 0, 0.4);
z-index: 2;
}
.division-label {
position: absolute;
right: 10px;
font-size: 14px;
color: rgba(0, 0, 0, 0.7);
z-index: 3;
font-weight: bold;
background: rgba(255, 255, 255, 0.7);
padding: 2px 5px;
border-radius: 3px;
}
.error-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(231, 76, 60, 0.9);
color: white;
padding: 12px 25px;
border-radius: 8px;
font-weight: bold;
font-size: 1.4rem;
z-index: 20;
opacity: 0;
transition: opacity 0.5s ease;
}
.error-indicator.visible {
opacity: 1;
}
.pause-btn {
cursor: pointer;
padding: 5px 10px;
background: rgba(255, 255, 255, 0.3);
border-radius: 5px;
font-size: 1.3rem;
}
.experiment-tips {
margin-top: 20px;
padding: 15px;
background-color: rgba(255, 255, 240, 0.9);
border-radius: 8px;
border-left: 4px solid #f1c40f;
font-size: 1.05rem;
line-height: 1.7;
}
@media (max-width: 900px) {
.container {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"controls"
"particles"
"knowledge";
}
.particles-container {
height: 450px;
}
h1 {
font-size: 1.8rem;
}
.value-display {
top: 15px;
right: 15px;
padding: 10px;
width: 140px;
}
.explanation-panel {
top: 50px;
font-size: 1rem;
padding: 10px 15px;
max-height: 70px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>空气中氧气含量测定实验误差分析</h1>
<p>气体粒子微观模型与误差原理可视化</p>
</div>
<div class="controls">
<h2>误差模拟选择</h2>
<div class="btn-group">
<button class="btn normal-btn" id="normalBtn">正常操作实验</button>
<button class="btn" id="error1Btn">止水夹未夹紧</button>
<button class="btn" id="error2Btn">未及时塞紧瓶塞</button>
<button class="btn" id="error3Btn">红磷量不足</button>
<button class="btn" id="error4Btn">未冷却至室温</button>
<button class="btn" id="error5Btn">装置漏气</button>
</div>
<div style="margin-top: 35px;">
<h2>实验原理</h2>
<p style="margin-top: 15px; line-height: 1.8; font-size: 1.15rem;">
<strong>红磷燃烧反应:</strong> 4P + 5O₂ → 2P₂O₅₅<br>
氧气消耗后,气体体积减少,冷却后水进入集气瓶占据被消耗氧气的空间。<br>
<strong>理想结果:</strong> 水位上升至空气总体积的1/5处(约21%)。
</p>
</div>
<div style="margin-top: 25px;">
<h2>实验说明</h2>
<p style="margin-top: 15px; line-height: 1.7;">
1. 点击上方按钮选择实验类型<br>
2. 观察气体粒子微观行为变化<br>
3. 查看水位变化和氧气含量结果<br>
4. 阅读下方误差分析原理<br>
5. 使用暂停按钮控制动画
</p>
</div>
<div class="experiment-tips">
<strong>教学提示:</strong><br>
• 红磷燃烧后生成白色固体五氧化二磷(P₂O₅)<br>
• 粒子颜色:黄色-氧气(O₂), 蓝色-氮气(N₂), 红色-红磷(P)<br>
• 固体五氧化二磷会沉到容器底部<br>
• 不同误差类型导致水位变化不同
</div>
</div>
<div class="particles">
<h2>气体粒子微观模型</h2>
<div class="particles-container" id="particlesContainer">
<div class="status-bar" id="statusBar">
<span>准备开始实验</span>
<div class="pause-btn" id="pauseBtn">⏸⏸⏸</div>
</div>
<div class="error-indicator" id="errorIndicator">出现误差!</div>
<div class="value-display" id="valueDisplay">
<h3>氧气含量测定结果</h3>
<div class="oxygen-value" id="oxygenValue">21.0%</div>
</div>
<div class="water-level" id="waterLevel"></div>
<div class="explanation-panel" id="explanationPanel">
正常操作:红磷燃烧消耗氧气,冷却后水进入集气瓶约占空气体积的1/5(约21%)。
</div>
</div>
</div>
<div class="knowledge">
<h2>误差原理详细分析</h2>
<div class="knowledge-content">
<div class="knowledge-point active" id="knowledgePoint1">
<strong>正常操作:</strong> 红磷燃烧消耗氧气,反应式为:4P + 5O₂ → 2P₂O₅₅。待冷却至室温后,水进入集气瓶占据被消耗氧气的空间。理想情况下水位上升至空气总体积的1/5处(约21%),证明氧气约占空气体积的21%。
</div>
<div class="knowledge-point" id="knowledgePoint2">
<strong>止水夹未夹紧:</strong> 实验过程中气体从导管逸出,使瓶内气体总量减少。冷却后打开止水夹,水进入量大于正常值(结果偏大)。<strong>微观解释:</strong> 气体粒子(主要是N₂)从导管逸出,导致瓶内粒子总数减少,水进入比例增加。
</div>
<div class="knowledge-point" id="knowledgePoint3">
<strong>未及时塞紧瓶塞:</strong> 红磷放入时瓶塞未及时塞紧,部分气体(含氧气和氮气)从瓶口逸出。冷却后瓶内气体总量少于正常情况,水进入量大于正常值(结果偏大)。<strong>微观解释:</strong> 粒子从瓶口逸出,包括O₂和N₂,导致粒子总数减少。
</div>
<div class="knowledge-point" id="knowledgePoint4">
<strong>红磷量不足:</strong> 红磷量不足无法完全消耗氧气,瓶中仍有部分氧气残留。冷却后打开止水夹,水进入量小于正常值(结果偏小)。<strong>微观解释:</strong> 部分O₂粒子未被消耗,瓶内气体总量减少程度不足,水上升高度不足。
</div>
<div class="knowledge-point" id="knowledgePoint5">
<strong>未冷却至室温:</strong> 未冷却即打开止水夹,瓶内气体温度高,体积膨胀(压强较大),进入的水量小于冷却后应有的量(结果偏小)。<strong>微观解释:</strong> 高温使粒子运动加剧,占据更大空间,水无法充分进入。
</div>
<div class="knowledge-point" id="knowledgePoint6">
<strong>装置漏气:</strong> 装置存在漏气点,冷却过程中外界空气进入瓶内补充氧气。打开止水夹后,水进入量少于正常值(结果偏小)。<strong>微观解释:</strong> 外界空气(含O₂和N₂)进入瓶内,补充了被消耗的O₂,导致气体总量减少不足。
</div>
</div>
</div>
</div>
<script>
// 粒子定义
const particles = {
O2: { count: 21, label: "O₂", className: "O2" },
N2: { count: 79, label: "N₂", className: "N2" },
P: { count: 20, label: "P", className: "P" }
};
// 误差解释文本
const explanations = {
normal: "正常操作:红磷燃烧消耗氧气,冷却后水进入集气瓶约占空气体积的1/5(约21%)。",
error1: "止水夹未夹紧:气体从导管逸出(主要是N₂),瓶内粒子总数减少,水进入比例增加(结果偏大)。",
error2: "未及时塞紧瓶塞:气体从瓶口逸出(含O₂和N₂),瓶内粒子总数减少,水进入比例增加(结果偏大)。",
error3: "红磷量不足:部分O₂粒子未被消耗,瓶内气体总量减少程度不足,水上升高度不足(结果偏小)。",
error4: "未冷却至室温:高温使粒子运动加剧,占据更大空间,水无法充分进入(结果偏小)。",
error5: "装置漏气:外界空气(含O₂和N₂)进入瓶内,补充了被消耗的O₂,导致气体总量减少不足(结果偏小)。"
};
// 实验状态
let currentError = null;
let isHeating = false;
let animationTimer = null;
let isPaused = false;
let activeTimers = [];
// 初始化粒子系统
function initParticles() {
const container = document.getElementById('particlesContainer');
container.innerHTML = '';
// 添加状态条
const statusBar = document.createElement('div');
statusBar.id = 'statusBar';
statusBar.className = 'status-bar';
statusBar.innerHTML = '<span>准备开始实验</span><div class="pause-btn" id="pauseBtn">⏸⏸⏸</div>';
container.appendChild(statusBar);
// 添加暂停按钮事件
document.getElementById('pauseBtn').addEventListener('click', togglePause);
// 添加五等分标记线
for (let i = 1; i <= 4; i++) {
const line = document.createElement('div');
line.className = 'division-line';
line.style.top = `${i * 20}%`;
container.appendChild(line);
const label = document.createElement('div');
label.className = 'division-label';
label.style.top = `${i * 20}%`;
label.textContent = `${i * 20}%`;
container.appendChild(label);
}
// 添加水位
const waterLevel = document.createElement('div');
waterLevel.id = 'waterLevel';
waterLevel.className = 'water-level';
container.appendChild(waterLevel);
// 添加粒子
Object.entries(particles).forEach(([key, config]) => {
for (let i = 0; i < config.count; i++) {
createParticle(container, config.className, config.label);
}
});
// 添加误差指示器
const errorIndicator = document.createElement('div');
errorIndicator.id = 'errorIndicator';
errorIndicator.className = 'error-indicator';
errorIndicator.textContent = '出现误差!';
container.appendChild(errorIndicator);
// 添加解释面板
const explanationPanel = document.createElement('div');
explanationPanel.id = 'explanationPanel';
explanationPanel.className = 'explanation-panel';
explanationPanel.textContent = explanations.normal;
container.appendChild(explanationPanel);
// 添加值显示
const valueDisplay = document.createElement('div');
valueDisplay.id = 'valueDisplay';
valueDisplay.className = 'value-display';
valueDisplay.innerHTML = `
<h3>氧气含量测定结果</h3>
<div class="oxygen-value" id="oxygenValue">21.0%</div>
`;
container.appendChild(valueDisplay);
}
// 暂停/继续动画
function togglePause() {
isPaused = !isPaused;
const pauseBtn = document.getElementById('pauseBtn');
if (isPaused) {
pauseBtn.textContent = '▶';
pauseAllAnimations();
updateStatus('实验暂停');
} else {
pauseBtn.textContent = '⏸⏸⏸';
resumeAllAnimations();
updateStatus('实验继续');
}
}
// 暂停所有动画
function pauseAllAnimations() {
// 暂停粒子动画
const allParticles = document.querySelectorAll('.particle');
allParticles.forEach(p => {
p.style.animationPlayState = 'paused';
});
// 暂停水位上升
const waterLevel = document.getElementById('waterLevel');
if (waterLevel) {
const computedStyle = getComputedStyle(waterLevel);
const currentHeight = parseFloat(computedStyle.height);
waterLevel.style.height = `${currentHeight}%`;
waterLevel.style.transition = 'none';
}
// 清除所有计时器
activeTimers.forEach(timer => clearTimeout(timer));
activeTimers = [];
}
// 继续所有动画
function resumeAllAnimations() {
// 继续粒子动画
const allParticles = document.querySelectorAll('.particle');
allParticles.forEach(p => {
p.style.animationPlayState = 'running';
});
// 恢复水位过渡效果
const waterLevel = document.getElementById('waterLevel');
if (waterLevel) {
waterLevel.style.transition = 'height 10s ease';
}
}
// 创建定时器(可被暂停)
function createTimer(func, delay) {
if (isPaused) return;
const timer = setTimeout(() => {
if (!isPaused) {
func();
}
}, delay);
activeTimers.push(timer);
return timer;
}
// 更新状态条
function updateStatus(text) {
const statusBar = document.getElementById('statusBar');
if (statusBar) {
statusBar.querySelector('span').textContent = text;
}
}
// 显示/隐藏误差指示器
function toggleErrorIndicator(show) {
const indicator = document.getElementById('errorIndicator');
if (indicator) {
indicator.className = show ? 'error-indicator visible' : 'error-indicator';
}
}
// 显示误差解释面板
function showErrorExplanation(text) {
const explanationPanel = document.getElementById('explanationPanel');
if (explanationPanel) {
explanationPanel.textContent = text;
explanationPanel.classList.add('visible');
// 5秒后自动隐藏
setTimeout(() => {
explanationPanel.classList.remove('visible');
}, 5000);
}
}
// 创建单个粒子
function createParticle(container, className, label) {
const particle = document.createElement('div');
particle.className = `particle ${className}`;
particle.innerHTML = label;
// 随机位置(上半部分)
const posX = Math.random() * (container.offsetWidth - 50);
const posY = Math.random() * (container.offsetHeight * 0.8);
particle.style.left = `${posX}px`;
particle.style.top = `${posY}px`;
// 随机动画延迟
particle.style.animationDelay = `${Math.random() * 10}s`;
container.appendChild(particle);
}
// 移除部分氧气
function removeOxygen(ratio) {
const O2Particles = document.querySelectorAll('.particle.O2');
const toRemove = Math.floor(O2Particles.length * ratio);
// 移除部分O2粒子
for (let i = 0; i < toRemove; i++) {
if (i < O2Particles.length) {
O2Particles[i].style.opacity = '0';
O2Particles[i].style.transform = 'scale(0)';
}
}
}
// 模拟气体逸出
function simulateLeak(count, direction) {
const allParticles = document.querySelectorAll('.particle:not(.P)');
const toRemove = Math.min(count, allParticles.length);
// 移除部分粒子
for (let i = 0; i < toRemove; i++) {
const particle = allParticles[Math.floor(Math.random() * allParticles.length)];
// 创建逸出动画
particle.style.transition = 'all 10s ease';
if (direction === 'right') {
particle.style.transform = `translateX(${Math.random() * 100 + 100}px)`;
} else {
particle.style.transform = `translateY(${-Math.random() * 100 - 100}px)`;
}
particle.style.opacity = '0';
}
}
// 转换红磷为五氧化二磷
function convertPhosphorus(convertCount) {
const PParticles = document.querySelectorAll('.particle.P');
const count = Math.min(convertCount, PParticles.length);
// 随机选择部分红磷进行转换
const indices = new Set();
while (indices.size < count) {
indices.add(Math.floor(Math.random() * PParticles.length));
}
Array.from(indices).forEach(index => {
const particle = PParticles[index];
// 转换红磷为五氧化二磷
particle.className = 'particle P2O5';
particle.innerHTML = 'P₂O₅';
// 添加转换动画
particle.style.transition = 'all 10s ease';
particle.style.transform = 'scale(1.5)';
// 移动到容器底部(固体沉底)
const container = document.getElementById('particlesContainer');
const containerHeight = container.offsetHeight;
const particleHeight = particle.offsetHeight;
createTimer(() => {
particle.style.top = `${containerHeight - particleHeight - 20}px`;
particle.style.transform = 'scale(1)';
particle.style.zIndex = '2';
}, 500);
});
}
// 温度升高(粒子运动加剧)
function heatParticles() {
if (isHeating) return;
isHeating = true;
const allParticles = document.querySelectorAll('.particle');
allParticles.forEach(p => p.classList.add('heating'));
}
// 冷却粒子
function coolParticles() {
isHeating = false;
const allParticles = document.querySelectorAll('.particle');
allParticles.forEach(p => p.classList.remove('heating'));
}
// 添加外界空气(装置漏气)
function addExternalAir() {
const container = document.getElementById('particlesContainer');
// 添加4个氮气粒子
for (let i = 0; i < 4; i++) {
createTimer(() => {
const particle = document.createElement('div');
particle.className = 'particle N2';
particle.innerHTML = 'N₂';
// 从顶部进入
particle.style.left = `${Math.random() * (container.offsetWidth - 50)}px`;
particle.style.top = '-50px';
particle.style.opacity = '0';
container.appendChild(particle);
// 动画:向下移动并显示
createTimer(() => {
particle.style.transition = 'all 10s ease';
particle.style.top = `${Math.random() * (container.offsetHeight * 0.8)}px`;
particle.style.opacity = '1';
}, 100);
}, i * 1000);
}
// 添加1个氧气粒子
createTimer(() => {
const particle = document.createElement('div');
particle.className = 'particle O2';
particle.innerHTML = 'O₂';
// 从顶部进入
particle.style.left = `${Math.random() * (container.offsetWidth - 50)}px`;
particle.style.top = '-50px';
particle.style.opacity = '0';
container.appendChild(particle);
// 动画:向下移动并显示
createTimer(() => {
particle.style.transition = 'all 10s ease';
particle.style.top = `${Math.random() * (container.offsetHeight * 0.8)}px`;
particle.style.opacity = '1';
}, 100);
}, 4000);
}
// 更新氧气含量显示
function updateOxygenValue(percent) {
const oxygenValue = document.getElementById('oxygenValue');
oxygenValue.textContent = `${percent.toFixed(1)}%`;
// 根据值调整颜色
if (percent > 21.1) {
oxygenValue.style.color = '#27ae60';
} else if (percent < 20.9) {
oxygenValue.style.color = '#e74c3c';
} else {
oxygenValue.style.color = '#3498db';
}
}
// 更新水位高度
function updateWaterLevel(percent) {
const waterLevel = document.getElementById('waterLevel');
waterLevel.style.height = `${percent}%`;
}
// 更新解释面板
function updateExplanation(type) {
const explanationPanel = document.getElementById('explanationPanel');
explanationPanel.textContent = explanations[type];
}
// 显示面板(值显示和解释面板)
function showPanels() {
const valueDisplay = document.getElementById('valueDisplay');
const explanationPanel = document.getElementById('explanationPanel');
if (valueDisplay) valueDisplay.classList.add('visible');
if (explanationPanel) explanationPanel.classList.add('visible');
}
// 隐藏面板
function hidePanels() {
const valueDisplay = document.getElementById('valueDisplay');
const explanationPanel = document.getElementById('explanationPanel');
if (valueDisplay) valueDisplay.classList.remove('visible');
if (explanationPanel) explanationPanel.classList.remove('visible');
}
// 重置实验
function resetExperiment() {
// 清除之前的定时器
activeTimers.forEach(timer => clearTimeout(timer));
activeTimers = [];
// 重置粒子
initParticles();
// 重置水位
updateWaterLevel(0);
// 重置加热状态
coolParticles();
// 隐藏面板
hidePanels();
toggleErrorIndicator(false);
// 重置知识点高亮
document.querySelectorAll('.knowledge-point').forEach(point => {
point.classList.remove('active');
});
document.getElementById('knowledgePoint1').classList.add('active');
// 更新解释
updateExplanation('normal');
// 更新状态
updateStatus('准备开始实验');
// 重置按钮状态
document.querySelectorAll('.btn').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById('normalBtn').classList.add('active');
// 重置暂停状态
isPaused = false;
document.getElementById('pauseBtn').textContent = '⏸⏸⏸';
}
// 执行实验
function runExperiment(errorType = null) {
resetExperiment();
currentError = errorType;
// 高亮当前按钮
document.querySelectorAll('.btn').forEach(btn => btn.classList.remove('active'));
if (errorType === null) {
document.getElementById('normalBtn').classList.add('active');
} else {
document.getElementById(`error${errorType}Btn`).classList.add('active');
}
// 开始燃烧
updateStatus('点燃红磷...');
heatParticles(); // 产生热量
createTimer(() => {
// 显示误差提示(如果有)
if (errorType) {
toggleErrorIndicator(true);
// 根据错误类型显示不同的解释
switch (errorType) {
case 1:
showErrorExplanation("止水夹未夹紧:气体从导管逸出");
break;
case 2:
showErrorExplanation("未及时塞紧瓶塞:气体从瓶口逸出");
break;
case 3:
showErrorExplanation("红磷量不足:氧气未被完全消耗");
break;
case 4:
showErrorExplanation("未冷却至室温:气体体积膨胀");
break;
case 5:
showErrorExplanation("装置漏气:外界空气进入瓶内");
break;
}
}
// 消耗氧气并转换红磷
switch (errorType) {
case 1: // 止水夹未夹紧
simulateLeak(15, 'right'); // 逸出约15个粒子
removeAllOxygen(); // 移除所有氧气(完全消耗)
convertPhosphorus(16); // 转换16个红磷(保留4个)
updateExplanation('error1');
break;
case 2: // 未及时塞紧瓶塞
simulateLeak(12, 'top'); // 逸出12个粒子
removeAllOxygen(); // 移除所有氧气(完全消耗)
convertPhosphorus(16); // 转换16个红磷(保留4个)
updateExplanation('error2');
break;
case 3: // 红磷量不足
// 先转换8个红磷(因为量不足)
convertPhosphorus(8);
// 移除所有未被转换的红磷(因为量不足)
createTimer(() => {
removeAllPhosphorus();
}, 1000);
removeOxygen(0.5); // 只消耗50%氧气
updateExplanation('error3');
break;
case 4: // 未冷却至室温
// 正常消耗氧气,但不冷却
removeAllOxygen();
convertPhosphorus(16); // 转换16个红磷(保留4个)
updateExplanation('error4');
break;
case 5: // 装置漏气
removeAllOxygen();
convertPhosphorus(16); // 转换16个红磷(保留4个)
updateExplanation('error5');
break;
default: // 正常操作
removeAllOxygen(); // 消耗全部氧气
convertPhosphorus(16); // 转换16个红磷(保留4个)
updateExplanation('normal');
break;
}
// 等待五氧化二磷沉降
createTimer(() => {
updateStatus('燃烧结束');
// 冷却阶段(如果适用)
createTimer(() => {
if (errorType !== 4) {
updateStatus('冷却至室温...');
coolParticles();
}
// 装置漏气:添加外界空气
if (errorType === 5) {
updateStatus('装置漏气,外界空气进入...');
addExternalAir();
}
// 打开止水夹
createTimer(() => {
updateStatus('打开止水夹,水进入集气瓶...');
// 计算最终氧气含量和水位高度
let oxygenPercentage, waterLevel;
switch (errorType) {
case 1: // 止水夹未夹紧(结果偏大)
oxygenPercentage = 21 + Math.random() * 5 + 3;
waterLevel = oxygenPercentage;
break;
case 2: // 未及时塞紧瓶塞(结果偏大)
oxygenPercentage = 21 + Math.random() * 4 + 2.5;
waterLevel = oxygenPercentage;
break;
case 3: // 红磷量不足(结果偏小)
oxygenPercentage = 21 - Math.random() * 5 - 5;
waterLevel = oxygenPercentage;
break;
case 4: // 未冷却至室温(结果偏小)
oxygenPercentage = 21 - Math.random() * 4 - 4;
waterLevel = oxygenPercentage;
break;
case 5: // 装置漏气(结果偏小)
oxygenPercentage = 21 - Math.random() * 6 - 4;
waterLevel = oxygenPercentage;
break;
default: // 正常情况
oxygenPercentage = 21;
waterLevel = 21;
break;
}
// 更新水位(装置漏气需要等待外界空气进入后再更新)
if (errorType === 5) {
createTimer(() => {
updateOxygenValue(oxygenPercentage);
updateWaterLevel(waterLevel);
// 水位动画结束后10秒显示面板
createTimer(() => {
updateStatus('实验完成');
showPanels();
toggleErrorIndicator(false);
}, 10000); // 10秒后显示面板
}, 5000);
} else {
updateOxygenValue(oxygenPercentage);
updateWaterLevel(waterLevel);
// 水位动画结束后10秒显示面板
createTimer(() => {
updateStatus('实验完成');
showPanels();
toggleErrorIndicator(false);
}, 10000); // 10秒后显示面板
}
// 高亮对应知识点
document.querySelectorAll('.knowledge-point').forEach(point => {
point.classList.remove('active');
});
const pointId = errorType === null ? 1 : errorType + 1;
document.getElementById(`knowledgePoint${pointId}`).classList.add('active');
}, 5000); // 等待5秒后打开止水夹
}, 5000); // 冷却阶段延长至5秒
}, 10000); // 等待10秒让五氧化二磷沉降
}, 5000); // 燃烧阶段延长至5秒
}
// 移除所有氧气
function removeAllOxygen() {
const O2Particles = document.querySelectorAll('.particle.O2');
O2Particles.forEach(p => {
p.style.opacity = '0';
p.style.transform = 'scale(0)';
});
}
// 移除所有红磷
function removeAllPhosphorus() {
const PParticles = document.querySelectorAll('.particle.P');
PParticles.forEach(p => p.remove());
}
// 绑定按钮事件
document.getElementById('normalBtn').addEventListener('click', () => runExperiment(null));
document.getElementById('error1Btn').addEventListener('click', () => runExperiment(1));
document.getElementById('error2Btn').addEventListener('click', () => runExperiment(2));
document.getElementById('error3Btn').addEventListener('click', () => runExperiment(3));
document.getElementById('error4Btn').addEventListener('click', () => runExperiment(4));
document.getElementById('error5Btn').addEventListener('click', () => runExperiment(5));
// 初始化
window.addEventListener('load', () => {
initParticles();
resetExperiment();
});
</script>
</body>
</html>