Equivalent Fractions Lab
Drag the sliders and watch the bars. Match the shaded length on the second bar to the first — the matching is the math. Then test yourself with challenges.
Two bars, one amount
Bar A (target)
Bar B (yours)
Controls
Challenges
1. Match 3/4 on Bar B
Set Bar A to 3/4, then drag Bar B’s sliders until the shaded lengths match exactly.
2. Find three different-looking equivalents of 2/3
Press “Set Bar A to 2/3”, then match it on Bar B with three different denominators. Matches found: 0
3. Predict: does 5/6 have an equivalent with denominator 18? With 20?
Decide first, then check.
/* == PURE (tested by test_lab.js) == */ function lequivOK(n1, d1, n2, d2) { if (d1 <= 0 || d2 <= 0) return false; return n1 * d2 === n2 * d1; } /* can n/d be renamed with denominator D? (D a multiple of d, or n=0) */ function ldenomWorks(n, d, D) { if (d <= 0 || D <= 0) return false; if (n === 0) return true; return (D * n) % d === 0; } function lequivNum(n, d, D) { /* the matching numerator; valid only when ldenomWorks */ if (n === 0) return 0; return (D * n) / d; } function ldec(n, d) { return n / d; } /* challenge 1: bar A must show targetN/targetD and bar B must match it */ function lcheckMatch(targetN, targetD, n1, d1, n2, d2) { if (n1 !== targetN || d1 !== targetD) return { pass: false, message: 'First set Bar A to ' + targetN + '/' + targetD + ' (it shows ' + n1 + '/' + d1 + ').' }; if (!lequivOK(n1, d1, n2, d2)) return { pass: false, message: 'Not yet — Bar B shows ' + n2 + '/' + d2 + ', whose shaded length differs from ' + targetN + '/' + targetD + '.' }; return { pass: true, message: 'Correct — ' + n2 + '/' + d2 + ' shades exactly the same length as ' + targetN + '/' + targetD + '.' }; } /* challenge 3 answers for 5/6 */ function lcheckPredict() { return { d18: ldenomWorks(5, 6, 18), d20: ldenomWorks(5, 6, 20) }; } /* == /PURE == */ (function () { 'use strict'; var state = { n1: 3, d1: 4, n2: 6, d2: 8, found: {} }; function el(id) { return document.getElementById(id); } function barSVG(n, d, fill) { var w = 620, h = 74, x0 = 14, bw = w - 28, seg = bw / d, s = ''; s += ''; return s; }
function draw() { if (state.n1 > state.d1) state.n1 = state.d1; if (state.n2 > state.d2) state.n2 = state.d2; el('n1s').value = state.n1; el('d1s').value = state.d1; el('n2s').value = state.n2; el('d2s').value = state.d2; el('n1v').textContent = state.n1; el('d1v').textContent = state.d1; el('n2v').textContent = state.n2; el('d2v').textContent = state.d2; el('barA').innerHTML = barSVG(state.n1, state.d1, '#2563EB'); el('barB').innerHTML = barSVG(state.n2, state.d2, '#14B8A6'); var ok = lequivOK(state.n1, state.d1, state.n2, state.d2); var note = el('matchNote'); if (ok) { note.textContent = 'MATCH — ' + state.n2 + '/' + state.d2 + ' shades exactly the same length as ' + state.n1 + '/' + state.d1 + '.'; note.style.background = '#F0FDF9'; note.style.borderColor = '#99E4DA'; note.style.color = '#065f46'; state.found[state.d2] = 1; el('foundCount').textContent = Object.keys(state.found).length; } else { note.textContent = 'Not matching — the shaded lengths differ. Cross-products: ' + state.n1 + '×' + state.d2 + '=' + (state.n1 * state.d2) + ' vs ' + state.d1 + '×' + state.n2 + '=' + (state.d1 * state.n2) + '.'; note.style.background = '#fff'; note.style.borderColor = '#E2E8F0'; note.style.color = '#475569'; } var works = ldenomWorks(state.n1, state.d1, state.d2); el('multNote').textContent = works ? 'Denominator ' + state.d2 + ' is a multiple of ' + state.d1 + ' (×' + (state.d2 / state.d1) + '), so an exact equivalent exists: ' + lequivNum(state.n1, state.d1, state.d2) + '/' + state.d2 + '.' : 'Denominator ' + state.d2 + ' is NOT a multiple of ' + state.d1 + ' — no whole-number numerator makes an exact match. Try a multiple of ' + state.d1 + '.'; }
el('n1s').addEventListener('input', function () { state.n1 = parseInt(this.value, 10); draw(); }); el('d1s').addEventListener('input', function () { state.d1 = parseInt(this.value, 10); draw(); }); el('n2s').addEventListener('input', function () { state.n2 = parseInt(this.value, 10); draw(); }); el('d2s').addEventListener('input', function () { state.d2 = parseInt(this.value, 10); draw(); }); el('resetBtn').addEventListener('click', function () { state = { n1: 3, d1: 4, n2: 6, d2: 8, found: {} }; el('foundCount').textContent = '0'; ['c1r', 'c2r', 'c3r'].forEach(function (id) { var r = el(id); r.textContent = ''; r.className = 'c-result'; }); draw(); }); el('setA34').addEventListener('click', function () { state.n1 = 3; state.d1 = 4; draw(); }); el('setA23').addEventListener('click', function () { state.n1 = 2; state.d1 = 3; state.found = {}; el('foundCount').textContent = '0'; draw(); });
el('c1check').addEventListener('click', function () { var r = lcheckMatch(3, 4, state.n1, state.d1, state.n2, state.d2); var box = el('c1r'); box.textContent = r.message; box.className = 'c-result ' + (r.pass ? 'pass' : 'fail'); }); el('c2check').addEventListener('click', function () { var okA = !((state.n1 !== 2) || (state.d1 !== 3)); var count = Object.keys(state.found).length; var box = el('c2r'); if (!okA) { box.textContent = 'First set Bar A to 2/3 (it shows ' + state.n1 + '/' + state.d1 + ').'; box.className = 'c-result fail'; return; } if (count >= 3) { box.textContent = 'Correct — you matched ' + count + ' different denominators. Infinitely many names, one amount.'; box.className = 'c-result pass'; } else { box.textContent = 'Not yet — you have matched ' + count + ' denominator(s) so far. Find ' + (3 - count) + ' more (press "Set Bar A to 2/3" to restart the count).'; box.className = 'c-result fail'; } }); var pred = { d18: null, d20: null }; function predBtn(id, key, val) { el(id).addEventListener('click', function () { pred[key] = val; document.querySelectorAll('[data-pred="' + key + '"]').forEach(function (b) { b.classList.remove('btn-primary'); b.classList.add('btn-ghost'); }); el(id).classList.remove('btn-ghost'); el(id).classList.add('btn-primary'); }); } predBtn('p18y', 'd18', true); predBtn('p18n', 'd18', false); predBtn('p20y', 'd20', true); predBtn('p20n', 'd20', false); el('c3check').addEventListener('click', function () { var exp = lcheckPredict(); var box = el('c3r'); if (pred.d18 === null || pred.d20 === null) { box.textContent = 'Answer both questions first.'; box.className = 'c-result fail'; return; } var ok = !((pred.d18 !== exp.d18) || (pred.d20 !== exp.d20)); box.textContent = ok ? 'Correct — 18 is a multiple of 6 (5/6 = 15/18), but 20 is not, so no whole-number numerator works for denominator 20.' : 'Not quite — think about multiples: is 18 a multiple of 6? Is 20?'; box.className = 'c-result ' + (ok ? 'pass' : 'fail'); });
draw(); })();
/* == PURE (tested by test_lab.js) == */ function lequivOK(n1, d1, n2, d2) { if (d1 <= 0 || d2 <= 0) return false; return n1 * d2 === n2 * d1; } /* can n/d be renamed with denominator D? (D a multiple of d, or n=0) */ function ldenomWorks(n, d, D) { if (d <= 0 || D <= 0) return false; if (n === 0) return true; return (D * n) % d === 0; } function lequivNum(n, d, D) { /* the matching numerator; valid only when ldenomWorks */ if (n === 0) return 0; return (D * n) / d; } function ldec(n, d) { return n / d; } /* challenge 1: bar A must show targetN/targetD and bar B must match it */ function lcheckMatch(targetN, targetD, n1, d1, n2, d2) { if (n1 !== targetN || d1 !== targetD) return { pass: false, message: 'First set Bar A to ' + targetN + '/' + targetD + ' (it shows ' + n1 + '/' + d1 + ').' }; if (!lequivOK(n1, d1, n2, d2)) return { pass: false, message: 'Not yet — Bar B shows ' + n2 + '/' + d2 + ', whose shaded length differs from ' + targetN + '/' + targetD + '.' }; return { pass: true, message: 'Correct — ' + n2 + '/' + d2 + ' shades exactly the same length as ' + targetN + '/' + targetD + '.' }; } /* challenge 3 answers for 5/6 */ function lcheckPredict() { return { d18: ldenomWorks(5, 6, 18), d20: ldenomWorks(5, 6, 20) }; } /* == /PURE == */ (function () { 'use strict'; var state = { n1: 3, d1: 4, n2: 6, d2: 8, found: {} }; function el(id) { return document.getElementById(id); } function barSVG(n, d, fill) { var w = 620, h = 74, x0 = 14, bw = w - 28, seg = bw / d, s = ''; s += ''; return s; }
function draw() { if (state.n1 > state.d1) state.n1 = state.d1; if (state.n2 > state.d2) state.n2 = state.d2; el('n1s').value = state.n1; el('d1s').value = state.d1; el('n2s').value = state.n2; el('d2s').value = state.d2; el('n1v').textContent = state.n1; el('d1v').textContent = state.d1; el('n2v').textContent = state.n2; el('d2v').textContent = state.d2; el('barA').innerHTML = barSVG(state.n1, state.d1, '#2563EB'); el('barB').innerHTML = barSVG(state.n2, state.d2, '#14B8A6'); var ok = lequivOK(state.n1, state.d1, state.n2, state.d2); var note = el('matchNote'); if (ok) { note.textContent = 'MATCH — ' + state.n2 + '/' + state.d2 + ' shades exactly the same length as ' + state.n1 + '/' + state.d1 + '.'; note.style.background = '#F0FDF9'; note.style.borderColor = '#99E4DA'; note.style.color = '#065f46'; state.found[state.d2] = 1; el('foundCount').textContent = Object.keys(state.found).length; } else { note.textContent = 'Not matching — the shaded lengths differ. Cross-products: ' + state.n1 + '×' + state.d2 + '=' + (state.n1 * state.d2) + ' vs ' + state.d1 + '×' + state.n2 + '=' + (state.d1 * state.n2) + '.'; note.style.background = '#fff'; note.style.borderColor = '#E2E8F0'; note.style.color = '#475569'; } var works = ldenomWorks(state.n1, state.d1, state.d2); el('multNote').textContent = works ? 'Denominator ' + state.d2 + ' is a multiple of ' + state.d1 + ' (×' + (state.d2 / state.d1) + '), so an exact equivalent exists: ' + lequivNum(state.n1, state.d1, state.d2) + '/' + state.d2 + '.' : 'Denominator ' + state.d2 + ' is NOT a multiple of ' + state.d1 + ' — no whole-number numerator makes an exact match. Try a multiple of ' + state.d1 + '.'; }
el('n1s').addEventListener('input', function () { state.n1 = parseInt(this.value, 10); draw(); }); el('d1s').addEventListener('input', function () { state.d1 = parseInt(this.value, 10); draw(); }); el('n2s').addEventListener('input', function () { state.n2 = parseInt(this.value, 10); draw(); }); el('d2s').addEventListener('input', function () { state.d2 = parseInt(this.value, 10); draw(); }); el('resetBtn').addEventListener('click', function () { state = { n1: 3, d1: 4, n2: 6, d2: 8, found: {} }; el('foundCount').textContent = '0'; ['c1r', 'c2r', 'c3r'].forEach(function (id) { var r = el(id); r.textContent = ''; r.className = 'c-result'; }); draw(); }); el('setA34').addEventListener('click', function () { state.n1 = 3; state.d1 = 4; draw(); }); el('setA23').addEventListener('click', function () { state.n1 = 2; state.d1 = 3; state.found = {}; el('foundCount').textContent = '0'; draw(); });
el('c1check').addEventListener('click', function () { var r = lcheckMatch(3, 4, state.n1, state.d1, state.n2, state.d2); var box = el('c1r'); box.textContent = r.message; box.className = 'c-result ' + (r.pass ? 'pass' : 'fail'); }); el('c2check').addEventListener('click', function () { var okA = !((state.n1 !== 2) || (state.d1 !== 3)); var count = Object.keys(state.found).length; var box = el('c2r'); if (!okA) { box.textContent = 'First set Bar A to 2/3 (it shows ' + state.n1 + '/' + state.d1 + ').'; box.className = 'c-result fail'; return; } if (count >= 3) { box.textContent = 'Correct — you matched ' + count + ' different denominators. Infinitely many names, one amount.'; box.className = 'c-result pass'; } else { box.textContent = 'Not yet — you have matched ' + count + ' denominator(s) so far. Find ' + (3 - count) + ' more (press "Set Bar A to 2/3" to restart the count).'; box.className = 'c-result fail'; } }); var pred = { d18: null, d20: null }; function predBtn(id, key, val) { el(id).addEventListener('click', function () { pred[key] = val; document.querySelectorAll('[data-pred="' + key + '"]').forEach(function (b) { b.classList.remove('btn-primary'); b.classList.add('btn-ghost'); }); el(id).classList.remove('btn-ghost'); el(id).classList.add('btn-primary'); }); } predBtn('p18y', 'd18', true); predBtn('p18n', 'd18', false); predBtn('p20y', 'd20', true); predBtn('p20n', 'd20', false); el('c3check').addEventListener('click', function () { var exp = lcheckPredict(); var box = el('c3r'); if (pred.d18 === null || pred.d20 === null) { box.textContent = 'Answer both questions first.'; box.className = 'c-result fail'; return; } var ok = !((pred.d18 !== exp.d18) || (pred.d20 !== exp.d20)); box.textContent = ok ? 'Correct — 18 is a multiple of 6 (5/6 = 15/18), but 20 is not, so no whole-number numerator works for denominator 20.' : 'Not quite — think about multiples: is 18 a multiple of 6? Is 20?'; box.className = 'c-result ' + (ok ? 'pass' : 'fail'); });
draw(); })();