d190fd2de9
F5. Машина Атвуда (§22): - Canvas 640×420: блок с двумя массами на нити - Slider'ы: m₁, m₂, трение в блоке - Запуск: бо́льшая масса опускается, меньшая поднимается - Физика: a = ((m₁-m₂)g - μ)/(m₁+m₂) - Анимация: блок вращается, грузы движутся, размер пропорц. m - Показ векторов сил тяжести (m₁g, m₂g) и натяжений (T) в покое - Stats: a, T, v, t F7. Лифт с динамометром (§24): - Canvas 640×460: шахта с 5 этажами + большой циферблат справа - Слева кабина с динамометром и грузом m - Slider'ы: m груза, a разгона - 5 режимов кнопок: - Разгон ⬆ (hold) → a = +a_in - Разгон ⬇ (hold) → a = -a_in - Стоп → a = 0 - Свободное падение → a = -g (трос показывается пунктиром) - Сброс - 2 динамометра: мини в кабине + большой круглый (шкала 0..2.5g) - Stats: P, P/(mg), v лифта, h высота - Контекстный feedback: невесомость / норма / перегрузка / P<0 Подключение в ch2: F5 на p22 (закон Ньютона II), F7 на p24 (вес). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
183 lines
8.3 KiB
JavaScript
183 lines
8.3 KiB
JavaScript
// F5. Машина Атвуда (§22 в ch2) — связанные массы через блок.
|
||
(function(){
|
||
'use strict';
|
||
const B = () => window.PHYS9_FLAG_BASE;
|
||
const C = () => window.PHYS9_COLORS || {};
|
||
|
||
function init(secId){
|
||
if (!B()) return false;
|
||
const body = ''
|
||
+ '<div class="flag-sliders">'
|
||
+ '<label>$m_1$ (левая), кг: <b id="F5-m1v">3</b><input type="range" id="F5-m1" min="0.1" max="10" step="0.1" value="3"></label>'
|
||
+ '<label>$m_2$ (правая), кг: <b id="F5-m2v">2</b><input type="range" id="F5-m2" min="0.1" max="10" step="0.1" value="2"></label>'
|
||
+ '<label>Трение в блоке: <b id="F5-frv">0</b><input type="range" id="F5-fr" min="0" max="3" step="0.05" value="0"></label>'
|
||
+ '</div>'
|
||
+ '<canvas id="F5-cv" class="flag-canvas" width="640" height="420" style="height:420px"></canvas>'
|
||
+ '<div class="flag-controls">'
|
||
+ '<button class="flag-btn primary" id="F5-go">Запустить</button>'
|
||
+ '<button class="flag-btn" id="F5-reset">Сброс</button>'
|
||
+ '</div>'
|
||
+ '<div class="flag-stats">'
|
||
+ '<div class="flag-stat"><span class="lbl">$a = (m_1-m_2)g/(m_1+m_2)$</span><span class="val" id="F5-a">1.96 м/с²</span></div>'
|
||
+ '<div class="flag-stat"><span class="lbl">Натяжение T</span><span class="val" id="F5-T">23.5 Н</span></div>'
|
||
+ '<div class="flag-stat"><span class="lbl">$v$ текущая</span><span class="val" id="F5-v">0 м/с</span></div>'
|
||
+ '<div class="flag-stat"><span class="lbl">$t$ прошло</span><span class="val" id="F5-t">0 с</span></div>'
|
||
+ '</div>';
|
||
|
||
const card = B().makeCard(secId,
|
||
'F5. Машина Атвуда',
|
||
'Две массы через блок на нити. Бо́льшая опускается, меньшая поднимается. Физика: $a = (m_1-m_2)g/(m_1+m_2)$.',
|
||
body);
|
||
if (!card) return false;
|
||
|
||
const cv = document.getElementById('F5-cv');
|
||
const ctx = cv.getContext('2d');
|
||
const W = cv.width, H = cv.height;
|
||
const blockX = W/2, blockY = 60, blockR = 28;
|
||
|
||
let st = { y1: 220, y2: 220, v: 0, t: 0, running: false };
|
||
/* y — расстояние от блока вниз, +v = m1 опускается */
|
||
|
||
function readSliders(){
|
||
const m1 = +document.getElementById('F5-m1').value;
|
||
const m2 = +document.getElementById('F5-m2').value;
|
||
const fr = +document.getElementById('F5-fr').value;
|
||
document.getElementById('F5-m1v').textContent = m1.toFixed(2);
|
||
document.getElementById('F5-m2v').textContent = m2.toFixed(2);
|
||
document.getElementById('F5-frv').textContent = fr.toFixed(2);
|
||
const g = 9.8;
|
||
const a = ((m1 - m2)*g - fr) / (m1 + m2);
|
||
const T = m2 * (g + a);
|
||
document.getElementById('F5-a').textContent = a.toFixed(2) + ' м/с²';
|
||
document.getElementById('F5-T').textContent = T.toFixed(1) + ' Н';
|
||
}
|
||
|
||
function reset(){
|
||
st = { y1: 220, y2: 220, v: 0, t: 0, running: false };
|
||
document.getElementById('F5-go').textContent = 'Запустить';
|
||
document.getElementById('F5-v').textContent = '0 м/с';
|
||
document.getElementById('F5-t').textContent = '0 с';
|
||
draw();
|
||
}
|
||
|
||
function tick(dt){
|
||
if (!st.running) { draw(); return; }
|
||
const m1 = +document.getElementById('F5-m1').value;
|
||
const m2 = +document.getElementById('F5-m2').value;
|
||
const fr = +document.getElementById('F5-fr').value;
|
||
const g = 9.8;
|
||
const a = ((m1 - m2)*g - fr*Math.sign(st.v || (m1-m2))) / (m1 + m2);
|
||
st.v += a * dt;
|
||
/* движение: m1 опускается со скоростью v, m2 поднимается */
|
||
const dy = st.v * dt * 30; /* масштаб 30 px/м */
|
||
st.y1 += dy;
|
||
st.y2 -= dy;
|
||
/* ограничения */
|
||
if (st.y1 > H - 80){ st.y1 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
|
||
if (st.y2 < 100){ st.y2 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
|
||
if (st.y1 < 100){ st.y1 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
|
||
if (st.y2 > H - 80){ st.y2 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
|
||
st.t += dt;
|
||
document.getElementById('F5-v').textContent = st.v.toFixed(2) + ' м/с';
|
||
document.getElementById('F5-t').textContent = st.t.toFixed(1) + ' с';
|
||
draw();
|
||
}
|
||
|
||
function draw(){
|
||
const col = C();
|
||
ctx.fillStyle = col.bg || '#fafafa';
|
||
ctx.fillRect(0, 0, W, H);
|
||
/* потолок */
|
||
ctx.fillStyle = col.surface || '#a16207';
|
||
ctx.fillRect(0, 0, W, 30);
|
||
for (let x = 0; x < W; x += 12){
|
||
ctx.strokeStyle = '#7c4a08';
|
||
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x+6, 6); ctx.stroke();
|
||
}
|
||
/* кронштейн к блоку */
|
||
ctx.strokeStyle = col.axis || '#1e293b';
|
||
ctx.lineWidth = 4;
|
||
ctx.beginPath(); ctx.moveTo(blockX, 30); ctx.lineTo(blockX, blockY - blockR); ctx.stroke();
|
||
/* блок */
|
||
ctx.fillStyle = col.bodyLight || '#cbd5e1';
|
||
ctx.beginPath(); ctx.arc(blockX, blockY, blockR, 0, Math.PI*2); ctx.fill();
|
||
ctx.strokeStyle = col.axis || '#1e293b';
|
||
ctx.lineWidth = 2.5;
|
||
ctx.stroke();
|
||
/* радиальные линии (вращение) */
|
||
const rot = st.v * 0.1;
|
||
for (let i = 0; i < 4; i++){
|
||
const a = rot + i*Math.PI/2;
|
||
ctx.beginPath();
|
||
ctx.moveTo(blockX + (blockR-5)*Math.cos(a), blockY + (blockR-5)*Math.sin(a));
|
||
ctx.lineTo(blockX + 5*Math.cos(a), blockY + 5*Math.sin(a));
|
||
ctx.stroke();
|
||
}
|
||
/* нити */
|
||
const cordL = blockX - blockR;
|
||
const cordR = blockX + blockR;
|
||
ctx.lineWidth = 2;
|
||
ctx.beginPath();
|
||
ctx.moveTo(cordL, blockY); ctx.lineTo(cordL, st.y1);
|
||
ctx.moveTo(cordR, blockY); ctx.lineTo(cordR, st.y2);
|
||
ctx.stroke();
|
||
/* грузы */
|
||
const m1 = +document.getElementById('F5-m1').value;
|
||
const m2 = +document.getElementById('F5-m2').value;
|
||
const r1 = Math.min(40, 12 + m1*3);
|
||
const r2 = Math.min(40, 12 + m2*3);
|
||
ctx.fillStyle = col.forceGravity || '#2563eb';
|
||
ctx.fillRect(cordL - r1, st.y1, r1*2, r1);
|
||
ctx.strokeStyle = col.axis || '#1e293b';
|
||
ctx.lineWidth = 1.5;
|
||
ctx.strokeRect(cordL - r1, st.y1, r1*2, r1);
|
||
ctx.fillStyle = col.fail || '#dc2626';
|
||
ctx.fillRect(cordR - r2, st.y2, r2*2, r2);
|
||
ctx.strokeRect(cordR - r2, st.y2, r2*2, r2);
|
||
ctx.fillStyle = '#fff';
|
||
ctx.font = 'bold 12px Inter,sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('m₁ = '+m1.toFixed(1)+' кг', cordL, st.y1 + r1/2 + 4);
|
||
ctx.fillText('m₂ = '+m2.toFixed(1)+' кг', cordR, st.y2 + r2/2 + 4);
|
||
ctx.textAlign = 'left';
|
||
/* векторы силы тяжести */
|
||
if (st.t < 0.5 || !st.running){
|
||
B().arrow(ctx, cordL, st.y1 + r1, cordL, st.y1 + r1 + 40, col.forceGravity || '#2563eb', 2);
|
||
ctx.fillStyle = col.forceGravity || '#2563eb';
|
||
ctx.font = 'bold 12px Inter,sans-serif';
|
||
ctx.fillText('m₁g', cordL + 8, st.y1 + r1 + 30);
|
||
B().arrow(ctx, cordR, st.y2 + r2, cordR, st.y2 + r2 + 40, col.forceGravity || '#2563eb', 2);
|
||
ctx.fillText('m₂g', cordR + 8, st.y2 + r2 + 30);
|
||
/* T снизу */
|
||
B().arrow(ctx, cordL, st.y1, cordL, st.y1 - 30, col.forceTension || '#16a34a', 2);
|
||
ctx.fillStyle = col.forceTension || '#16a34a';
|
||
ctx.fillText('T', cordL + 8, st.y1 - 20);
|
||
B().arrow(ctx, cordR, st.y2, cordR, st.y2 - 30, col.forceTension || '#16a34a', 2);
|
||
ctx.fillText('T', cordR + 8, st.y2 - 20);
|
||
}
|
||
}
|
||
|
||
document.getElementById('F5-go').addEventListener('click', ()=>{
|
||
if (st.y1 >= H - 80 || st.y2 >= H - 80 || st.y1 < 110 || st.y2 < 110) reset();
|
||
st.running = !st.running;
|
||
document.getElementById('F5-go').textContent = st.running ? 'Пауза' : 'Запустить';
|
||
});
|
||
document.getElementById('F5-reset').addEventListener('click', reset);
|
||
['F5-m1','F5-m2','F5-fr'].forEach(id => document.getElementById(id).addEventListener('input', ()=>{
|
||
readSliders();
|
||
if (!st.running) reset();
|
||
}));
|
||
|
||
readSliders();
|
||
draw();
|
||
B().startLoop('F5', cv, tick);
|
||
return true;
|
||
}
|
||
|
||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} });
|
||
else document.addEventListener('DOMContentLoaded', ()=>{
|
||
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} });
|
||
});
|
||
|
||
})();
|