feat(math6): ещё 2 canvas-демо — прыжки по прямой (±) и машинка+график
Math6Anim расширен: numberLineWalk (анимированные стрелки-шаги a→b на числовой прямой для сложения рациональных) и carGraph (машина едет по дороге, а график «путь–время» вычерчивается синхронно; горизонталь = стоянка). Вшито: Гл.4 §4 (прыжки, ползунки a,b) и Гл.5 §2 (машинка+график). Headless-safe. Тесты math6: 19/19 (анимации в Гл.1/4/5/6 монтируются). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -180,6 +180,16 @@ test('анимации: canvas-демо монтируются (headless-safe)',
|
|||||||
r1.doc.defaultView.goTo('p6'); await wait(100);
|
r1.doc.defaultView.goTo('p6'); await wait(100);
|
||||||
assert.ok(r1.doc.querySelector('#p6-area canvas'), 'canvas «площадная модель» §1.6');
|
assert.ok(r1.doc.querySelector('#p6-area canvas'), 'canvas «площадная модель» §1.6');
|
||||||
assert.deepEqual(r1.errors, [], 'ch1 без ошибок: ' + r1.errors.join(' | '));
|
assert.deepEqual(r1.errors, [], 'ch1 без ошибок: ' + r1.errors.join(' | '));
|
||||||
|
// Глава 4 §4: прыжки по числовой прямой
|
||||||
|
const r4 = await loadDom('math_6_ch4.html');
|
||||||
|
r4.doc.defaultView.goTo('p4'); await wait(100);
|
||||||
|
assert.ok(r4.doc.querySelector('#p4-walk canvas'), 'canvas «прыжки по прямой» §4.4');
|
||||||
|
assert.deepEqual(r4.errors, [], 'ch4 без ошибок: ' + r4.errors.join(' | '));
|
||||||
|
// Глава 5 §2: машинка + график
|
||||||
|
const r5 = await loadDom('math_6_ch5.html');
|
||||||
|
r5.doc.defaultView.goTo('p2'); await wait(100);
|
||||||
|
assert.ok(r5.doc.querySelector('#p2-car canvas'), 'canvas «машинка + график» §5.2');
|
||||||
|
assert.deepEqual(r5.errors, [], 'ch5 без ошибок: ' + r5.errors.join(' | '));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hub: 6 карточек глав + курсовой финал', async () => {
|
test('hub: 6 карточек глав + курсовой финал', async () => {
|
||||||
|
|||||||
@@ -174,4 +174,66 @@ M.areaModel = function (host, opts) {
|
|||||||
return { stop: L.stop, host: host };
|
return { stop: L.stop, host: host };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ============================ ДЕМО 4: ПРЫЖКИ ПО ЧИСЛОВОЙ ПРЯМОЙ (a + b) ============================ */
|
||||||
|
M.numberLineWalk = function (host, opts) {
|
||||||
|
opts = opts || {}; var a = opts.a != null ? opts.a : 3, b = opts.b != null ? opts.b : -5;
|
||||||
|
var sum = a + b, W0 = 540, H0 = 150;
|
||||||
|
var sc = sceneCanvas(host, W0, H0); var cap = caption(host, '');
|
||||||
|
var min = Math.min(-3, 0, a, sum) - 1, max = Math.max(3, 0, a, sum) + 1, period = 4.2;
|
||||||
|
function X(v) { var pad = 30; return pad + (v - min) / (max - min) * (W0 - 2 * pad); }
|
||||||
|
function arrow(ctx, x1, x2, y, col) {
|
||||||
|
ctx.strokeStyle = col; ctx.fillStyle = col; ctx.lineWidth = 3; ctx.lineCap = 'round';
|
||||||
|
ctx.beginPath(); ctx.moveTo(x1, y); ctx.lineTo(x2, y); ctx.stroke();
|
||||||
|
var d = x2 >= x1 ? 1 : -1; ctx.beginPath(); ctx.moveTo(x2, y); ctx.lineTo(x2 - d * 9, y - 5); ctx.lineTo(x2 - d * 9, y + 5); ctx.closePath(); ctx.fill();
|
||||||
|
}
|
||||||
|
function draw(t) {
|
||||||
|
var ctx = sc.ctx; if (!ctx) return; var mut = cssVar('--muted', '#64748b'), acc = cssVar('--pri2', '#3730a3');
|
||||||
|
var axisY = H0 - 50; ctx.clearRect(0, 0, W0, H0);
|
||||||
|
ctx.strokeStyle = mut; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(15, axisY); ctx.lineTo(W0 - 15, axisY); ctx.stroke();
|
||||||
|
ctx.font = '11px JetBrains Mono, monospace'; ctx.textAlign = 'center';
|
||||||
|
for (var v = Math.ceil(min); v <= Math.floor(max); v++) { var x = X(v); ctx.strokeStyle = mut; ctx.beginPath(); ctx.moveTo(x, axisY - 5); ctx.lineTo(x, axisY + 5); ctx.stroke(); ctx.fillStyle = (v === 0 ? acc : mut); ctx.fillText(v, x, axisY + 20); }
|
||||||
|
var p = (t % period) / period, p1 = Math.min(1, p / 0.4), p2 = Math.max(0, Math.min(1, (p - 0.45) / 0.4));
|
||||||
|
arrow(ctx, X(0), X(0) + (X(a) - X(0)) * p1, axisY - 14, a >= 0 ? '#059669' : '#e11d48');
|
||||||
|
if (p2 > 0) arrow(ctx, X(a), X(a) + (X(sum) - X(a)) * p2, axisY - 30, b >= 0 ? '#059669' : '#e11d48');
|
||||||
|
if (p > 0.88) { ctx.fillStyle = '#e11d48'; ctx.beginPath(); ctx.arc(X(sum), axisY, 6, 0, 2 * Math.PI); ctx.fill(); ctx.fillStyle = acc; ctx.font = '14px Unbounded, sans-serif'; ctx.fillText('' + sum, X(sum), axisY - 42); }
|
||||||
|
}
|
||||||
|
var L = loop(host, draw);
|
||||||
|
cap.innerHTML = '$' + a + ' + (' + b + ') = ' + sum + '$ — от нуля шагаем ' + (a >= 0 ? 'вправо' : 'влево') + ' на ' + Math.abs(a) + ', затем ' + (b >= 0 ? 'вправо' : 'влево') + ' на ' + Math.abs(b) + '.';
|
||||||
|
if (W.renderMathInElement) try { W.renderMathInElement(cap, { delimiters: [{ left: '$', right: '$', display: false }], throwOnError: false }); } catch (e) {}
|
||||||
|
return { stop: L.stop };
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ============================ ДЕМО 5: МАШИНКА + ГРАФИК «ПУТЬ–ВРЕМЯ» ============================ */
|
||||||
|
M.carGraph = function (host, opts) {
|
||||||
|
var W0 = 460, H0 = 330; var sc = sceneCanvas(host, W0, H0); var cap = caption(host, '');
|
||||||
|
var J = [{ t: 0, s: 0 }, { t: 1, s: 40 }, { t: 2, s: 80 }, { t: 3, s: 80 }, { t: 4, s: 120 }, { t: 5, s: 160 }];
|
||||||
|
var Tmax = 5, Smax = 160, period = 7;
|
||||||
|
function sAt(tt) { for (var i = 0; i < J.length - 1; i++) { if (tt >= J[i].t && tt <= J[i + 1].t) { var f = (tt - J[i].t) / ((J[i + 1].t - J[i].t) || 1); return J[i].s + f * (J[i + 1].s - J[i].s); } } return J[J.length - 1].s; }
|
||||||
|
function draw(t) {
|
||||||
|
var ctx = sc.ctx; if (!ctx) return;
|
||||||
|
var pri = cssVar('--pri', '#059669'), mut = cssVar('--muted', '#64748b'), bd = cssVar('--border', '#e2e8f0');
|
||||||
|
var cur = (t % period) / period * Tmax;
|
||||||
|
ctx.clearRect(0, 0, W0, H0);
|
||||||
|
var roadY = 52, rx0 = 24, rx1 = W0 - 24;
|
||||||
|
ctx.strokeStyle = bd; ctx.lineWidth = 8; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(rx0, roadY); ctx.lineTo(rx1, roadY); ctx.stroke();
|
||||||
|
var carX = rx0 + (sAt(cur) / Smax) * (rx1 - rx0);
|
||||||
|
ctx.fillStyle = pri; ctx.fillRect(carX - 13, roadY - 11, 26, 14);
|
||||||
|
ctx.fillStyle = '#1e293b'; ctx.beginPath(); ctx.arc(carX - 7, roadY + 4, 4, 0, 6.3); ctx.arc(carX + 7, roadY + 4, 4, 0, 6.3); ctx.fill();
|
||||||
|
ctx.fillStyle = mut; ctx.font = '13px Inter, sans-serif'; ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('время: ' + (Math.round(cur * 10) / 10) + ' ч · путь: ' + Math.round(sAt(cur)) + ' км', W0 / 2, 24);
|
||||||
|
var gx0 = 46, gy0 = H0 - 28, gw = W0 - 70, gh = H0 - 120;
|
||||||
|
function GX(tt) { return gx0 + (tt / Tmax) * gw; } function GY(ss) { return gy0 - (ss / Smax) * gh; }
|
||||||
|
ctx.strokeStyle = mut; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(gx0, gy0 - gh); ctx.lineTo(gx0, gy0); ctx.lineTo(gx0 + gw, gy0); ctx.stroke();
|
||||||
|
ctx.fillStyle = mut; ctx.font = '11px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('t, ч', gx0 + gw, gy0 + 16); ctx.textAlign = 'left'; ctx.fillText('s, км', gx0 - 38, gy0 - gh + 4);
|
||||||
|
ctx.strokeStyle = bd; ctx.lineWidth = 1.5; ctx.beginPath(); J.forEach(function (pt, i) { var x = GX(pt.t), y = GY(pt.s); if (i) ctx.lineTo(x, y); else ctx.moveTo(x, y); }); ctx.stroke();
|
||||||
|
ctx.strokeStyle = pri; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(GX(0), GY(0));
|
||||||
|
for (var tt = 0; tt <= cur; tt += 0.04) ctx.lineTo(GX(tt), GY(sAt(tt)));
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillStyle = '#e11d48'; ctx.beginPath(); ctx.arc(GX(cur), GY(sAt(cur)), 5, 0, 2 * Math.PI); ctx.fill();
|
||||||
|
}
|
||||||
|
var L = loop(host, draw);
|
||||||
|
cap.innerHTML = 'Машина едет — график «путь–время» вычерчивается сам. Где линия <b>горизонтальна</b> (с 2 до 3 ч) — машина <b>стоит</b>: время идёт, а путь не растёт.';
|
||||||
|
return { stop: L.stop };
|
||||||
|
};
|
||||||
|
|
||||||
})(window);
|
})(window);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<script src="/js/api.js" defer></script>
|
<script src="/js/api.js" defer></script>
|
||||||
<script src="/js/xp.js" defer></script>
|
<script src="/js/xp.js" defer></script>
|
||||||
<script src="/js/math6_svg.js" defer></script>
|
<script src="/js/math6_svg.js" defer></script>
|
||||||
|
<script src="/js/math6_anim.js" defer></script>
|
||||||
<script src="/js/math6_engine.js" defer></script>
|
<script src="/js/math6_engine.js" defer></script>
|
||||||
<style>:root{--pri:#e11d48;--pri2:#be123c;--pri-soft:#ffe4e6;--acc:#f43f5e;--acc2:#e11d48;--acc-soft:#fff1f2}</style>
|
<style>:root{--pri:#e11d48;--pri2:#be123c;--pri-soft:#ffe4e6;--acc:#f43f5e;--acc2:#e11d48;--acc-soft:#fff1f2}</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -307,7 +308,7 @@ function buildP4(){
|
|||||||
h+='<div class="wg" id="p4-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Сложение на прямой</div></div>'
|
h+='<div class="wg" id="p4-iv1"><div class="wg-header"><span class="wg-badge">Интерактив 1</span><div class="wg-title">Сложение на прямой</div></div>'
|
||||||
+'<div class="wg-help">Двигай слагаемые — результат отмечается на координатной прямой.</div>'
|
+'<div class="wg-help">Двигай слагаемые — результат отмечается на координатной прямой.</div>'
|
||||||
+'<div class="sliders"><label>$a$ = <b id="p4-av">-3</b><input type="range" id="p4-asl" min="-6" max="6" value="-3"></label><label>$b$ = <b id="p4-bv">5</b><input type="range" id="p4-bsl" min="-6" max="6" value="5"></label></div>'
|
+'<div class="sliders"><label>$a$ = <b id="p4-av">-3</b><input type="range" id="p4-asl" min="-6" max="6" value="-3"></label><label>$b$ = <b id="p4-bv">5</b><input type="range" id="p4-bsl" min="-6" max="6" value="5"></label></div>'
|
||||||
+'<div id="p4-fig"></div><div id="p4-out" class="qbox"></div></div>';
|
+'<div id="p4-fig"></div><div id="p4-walk" style="margin-top:8px"></div><div id="p4-out" class="qbox"></div></div>';
|
||||||
h+='<div class="wg" id="p4-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Тренажёр сложения</div></div>'
|
h+='<div class="wg" id="p4-iv2"><div class="wg-header"><span class="wg-badge">Интерактив 2</span><div class="wg-title">Тренажёр сложения</div></div>'
|
||||||
+'<div class="wg-help">Сложи рациональные числа.</div>'
|
+'<div class="wg-help">Сложи рациональные числа.</div>'
|
||||||
+'<div class="score-display"><span>Пример <b id="p4-i">1</b> / 6</span><span>Очки: <b id="p4-s">0</b> / 6</span></div>'
|
+'<div class="score-display"><span>Пример <b id="p4-i">1</b> / 6</span><span>Очки: <b id="p4-s">0</b> / 6</span></div>'
|
||||||
@@ -318,10 +319,11 @@ function buildP4(){
|
|||||||
box.innerHTML=h; renderMath(box);
|
box.innerHTML=h; renderMath(box);
|
||||||
|
|
||||||
(function(){
|
(function(){
|
||||||
var asl=document.getElementById('p4-asl'), bsl=document.getElementById('p4-bsl'), fig=document.getElementById('p4-fig'), out=document.getElementById('p4-out');
|
var asl=document.getElementById('p4-asl'), bsl=document.getElementById('p4-bsl'), fig=document.getElementById('p4-fig'), out=document.getElementById('p4-out'), walk=null;
|
||||||
function render(){ var a=+asl.value,b=+bsl.value,s=a+b; document.getElementById('p4-av').textContent=a; document.getElementById('p4-bv').textContent=b;
|
function render(){ var a=+asl.value,b=+bsl.value,s=a+b; document.getElementById('p4-av').textContent=a; document.getElementById('p4-bv').textContent=b;
|
||||||
fig.innerHTML=Math6.numberLine({min:-12,max:12,minor:1,major:2,width:580,marks:[{v:a,label:'a',color:'#4f46e5'},{v:s,label:'a+b',color:'#059669',above:false}]});
|
fig.innerHTML=Math6.numberLine({min:-12,max:12,minor:1,major:2,width:580,marks:[{v:a,label:'a',color:'#4f46e5'},{v:s,label:'a+b',color:'#059669',above:false}]});
|
||||||
out.innerHTML='<div style="font-size:1.3rem;font-weight:800;color:var(--pri2)">$'+a+' + '+_par(b)+' = '+s+'$</div>'; renderMath(out); }
|
out.innerHTML='<div style="font-size:1.3rem;font-weight:800;color:var(--pri2)">$'+a+' + '+_par(b)+' = '+s+'$</div>'; renderMath(out);
|
||||||
|
if(window.Math6Anim){ if(walk)walk.stop(); walk=Math6Anim.numberLineWalk(document.getElementById('p4-walk'),{a:a,b:b}); } }
|
||||||
asl.oninput=render; bsl.oninput=render; render();
|
asl.oninput=render; bsl.oninput=render; render();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
<script src="/js/api.js" defer></script>
|
<script src="/js/api.js" defer></script>
|
||||||
<script src="/js/xp.js" defer></script>
|
<script src="/js/xp.js" defer></script>
|
||||||
<script src="/js/math6_svg.js" defer></script>
|
<script src="/js/math6_svg.js" defer></script>
|
||||||
|
<script src="/js/math6_anim.js" defer></script>
|
||||||
<script src="/js/math6_engine.js" defer></script>
|
<script src="/js/math6_engine.js" defer></script>
|
||||||
<style>:root{--pri:#059669;--pri2:#047857;--pri-soft:#d1fae5;--acc:#10b981;--acc2:#059669;--acc-soft:#ecfdf5}</style>
|
<style>:root{--pri:#059669;--pri2:#047857;--pri-soft:#d1fae5;--acc:#10b981;--acc2:#059669;--acc-soft:#ecfdf5}</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -218,9 +219,14 @@ function buildP2(){
|
|||||||
+'<div id="p2-dq" class="qbox"></div>'
|
+'<div id="p2-dq" class="qbox"></div>'
|
||||||
+'<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap"><button class="btn primary" data-dv="up">Растёт</button><button class="btn primary" data-dv="flat">Стоит</button><button class="btn primary" data-dv="down">Падает</button></div>'
|
+'<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap"><button class="btn primary" data-dv="up">Растёт</button><button class="btn primary" data-dv="flat">Стоит</button><button class="btn primary" data-dv="down">Падает</button></div>'
|
||||||
+'<div class="feedback" id="p2-dfb"></div></div>';
|
+'<div class="feedback" id="p2-dfb"></div></div>';
|
||||||
|
h+='<div class="wg" id="p2-anim"><div class="wg-header"><span class="wg-badge">Анимация</span><div class="wg-title">Машинка едет — график рисуется сам</div></div>'
|
||||||
|
+'<div class="wg-help">Смотри, как движение машины по дороге превращается в линию на графике «путь–время». Горизонтальный участок — машина стоит.</div>'
|
||||||
|
+'<div id="p2-car"></div></div>';
|
||||||
h+=secNav('p1','p3')+readBtn('p2');
|
h+=secNav('p1','p3')+readBtn('p2');
|
||||||
box.innerHTML=h; renderMath(box);
|
box.innerHTML=h; renderMath(box);
|
||||||
|
|
||||||
|
(function(){ if(window.Math6Anim) Math6Anim.carGraph(document.getElementById('p2-car'),{}); })();
|
||||||
|
|
||||||
var G=[{x:0,y:2},{x:1,y:4},{x:2,y:4},{x:3,y:6},{x:4,y:5},{x:5,y:5},{x:6,y:3}];
|
var G=[{x:0,y:2},{x:1,y:4},{x:2,y:4},{x:3,y:6},{x:4,y:5},{x:5,y:5},{x:6,y:3}];
|
||||||
function gy(x){ for(var k=0;k<G.length;k++) if(G[k].x===x) return G[k].y; return null; }
|
function gy(x){ for(var k=0;k<G.length;k++) if(G[k].x===x) return G[k].y; return null; }
|
||||||
function fig(){ return Math6.plane({xmin:0,xmax:7,ymin:0,ymax:7,size:340,polyline:G,polylineColor:'#059669',polylineDots:true}); }
|
function fig(){ return Math6.plane({xmin:0,xmax:7,ymin:0,ymax:7,size:340,polyline:G,polylineColor:'#059669',polylineDots:true}); }
|
||||||
|
|||||||
Reference in New Issue
Block a user