785 lines
27 KiB
HTML
785 lines
27 KiB
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>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||
}
|
||
|
||
body {
|
||
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
|
||
color: #fff;
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
}
|
||
|
||
header {
|
||
grid-column: 1 / -1;
|
||
text-align: center;
|
||
padding: 20px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 15px;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
h1 {
|
||
font-size: 2.5rem;
|
||
margin-bottom: 10px;
|
||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 1.2rem;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.panel {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(10px);
|
||
border-radius: 15px;
|
||
padding: 20px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||
}
|
||
|
||
.panel-title {
|
||
font-size: 1.5rem;
|
||
margin-bottom: 15px;
|
||
text-align: center;
|
||
color: #ffcc00;
|
||
text-shadow: 0 0 10px rgba(255, 204, 0, 0.5);
|
||
}
|
||
|
||
.simulation-area {
|
||
position: relative;
|
||
height: 400px;
|
||
background: linear-gradient(to bottom, #87CEEB, #1E90FF);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.liquid {
|
||
position: absolute;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 60%;
|
||
background: rgba(30, 144, 255, 0.7);
|
||
}
|
||
|
||
.liquid-surface {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 2px;
|
||
background: rgba(255, 255, 255, 0.5);
|
||
bottom: 40%; /* 液面高度为60% */
|
||
}
|
||
|
||
.object {
|
||
position: absolute;
|
||
width: 60px;
|
||
height: 60px;
|
||
background: #FF6B6B;
|
||
border: 2px solid #fff;
|
||
border-radius: 5px;
|
||
cursor: move;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: bold;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.object.dragging {
|
||
box-shadow: 0 0 15px #FFD700;
|
||
z-index: 10;
|
||
}
|
||
|
||
.force-diagram {
|
||
height: 400px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
}
|
||
|
||
.force-arrow {
|
||
position: absolute;
|
||
width: 8px;
|
||
transform-origin: bottom center;
|
||
}
|
||
|
||
.gravity-arrow {
|
||
background: #FF6B6B;
|
||
left: 50%;
|
||
bottom: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.buoyancy-arrow {
|
||
background: #4ECDC4;
|
||
left: 50%;
|
||
bottom: 50%;
|
||
transform: translateX(-50%) rotate(180deg);
|
||
}
|
||
|
||
.arrow-head {
|
||
position: absolute;
|
||
top: -10px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 10px solid transparent;
|
||
border-right: 10px solid transparent;
|
||
}
|
||
|
||
.gravity-arrow .arrow-head {
|
||
border-top: 15px solid #FF6B6B;
|
||
}
|
||
|
||
.buoyancy-arrow .arrow-head {
|
||
border-bottom: 15px solid #4ECDC4;
|
||
top: auto;
|
||
bottom: -10px;
|
||
}
|
||
|
||
.force-label {
|
||
position: absolute;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.gravity-label {
|
||
color: #FF6B6B;
|
||
top: -30px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.buoyancy-label {
|
||
color: #4ECDC4;
|
||
bottom: -30px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.controls {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 15px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.control-group {
|
||
background: rgba(0, 0, 0, 0.2);
|
||
padding: 15px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.control-label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.slider-container {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
input[type="range"] {
|
||
flex: 1;
|
||
height: 8px;
|
||
-webkit-appearance: none;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 4px;
|
||
outline: none;
|
||
}
|
||
|
||
input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: #FFD700;
|
||
cursor: pointer;
|
||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.value-display {
|
||
width: 60px;
|
||
text-align: right;
|
||
font-weight: bold;
|
||
color: #FFD700;
|
||
}
|
||
|
||
.scenarios {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.scenario-btn {
|
||
flex: 1;
|
||
margin: 0 5px;
|
||
padding: 12px;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border: none;
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.scenario-btn:hover {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.scenario-btn.active {
|
||
background: #FFD700;
|
||
color: #1a2a6c;
|
||
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
|
||
}
|
||
|
||
.formula-area {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background: rgba(0, 0, 0, 0.2);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.formula {
|
||
font-size: 1.2rem;
|
||
text-align: center;
|
||
margin: 15px 0;
|
||
font-family: 'Cambria Math', serif;
|
||
}
|
||
|
||
.condition {
|
||
text-align: center;
|
||
font-size: 1.1rem;
|
||
margin: 10px 0;
|
||
padding: 10px;
|
||
border-radius: 8px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.condition.floating {
|
||
background: rgba(78, 205, 196, 0.3);
|
||
color: #4ECDC4;
|
||
}
|
||
|
||
.condition.sinking {
|
||
background: rgba(255, 107, 107, 0.3);
|
||
color: #FF6B6B;
|
||
}
|
||
|
||
.condition.suspended {
|
||
background: rgba(255, 215, 0, 0.3);
|
||
color: #FFD700;
|
||
}
|
||
|
||
.condition.submerged {
|
||
background: rgba(106, 90, 205, 0.3);
|
||
color: #6A5ACD;
|
||
}
|
||
|
||
.explanation {
|
||
margin-top: 15px;
|
||
font-size: 0.95rem;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.status-indicator {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
margin-top: 15px;
|
||
}
|
||
|
||
.status-item {
|
||
text-align: center;
|
||
padding: 8px;
|
||
border-radius: 8px;
|
||
font-size: 0.9rem;
|
||
flex: 1;
|
||
margin: 0 5px;
|
||
opacity: 0.7;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.status-item.active {
|
||
opacity: 1;
|
||
transform: scale(1.05);
|
||
box-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
|
||
}
|
||
|
||
.status-item.floating {
|
||
background: rgba(78, 205, 196, 0.3);
|
||
border: 1px solid #4ECDC4;
|
||
}
|
||
|
||
.status-item.suspended {
|
||
background: rgba(255, 215, 0, 0.3);
|
||
border: 1px solid #FFD700;
|
||
}
|
||
|
||
.status-item.sinking {
|
||
background: rgba(255, 107, 107, 0.3);
|
||
border: 1px solid #FF6B6B;
|
||
}
|
||
|
||
.status-item.submerged {
|
||
background: rgba(106, 90, 205, 0.3);
|
||
border: 1px solid #6A5ACD;
|
||
}
|
||
|
||
footer {
|
||
grid-column: 1 / -1;
|
||
text-align: center;
|
||
padding: 20px;
|
||
margin-top: 20px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 15px;
|
||
font-size: 0.9rem;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.scenarios {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.scenario-btn {
|
||
margin: 5px 0;
|
||
}
|
||
|
||
.status-indicator {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.status-item {
|
||
flex: 0 0 45%;
|
||
margin: 5px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<h1>浮力仿真实验室</h1>
|
||
<p class="subtitle">精确模拟物体在液体中的四种浮沉状态</p>
|
||
</header>
|
||
|
||
<div class="panel">
|
||
<h2 class="panel-title">实时仿真实验区</h2>
|
||
<div class="simulation-area">
|
||
<div class="liquid"></div>
|
||
<div class="liquid-surface"></div>
|
||
<div class="object" id="draggable-object">物体</div>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<div class="control-group">
|
||
<label class="control-label">物体密度 (kg/m³)</label>
|
||
<div class="slider-container">
|
||
<input type="range" id="object-density" min="100" max="2000" value="800" step="10">
|
||
<span class="value-display" id="object-density-value">800</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label class="control-label">液体密度 (kg/m³)</label>
|
||
<div class="slider-container">
|
||
<input type="range" id="liquid-density" min="500" max="2000" value="1000" step="10">
|
||
<span class="value-display" id="liquid-density-value">1000</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="scenarios">
|
||
<button class="scenario-btn active" data-scenario="wood">木块漂浮</button>
|
||
<button class="scenario-btn" data-scenario="submarine">潜水艇悬浮</button>
|
||
<button class="scenario-btn" data-scenario="stone">石块沉底</button>
|
||
<button class="scenario-btn" data-scenario="iron">铁块沉没悬浮</button>
|
||
</div>
|
||
|
||
<div class="status-indicator">
|
||
<div class="status-item floating" id="floating-status">漂浮</div>
|
||
<div class="status-item suspended" id="suspended-status">悬浮</div>
|
||
<div class="status-item sinking" id="sinking-status">沉底</div>
|
||
<div class="status-item submerged" id="submerged-status">沉没悬浮</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h2 class="panel-title">动态受力分析</h2>
|
||
<div class="force-diagram">
|
||
<div class="force-arrow gravity-arrow" id="gravity-arrow">
|
||
<div class="arrow-head"></div>
|
||
<div class="force-label gravity-label" id="gravity-label">重力: 0 N</div>
|
||
</div>
|
||
<div class="force-arrow buoyancy-arrow" id="buoyancy-arrow">
|
||
<div class="arrow-head"></div>
|
||
<div class="force-label buoyancy-label" id="buoyancy-label">浮力: 0 N</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="formula-area">
|
||
<div class="formula">F<sub>浮</sub> = ρ<sub>液</sub> × g × V<sub>排</sub></div>
|
||
<div class="formula">G = m × g = ρ<sub>物</sub> × V<sub>物</sub> × g</div>
|
||
|
||
<div class="condition" id="condition-display">
|
||
物体悬浮 (F<sub>浮</sub> = G)
|
||
</div>
|
||
|
||
<div class="explanation" id="explanation">
|
||
当物体密度等于液体密度时,物体悬浮在液体中。
|
||
</div>
|
||
|
||
<div class="explanation">
|
||
<strong>四种浮沉状态:</strong>
|
||
<ul style="margin-top: 10px; padding-left: 20px;">
|
||
<li><span style="color: #4ECDC4">漂浮</span>: ρ<sub>物</sub> < ρ<sub>液</sub>,物体部分露出液面</li>
|
||
<li><span style="color: #FFD700">悬浮</span>: ρ<sub>物</sub> = ρ<sub>液</sub>,物体完全浸没但不沉底</li>
|
||
<li><span style="color: #FF6B6B">沉底</span>: ρ<sub>物</sub> > ρ<sub>液</sub>,物体沉到容器底部</li>
|
||
<li><span style="color: #6A5ACD">沉没悬浮</span>: 沉底但未完全浸没</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<footer>
|
||
<p>浮力仿真实验室改进版 | 基于阿基米德原理 | 精确模拟四种浮沉状态</p>
|
||
</footer>
|
||
</div>
|
||
|
||
<script>
|
||
// 获取DOM元素
|
||
const draggableObject = document.getElementById('draggable-object');
|
||
const objectDensitySlider = document.getElementById('object-density');
|
||
const liquidDensitySlider = document.getElementById('liquid-density');
|
||
const objectDensityValue = document.getElementById('object-density-value');
|
||
const liquidDensityValue = document.getElementById('liquid-density-value');
|
||
const gravityArrow = document.getElementById('gravity-arrow');
|
||
const buoyancyArrow = document.getElementById('buoyancy-arrow');
|
||
const gravityLabel = document.getElementById('gravity-label');
|
||
const buoyancyLabel = document.getElementById('buoyancy-label');
|
||
const conditionDisplay = document.getElementById('condition-display');
|
||
const explanation = document.getElementById('explanation');
|
||
const scenarioButtons = document.querySelectorAll('.scenario-btn');
|
||
|
||
// 状态指示器
|
||
const floatingStatus = document.getElementById('floating-status');
|
||
const suspendedStatus = document.getElementById('suspended-status');
|
||
const sinkingStatus = document.getElementById('sinking-status');
|
||
const submergedStatus = document.getElementById('submerged-status');
|
||
|
||
// 物理常数
|
||
const GRAVITY = 9.8; // 重力加速度 m/s²
|
||
const OBJECT_VOLUME = 0.001; // 物体体积 m³ (1立方分米)
|
||
|
||
// 容器尺寸
|
||
const CONTAINER_HEIGHT = 400; // 容器高度px
|
||
const LIQUID_HEIGHT_PERCENT = 0.6; // 液体占容器高度的比例
|
||
const LIQUID_SURFACE_Y = CONTAINER_HEIGHT * (1 - LIQUID_HEIGHT_PERCENT); // 液面位置
|
||
const OBJECT_HEIGHT = 60; // 物体高度px
|
||
|
||
// 当前状态
|
||
let isDragging = false;
|
||
let objectPosition = { x: 50, y: 150 };
|
||
let objectDensity = 800;
|
||
let liquidDensity = 1000;
|
||
let currentStatus = 'suspended'; // 初始状态
|
||
|
||
// 初始化物体位置
|
||
updateObjectPosition();
|
||
|
||
// 拖拽功能
|
||
draggableObject.addEventListener('mousedown', startDrag);
|
||
document.addEventListener('mousemove', drag);
|
||
document.addEventListener('mouseup', endDrag);
|
||
|
||
// 触摸设备支持
|
||
draggableObject.addEventListener('touchstart', handleTouchStart);
|
||
document.addEventListener('touchmove', handleTouchMove);
|
||
document.addEventListener('touchend', endDrag);
|
||
|
||
function handleTouchStart(e) {
|
||
e.preventDefault();
|
||
startDrag(e.touches[0]);
|
||
}
|
||
|
||
function handleTouchMove(e) {
|
||
e.preventDefault();
|
||
if (isDragging) {
|
||
drag(e.touches[0]);
|
||
}
|
||
}
|
||
|
||
function startDrag(e) {
|
||
isDragging = true;
|
||
draggableObject.classList.add('dragging');
|
||
const rect = draggableObject.getBoundingClientRect();
|
||
objectPosition.offsetX = e.clientX - rect.left;
|
||
objectPosition.offsetY = e.clientY - rect.top;
|
||
}
|
||
|
||
function drag(e) {
|
||
if (!isDragging) return;
|
||
|
||
const simulationArea = document.querySelector('.simulation-area');
|
||
const areaRect = simulationArea.getBoundingClientRect();
|
||
|
||
let newX = e.clientX - areaRect.left - objectPosition.offsetX;
|
||
let newY = e.clientY - areaRect.top - objectPosition.offsetY;
|
||
|
||
// 限制在容器内
|
||
newX = Math.max(0, Math.min(newX, areaRect.width - draggableObject.offsetWidth));
|
||
newY = Math.max(0, Math.min(newY, areaRect.height - draggableObject.offsetHeight));
|
||
|
||
objectPosition.x = newX;
|
||
objectPosition.y = newY;
|
||
|
||
updateObjectPosition();
|
||
}
|
||
|
||
function endDrag() {
|
||
isDragging = false;
|
||
draggableObject.classList.remove('dragging');
|
||
// 拖拽结束后重新计算位置
|
||
updatePositionBasedOnDensity();
|
||
}
|
||
|
||
function updateObjectPosition() {
|
||
draggableObject.style.left = `${objectPosition.x}px`;
|
||
draggableObject.style.top = `${objectPosition.y}px`;
|
||
}
|
||
|
||
// 密度滑块事件
|
||
objectDensitySlider.addEventListener('input', function() {
|
||
objectDensity = parseInt(this.value);
|
||
objectDensityValue.textContent = objectDensity;
|
||
updatePositionBasedOnDensity();
|
||
});
|
||
|
||
liquidDensitySlider.addEventListener('input', function() {
|
||
liquidDensity = parseInt(this.value);
|
||
liquidDensityValue.textContent = liquidDensity;
|
||
updatePositionBasedOnDensity();
|
||
});
|
||
|
||
// 场景切换
|
||
scenarioButtons.forEach(button => {
|
||
button.addEventListener('click', function() {
|
||
// 移除所有按钮的active类
|
||
scenarioButtons.forEach(btn => btn.classList.remove('active'));
|
||
// 为当前按钮添加active类
|
||
this.classList.add('active');
|
||
|
||
const scenario = this.dataset.scenario;
|
||
applyScenario(scenario);
|
||
});
|
||
});
|
||
|
||
function applyScenario(scenario) {
|
||
switch(scenario) {
|
||
case 'wood': // 木块漂浮
|
||
objectDensity = 600;
|
||
liquidDensity = 1000;
|
||
break;
|
||
case 'submarine': // 潜水艇悬浮
|
||
objectDensity = 1000;
|
||
liquidDensity = 1000;
|
||
break;
|
||
case 'stone': // 石块沉底
|
||
objectDensity = 2500;
|
||
liquidDensity = 1000;
|
||
break;
|
||
case 'iron': // 铁块沉没悬浮
|
||
objectDensity = 1200;
|
||
liquidDensity = 1000;
|
||
break;
|
||
}
|
||
|
||
// 更新滑块和显示值
|
||
objectDensitySlider.value = objectDensity;
|
||
liquidDensitySlider.value = liquidDensity;
|
||
objectDensityValue.textContent = objectDensity;
|
||
liquidDensityValue.textContent = liquidDensity;
|
||
|
||
updatePositionBasedOnDensity();
|
||
}
|
||
|
||
// 根据密度关系更新物体位置
|
||
function updatePositionBasedOnDensity() {
|
||
const simulationArea = document.querySelector('.simulation-area');
|
||
const areaWidth = simulationArea.offsetWidth;
|
||
const areaHeight = simulationArea.offsetHeight;
|
||
|
||
// 默认位置为中央
|
||
objectPosition.x = (areaWidth - draggableObject.offsetWidth) / 2;
|
||
|
||
// 计算物体在液体中的位置
|
||
if (objectDensity < liquidDensity) {
|
||
// 漂浮状态 - 部分露出液面
|
||
const immersionRatio = objectDensity / liquidDensity; // 浸入深度比例
|
||
const immersedHeight = OBJECT_HEIGHT * immersionRatio; // 浸入部分高度
|
||
objectPosition.y = LIQUID_SURFACE_Y - OBJECT_HEIGHT + immersedHeight;
|
||
currentStatus = 'floating';
|
||
} else if (objectDensity === liquidDensity) {
|
||
// 悬浮状态 - 完全浸没但不沉底
|
||
objectPosition.y = (LIQUID_SURFACE_Y + areaHeight) / 2 - OBJECT_HEIGHT / 2;
|
||
currentStatus = 'suspended';
|
||
} else {
|
||
// 沉底或沉没悬浮状态
|
||
objectPosition.y = areaHeight - OBJECT_HEIGHT; // 沉到底部
|
||
|
||
// 检查是否完全浸没
|
||
const objectBottom = objectPosition.y + OBJECT_HEIGHT;
|
||
if (objectBottom > LIQUID_SURFACE_Y) {
|
||
// 物体部分在液体中(沉没但未完全浸没)
|
||
currentStatus = 'submerged';
|
||
} else {
|
||
// 完全沉底
|
||
currentStatus = 'sinking';
|
||
}
|
||
}
|
||
|
||
// 确保位置在容器范围内
|
||
objectPosition.y = Math.max(0, Math.min(objectPosition.y, areaHeight - OBJECT_HEIGHT));
|
||
|
||
updateObjectPosition();
|
||
updateSimulation();
|
||
}
|
||
|
||
// 更新仿真
|
||
function updateSimulation() {
|
||
// 计算重力和浮力
|
||
const gravity = objectDensity * OBJECT_VOLUME * GRAVITY;
|
||
|
||
// 计算浮力 - 取决于浸入体积
|
||
let buoyancy = 0;
|
||
const objectBottomY = objectPosition.y + OBJECT_HEIGHT;
|
||
|
||
if (objectBottomY <= LIQUID_SURFACE_Y) {
|
||
// 物体完全在液面上方,浮力为0
|
||
buoyancy = 0;
|
||
} else if (objectPosition.y >= LIQUID_SURFACE_Y) {
|
||
// 物体完全在液体中
|
||
buoyancy = liquidDensity * OBJECT_VOLUME * GRAVITY;
|
||
} else {
|
||
// 物体部分在液体中
|
||
const immersedHeight = objectBottomY - LIQUID_SURFACE_Y;
|
||
const immersedRatio = immersedHeight / OBJECT_HEIGHT;
|
||
const immersedVolume = OBJECT_VOLUME * immersedRatio;
|
||
buoyancy = liquidDensity * immersedVolume * GRAVITY;
|
||
}
|
||
|
||
// 更新力的显示
|
||
updateForceArrows(gravity, buoyancy);
|
||
|
||
// 更新状态显示
|
||
updateStatusDisplay();
|
||
|
||
// 更新物体颜色
|
||
updateObjectColor();
|
||
}
|
||
|
||
function updateForceArrows(gravity, buoyancy) {
|
||
// 箭头长度与力的大小成正比
|
||
const maxLength = 200;
|
||
const maxForce = 20; // 最大力值(牛顿)
|
||
|
||
const gravityHeight = Math.min(maxLength, (gravity / maxForce) * maxLength);
|
||
const buoyancyHeight = Math.min(maxLength, (buoyancy / maxForce) * maxLength);
|
||
|
||
gravityArrow.style.height = `${gravityHeight}px`;
|
||
buoyancyArrow.style.height = `${buoyancyHeight}px`;
|
||
|
||
gravityLabel.textContent = `重力: ${gravity.toFixed(2)} N`;
|
||
buoyancyLabel.textContent = `浮力: ${buoyancy.toFixed(2)} N`;
|
||
}
|
||
|
||
function updateStatusDisplay() {
|
||
// 更新状态指示器
|
||
floatingStatus.classList.remove('active');
|
||
suspendedStatus.classList.remove('active');
|
||
sinkingStatus.classList.remove('active');
|
||
submergedStatus.classList.remove('active');
|
||
|
||
switch(currentStatus) {
|
||
case 'floating':
|
||
conditionDisplay.innerHTML = '物体漂浮 (F<sub>浮</sub> = G)';
|
||
conditionDisplay.className = 'condition floating';
|
||
explanation.textContent = '当物体密度小于液体密度时,物体漂浮在液面上。此时浮力等于重力。';
|
||
floatingStatus.classList.add('active');
|
||
break;
|
||
case 'suspended':
|
||
conditionDisplay.innerHTML = '物体悬浮 (F<sub>浮</sub> = G)';
|
||
conditionDisplay.className = 'condition suspended';
|
||
explanation.textContent = '当物体密度等于液体密度时,物体悬浮在液体中任何位置。此时浮力等于重力。';
|
||
suspendedStatus.classList.add('active');
|
||
break;
|
||
case 'sinking':
|
||
conditionDisplay.innerHTML = '物体沉底 (F<sub>浮</sub> < G)';
|
||
conditionDisplay.className = 'condition sinking';
|
||
explanation.textContent = '当物体密度大于液体密度时,物体沉到容器底部。此时浮力小于重力。';
|
||
sinkingStatus.classList.add('active');
|
||
break;
|
||
case 'submerged':
|
||
conditionDisplay.innerHTML = '沉没悬浮 (F<sub>浮</sub> < G)';
|
||
conditionDisplay.className = 'condition submerged';
|
||
explanation.textContent = '物体沉到底部但未完全浸没。此时浮力小于重力,容器底部提供支持力。';
|
||
submergedStatus.classList.add('active');
|
||
break;
|
||
}
|
||
}
|
||
|
||
function updateObjectColor() {
|
||
switch(currentStatus) {
|
||
case 'floating':
|
||
draggableObject.style.background = '#4ECDC4'; // 青色
|
||
break;
|
||
case 'suspended':
|
||
draggableObject.style.background = '#FFD700'; // 金色
|
||
break;
|
||
case 'sinking':
|
||
draggableObject.style.background = '#FF6B6B'; // 红色
|
||
break;
|
||
case 'submerged':
|
||
draggableObject.style.background = '#6A5ACD'; // 紫色
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 初始化仿真
|
||
updatePositionBasedOnDensity();
|
||
</script>
|
||
</body>
|
||
</html> |