feat(phys11 ch3): Wave 5 — Глава 3 «Оптика» §14-§17 + phys-fx TwoSlit/DiffractionGrating/FlatMirror
- phys-fx.js: PHYS.TwoSlit (опыт Юнга), PHYS.DiffractionGrating (с радужным спектром), PHYS.FlatMirror - ch3 §14: Электромагнитная природа света + скорость света - ch3 §15: Интерференция (опыт Юнга) - ch3 §16: Дифракция света + дифракционная решётка - ch3 §17: Отражение света + плоское зеркало - 8 квизов (I1_CALC/NAT, I2_CALC/TH, I3_CALC/TH, I4_CALC/IMG) - 4 босса (b1-b4) для §14-§17 - §18-§23 + Final — заглушки для W6/W7
This commit is contained in:
@@ -832,4 +832,205 @@ class Transformer {
|
||||
}
|
||||
P.Transformer = Transformer;
|
||||
|
||||
/* ============================================================ */
|
||||
/* TwoSlit — интерференция от двух щелей (опыт Юнга) */
|
||||
/* ============================================================ */
|
||||
|
||||
class TwoSlit {
|
||||
constructor(container, opts){
|
||||
opts = opts || {};
|
||||
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
|
||||
this.W = opts.width || 540;
|
||||
this.H = opts.height || 200;
|
||||
this.d = opts.d !== undefined ? opts.d : 0.5; /* расстояние между щелями (отн. ед.) */
|
||||
this.L = opts.L !== undefined ? opts.L : 5; /* расстояние до экрана */
|
||||
this.lambda = opts.lambda !== undefined ? opts.lambda : 0.05; /* длина волны */
|
||||
this.color = opts.color || '#f59e0b';
|
||||
this.paused = true; /* статика */
|
||||
this.render();
|
||||
}
|
||||
setD(v){ this.d = Math.max(0.05, v); this.render(); }
|
||||
setLambda(v){ this.lambda = Math.max(0.005, v); this.render(); }
|
||||
update(){}
|
||||
render(){
|
||||
if (!this.el) return;
|
||||
const W = this.W, H = this.H;
|
||||
/* Слева: щели (две точки), справа: экран с полосами */
|
||||
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
|
||||
/* Лазер слева */
|
||||
svg += '<rect x="6" y="' + (H/2 - 14) + '" width="40" height="28" fill="#fca5a5" stroke="#0f172a" stroke-width="1.4" rx="3"/>';
|
||||
svg += '<text x="26" y="' + (H/2 + 4) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" font-weight="700">laser</text>';
|
||||
/* Щели */
|
||||
const slitX = 130;
|
||||
const dPx = Math.min(60, this.d * 60);
|
||||
const s1y = H/2 - dPx/2, s2y = H/2 + dPx/2;
|
||||
svg += '<line x1="' + slitX + '" y1="20" x2="' + slitX + '" y2="' + (s1y - 4) + '" stroke="#0f172a" stroke-width="3"/>';
|
||||
svg += '<line x1="' + slitX + '" y1="' + (s1y + 4) + '" x2="' + slitX + '" y2="' + (s2y - 4) + '" stroke="#0f172a" stroke-width="3"/>';
|
||||
svg += '<line x1="' + slitX + '" y1="' + (s2y + 4) + '" x2="' + slitX + '" y2="' + (H - 20) + '" stroke="#0f172a" stroke-width="3"/>';
|
||||
/* Лучи от лазера к щелям */
|
||||
svg += '<line x1="46" y1="' + (H/2) + '" x2="' + slitX + '" y2="' + s1y + '" stroke="#fca5a5" stroke-width="1.4" opacity="0.6"/>';
|
||||
svg += '<line x1="46" y1="' + (H/2) + '" x2="' + slitX + '" y2="' + s2y + '" stroke="#fca5a5" stroke-width="1.4" opacity="0.6"/>';
|
||||
/* Экран */
|
||||
const screenX = W - 50;
|
||||
svg += '<line x1="' + screenX + '" y1="20" x2="' + screenX + '" y2="' + (H - 20) + '" stroke="#0f172a" stroke-width="3"/>';
|
||||
/* Интерференционная картина: интенсивность ~ cos²(πdy/(λL)) */
|
||||
const lp = this.lambda * this.L / this.d; /* шаг полос */
|
||||
const lpPx = Math.max(3, Math.min(80, lp * 200));
|
||||
const halfH = (H - 40) / 2;
|
||||
const N = 240;
|
||||
for (let i = 0; i < N; i++){
|
||||
const y = 20 + (H - 40) * i / N;
|
||||
const ry = y - H/2;
|
||||
const intens = Math.pow(Math.cos(Math.PI * ry / lpPx), 2);
|
||||
const op = intens.toFixed(3);
|
||||
svg += '<rect x="' + screenX + '" y="' + y.toFixed(1) + '" width="22" height="' + ((H - 40) / N + 1).toFixed(1) + '" fill="' + this.color + '" opacity="' + op + '"/>';
|
||||
}
|
||||
/* Подпись формулы */
|
||||
svg += '<text x="' + (W/2) + '" y="' + (H - 4) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569">d·sin φ = k·λ (max), d=' + this.d.toFixed(2) + ', λ=' + this.lambda.toFixed(3) + '</text>';
|
||||
svg += '</svg>';
|
||||
this.el.innerHTML = svg;
|
||||
}
|
||||
}
|
||||
P.TwoSlit = TwoSlit;
|
||||
|
||||
/* ============================================================ */
|
||||
/* DiffractionGrating — дифракционная решётка + спектр */
|
||||
/* ============================================================ */
|
||||
|
||||
class DiffractionGrating {
|
||||
constructor(container, opts){
|
||||
opts = opts || {};
|
||||
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
|
||||
this.W = opts.width || 540;
|
||||
this.H = opts.height || 220;
|
||||
this.d = opts.d !== undefined ? opts.d : 2e-6; /* период решётки, м */
|
||||
this.lambda = opts.lambda !== undefined ? opts.lambda : 550e-9; /* λ, м */
|
||||
this.color = opts.color || '#22c55e';
|
||||
this.paused = true;
|
||||
this.render();
|
||||
}
|
||||
setD(v){ this.d = Math.max(0.3e-6, v); this.render(); }
|
||||
setLambda(v){ this.lambda = Math.max(380e-9, Math.min(760e-9, v)); this.render(); }
|
||||
update(){}
|
||||
/* Возвращает цвет HEX для данной длины волны (для видимого света) */
|
||||
wavelengthToColor(lamNm){
|
||||
const ranges = [
|
||||
[380, 440, '#7c3aed'], [440, 490, '#3b82f6'], [490, 520, '#06b6d4'],
|
||||
[520, 570, '#22c55e'], [570, 590, '#facc15'], [590, 630, '#f97316'],
|
||||
[630, 760, '#dc2626']
|
||||
];
|
||||
for (const [lo, hi, c] of ranges) if (lamNm >= lo && lamNm < hi) return c;
|
||||
return '#94a3b8';
|
||||
}
|
||||
render(){
|
||||
if (!this.el) return;
|
||||
const W = this.W, H = this.H;
|
||||
let svg = util.svgFrame(W, H, {bg:'#0f172a'}); /* тёмный фон для спектра */
|
||||
/* Решётка слева */
|
||||
const gx = 50;
|
||||
svg += '<rect x="' + (gx - 6) + '" y="40" width="12" height="' + (H - 80) + '" fill="#475569"/>';
|
||||
/* «штрихи» решётки */
|
||||
for (let i = 0; i < 10; i++){
|
||||
const y = 40 + i * (H - 80) / 10;
|
||||
svg += '<line x1="' + gx + '" y1="' + y + '" x2="' + (gx + 6) + '" y2="' + y + '" stroke="#fff" stroke-width="0.8"/>';
|
||||
}
|
||||
svg += '<text x="' + gx + '" y="30" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#fff">решётка</text>';
|
||||
/* Падающий луч */
|
||||
svg += '<line x1="10" y1="' + (H/2) + '" x2="' + (gx - 6) + '" y2="' + (H/2) + '" stroke="' + this.wavelengthToColor(this.lambda * 1e9) + '" stroke-width="2.2"/>';
|
||||
/* Линии порядков k = -3..3 */
|
||||
const cx = gx + 6, cy = H/2;
|
||||
const lamNm = this.lambda * 1e9;
|
||||
const color = this.wavelengthToColor(lamNm);
|
||||
for (let k = -3; k <= 3; k++){
|
||||
const sinPhi = k * this.lambda / this.d;
|
||||
if (Math.abs(sinPhi) > 1) continue;
|
||||
const phi = Math.asin(sinPhi);
|
||||
const dx = 400, dy = dx * Math.tan(phi);
|
||||
const x2 = cx + dx, y2 = cy - dy;
|
||||
const op = k === 0 ? 1.0 : (1 - Math.abs(k) * 0.18);
|
||||
svg += '<line x1="' + cx + '" y1="' + cy + '" x2="' + x2 + '" y2="' + y2.toFixed(1) + '" stroke="' + color + '" stroke-width="' + (2.4 - Math.abs(k) * 0.3) + '" opacity="' + op.toFixed(2) + '"/>';
|
||||
svg += '<text x="' + (x2 - 24) + '" y="' + (y2 - 6).toFixed(1) + '" font-family="JetBrains Mono,monospace" font-size="10" fill="' + color + '" font-weight="700">k=' + k + '</text>';
|
||||
}
|
||||
/* Подпись формулы и параметров */
|
||||
svg += '<text x="' + (W/2) + '" y="' + (H - 8) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#cbd5e1">d sin φ = kλ · d=' + (this.d * 1e6).toFixed(2) + ' мкм · λ=' + (this.lambda * 1e9).toFixed(0) + ' нм</text>';
|
||||
svg += '</svg>';
|
||||
this.el.innerHTML = svg;
|
||||
}
|
||||
}
|
||||
P.DiffractionGrating = DiffractionGrating;
|
||||
|
||||
/* ============================================================ */
|
||||
/* FlatMirror — плоское зеркало, объект и мнимое изображение */
|
||||
/* ============================================================ */
|
||||
|
||||
class FlatMirror {
|
||||
constructor(container, opts){
|
||||
opts = opts || {};
|
||||
this.el = (typeof container === 'string') ? document.querySelector(container) : container;
|
||||
this.W = opts.width || 540;
|
||||
this.H = opts.height || 240;
|
||||
this.objX = opts.objX !== undefined ? opts.objX : 100; /* px от зеркала */
|
||||
this.objY = opts.objY !== undefined ? opts.objY : 50;
|
||||
this.objH = opts.objH !== undefined ? opts.objH : 50;
|
||||
this.color = opts.color || '#f59e0b';
|
||||
this.paused = true;
|
||||
this.render();
|
||||
}
|
||||
setObjX(v){ this.objX = Math.max(20, v); this.render(); }
|
||||
setObjY(v){ this.objY = v; this.render(); }
|
||||
update(){}
|
||||
render(){
|
||||
if (!this.el) return;
|
||||
const W = this.W, H = this.H;
|
||||
const cy = H / 2 + 30;
|
||||
const mirrorX = W / 2;
|
||||
let svg = util.svgFrame(W, H, {bg:'#f8fafc'});
|
||||
/* Зеркало с штриховкой */
|
||||
svg += '<line x1="' + mirrorX + '" y1="30" x2="' + mirrorX + '" y2="' + (H - 30) + '" stroke="#0f172a" stroke-width="3"/>';
|
||||
for (let i = 0; i < 12; i++){
|
||||
const y = 30 + i * (H - 60) / 12;
|
||||
svg += '<line x1="' + (mirrorX + 1) + '" y1="' + y + '" x2="' + (mirrorX + 10) + '" y2="' + (y - 8) + '" stroke="#0f172a" stroke-width="1.2"/>';
|
||||
}
|
||||
svg += '<text x="' + (mirrorX + 14) + '" y="' + (H - 16) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="#0f172a">зеркало</text>';
|
||||
/* Объект — стрелка */
|
||||
const objX = mirrorX - this.objX;
|
||||
const objBaseY = cy;
|
||||
const objTopY = cy - this.objH;
|
||||
svg += '<line x1="' + objX + '" y1="' + objBaseY + '" x2="' + objX + '" y2="' + objTopY + '" stroke="' + this.color + '" stroke-width="3"/>';
|
||||
svg += '<polygon points="' + objX + ',' + objTopY + ' ' + (objX - 6) + ',' + (objTopY + 10) + ' ' + (objX + 6) + ',' + (objTopY + 10) + '" fill="' + this.color + '"/>';
|
||||
svg += '<text x="' + (objX - 5) + '" y="' + (objBaseY + 16) + '" text-anchor="end" font-family="JetBrains Mono,monospace" font-size="11" fill="' + this.color + '" font-weight="700">объект</text>';
|
||||
/* Мнимое изображение справа от зеркала, симметрично */
|
||||
const imgX = mirrorX + this.objX;
|
||||
svg += '<line x1="' + imgX + '" y1="' + objBaseY + '" x2="' + imgX + '" y2="' + objTopY + '" stroke="' + this.color + '" stroke-width="2" stroke-dasharray="4 3" opacity="0.65"/>';
|
||||
svg += '<polygon points="' + imgX + ',' + objTopY + ' ' + (imgX - 6) + ',' + (objTopY + 10) + ' ' + (imgX + 6) + ',' + (objTopY + 10) + '" fill="' + this.color + '" opacity="0.5"/>';
|
||||
svg += '<text x="' + (imgX + 5) + '" y="' + (objBaseY + 16) + '" font-family="JetBrains Mono,monospace" font-size="11" fill="' + this.color + '" opacity="0.7">изображение</text>';
|
||||
/* Лучи: 1) от верха объекта горизонтально на зеркало, отражается под тем же углом
|
||||
2) от верха объекта в зеркало по диагонали, отражается симметрично */
|
||||
/* Луч 1 */
|
||||
svg += '<line x1="' + objX + '" y1="' + objTopY + '" x2="' + mirrorX + '" y2="' + objTopY + '" stroke="#dc2626" stroke-width="1.6"/>';
|
||||
svg += '<line x1="' + mirrorX + '" y1="' + objTopY + '" x2="' + objX + '" y2="' + objTopY + '" stroke="#dc2626" stroke-width="1.6"/>';
|
||||
/* Продолжение в зазеркалье (пунктир) */
|
||||
svg += '<line x1="' + mirrorX + '" y1="' + objTopY + '" x2="' + imgX + '" y2="' + objTopY + '" stroke="#dc2626" stroke-width="1" stroke-dasharray="3 3"/>';
|
||||
/* Луч 2: от верха к точке наблюдения слева внизу */
|
||||
const eyeX = 40, eyeY = H - 50;
|
||||
const hitY = objTopY + (cy - objTopY) * (mirrorX - objX) / (eyeX - objX + 2 * (mirrorX - objX));
|
||||
/* Упрощённо: луч от верха объекта к зеркалу и затем к глазу */
|
||||
const hitX = mirrorX;
|
||||
const hitYsimple = objTopY + (cy + 30 - objTopY) * 0.3;
|
||||
svg += '<line x1="' + objX + '" y1="' + objTopY + '" x2="' + hitX + '" y2="' + hitYsimple.toFixed(1) + '" stroke="#16a34a" stroke-width="1.6"/>';
|
||||
svg += '<line x1="' + hitX + '" y1="' + hitYsimple.toFixed(1) + '" x2="' + eyeX + '" y2="' + eyeY + '" stroke="#16a34a" stroke-width="1.6"/>';
|
||||
/* Продолжение от точки отражения в зазеркалье */
|
||||
svg += '<line x1="' + hitX + '" y1="' + hitYsimple.toFixed(1) + '" x2="' + imgX + '" y2="' + objTopY + '" stroke="#16a34a" stroke-width="1" stroke-dasharray="3 3"/>';
|
||||
/* Глаз */
|
||||
svg += '<circle cx="' + eyeX + '" cy="' + eyeY + '" r="10" fill="#fff" stroke="#0f172a" stroke-width="1.4"/>';
|
||||
svg += '<circle cx="' + eyeX + '" cy="' + eyeY + '" r="4" fill="#0f172a"/>';
|
||||
svg += '<text x="' + eyeX + '" y="' + (eyeY + 20) + '" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="10" fill="#475569">наблюдатель</text>';
|
||||
/* Подпись закона */
|
||||
svg += '<text x="' + (W/2) + '" y="22" text-anchor="middle" font-family="JetBrains Mono,monospace" font-size="11" fill="#475569" font-weight="700">∠ пад = ∠ отр · изображение мнимое, прямое, равное</text>';
|
||||
svg += '</svg>';
|
||||
this.el.innerHTML = svg;
|
||||
}
|
||||
}
|
||||
P.FlatMirror = FlatMirror;
|
||||
|
||||
})();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user