<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM操作リファレンス</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--orange: #D97706;
--orange-light: #FEF3C7;
--cream: #FFFBF5;
--sand: #F5F0E8;
--brown: #92400E;
--text-dark: #1C1917;
--text-mid: #44403C;
--text-light: #78716C;
--border: #E7E0D8;
--green: #059669;
--green-light: #ECFDF5;
--blue: #2563EB;
--blue-light: #EFF6FF;
--red: #DC2626;
--purple: #7C3AED;
--purple-light: #F5F3FF;
--white: #FFFFFF;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Noto Sans JP', sans-serif; background: var(--cream); color: var(--text-dark); line-height: 1.7; }
.hero { background: linear-gradient(135deg, #FFFBF5 0%, #FEF3C7 50%, #FDE68A 100%); padding: 64px 24px 48px; text-align: center; border-bottom: 2px solid var(--border); }
.badge { display: inline-block; background: var(--orange); color: white; font-size: 11px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; padding: 4px 14px; border-radius: 100px; margin-bottom: 16px; }
.hero h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); font-weight: 700; margin-bottom: 10px; }
.hero h1 span { color: var(--orange); }
.hero p { font-size: 0.97rem; color: var(--text-mid); max-width: 520px; margin: 0 auto; }
.toc { background: var(--white); border-bottom: 1px solid var(--border); padding: 12px 24px; display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; position: sticky; top: 0; z-index: 100; box-shadow: 0 1px 8px rgba(0,0,0,0.06); }
.toc a { font-size: 0.79rem; font-weight: 600; text-decoration: none; padding: 5px 12px; border-radius: 100px; border: 1.5px solid var(--border); color: var(--text-mid); transition: all 0.2s; white-space: nowrap; }
.toc a:hover { background: var(--orange); color: white; border-color: var(--orange); }
.container { max-width: 960px; margin: 0 auto; padding: 0 20px; }
.section-intro { padding: 52px 24px 10px; max-width: 960px; margin: 0 auto; }
.section-label { font-size: 11px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--orange); margin-bottom: 5px; }
.section-intro h2 { font-size: 1.55rem; font-weight: 700; margin-bottom: 5px; }
.section-intro p { color: var(--text-mid); font-size: 0.9rem; }
.card { background: var(--white); border: 1.5px solid var(--border); border-radius: 16px; margin: 0 auto 28px; max-width: 960px; overflow: hidden; box-shadow: 0 2px 12px rgba(0,0,0,0.04); }
.card-header { padding: 16px 22px 12px; border-bottom: 1px solid var(--border); display: flex; align-items: flex-start; gap: 12px; }
.method-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.1rem; flex-shrink: 0; }
.icon-prop { background: var(--purple-light); }
.icon-search { background: var(--blue-light); }
.icon-manip { background: var(--green-light); }
.card-header-text h3 { font-size: 1.02rem; font-weight: 700; margin-bottom: 2px; }
.mname { font-family: 'Fira Code', monospace; background: var(--sand); padding: 1px 7px; border-radius: 5px; font-size: 0.83rem; color: var(--brown); font-weight: 500; }
.card-header-text p { font-size: 0.83rem; color: var(--text-light); margin-top: 2px; }
/* USE CASE */
.usecase { background: linear-gradient(90deg, #FEF3C7, #FFFBF5); border-left: 3px solid var(--orange); padding: 9px 18px; margin: 12px 20px 0; border-radius: 0 8px 8px 0; }
.usecase-text { font-size: 0.85rem; font-weight: 600; color: var(--brown); }
.usecase-text em { color: var(--orange); font-style: normal; }
/* OUTPUT CARD */
.output-card {
margin: 14px 22px 0;
border: 1.5px solid var(--border);
border-radius: 10px;
overflow: hidden;
}
.output-card-label {
background: var(--sand);
padding: 8px 16px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-mid);
border-bottom: 1px solid var(--border);
display: flex;
align-items: baseline;
gap: 8px;
}
.output-card-desc {
font-size: 12px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
color: var(--text-mid);
font-family: 'Fira Code', monospace;
}
.output-card-body {
background: var(--white);
padding: 14px 16px;
min-height: 52px;
}
/* INITIAL VALUE STRIP */
.output-initial {
background: #F8F5F0;
border-bottom: 1px solid var(--border);
padding: 6px 16px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.initial-badge {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
background: var(--orange-light);
color: var(--brown);
border: 1px solid #FCD34D;
border-radius: 4px;
padding: 1px 7px;
white-space: nowrap;
flex-shrink: 0;
}
.initial-code {
font-family: 'Fira Code', monospace;
font-size: 0.75rem;
color: var(--text-light);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* DEMO AREA */
.demo-area { padding: 16px 22px 20px; }
/* SPLIT: left=steps, right=code */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
@media(max-width:660px) { .split { grid-template-columns: 1fr; } }
/* STEPS PANEL */
.steps-panel { background: var(--sand); border-radius: 10px; padding: 14px; }
.code-panel { background: #1C1917; border-radius: 10px; padding: 14px; }
.panel-lbl { font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 10px; display: flex; align-items: center; gap: 5px; }
.panel-lbl.sp { color: var(--text-mid); }
.panel-lbl.cd { color: #FDE68A; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
/* CODE LINES — highlight only, no callouts */
.code-lines { font-family: 'Fira Code', monospace; font-size: 0.79rem; line-height: 1.9; }
.cl { display: flex; align-items: center; border-radius: 4px; transition: background 0.2s; padding: 0 4px; }
.ln { color: #374151; user-select: none; min-width: 16px; text-align: right; margin-right: 10px; font-size: 0.7rem; flex-shrink: 0; }
.lb { color: #D1D5DB; flex: 1; white-space: pre; overflow: hidden; }
.lb .kw { color: #F97316; }
.lb .str { color: #A3E635; }
.lb .com { color: #4B5563; font-style: italic; }
.lb .pp { color: #38BDF8; }
.lb .fn { color: #C084FC; }
.lb .vr { color: #FDE68A; }
.cl.cur { background: rgba(217,119,6,0.26); border-left: 3px solid var(--orange); margin-left: -3px; padding-left: 7px; }
.cl.cur .ln { color: var(--orange); }
.cl.done { background: rgba(5,150,105,0.10); }
.cl.done .ln { color: var(--green); }
/* EXPLANATION BOX — fixed height so button never moves */
.explanation {
background: var(--blue-light);
border: 1.5px solid #BFDBFE;
border-radius: 8px;
padding: 10px 14px;
margin-bottom: 12px;
font-size: 0.83rem;
height: 112px;
overflow-y: auto;
}
.ex-step { font-weight: 700; color: var(--blue); margin-bottom: 3px; font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; }
.ex-text { color: var(--text-dark); line-height: 1.55; }
.explanation.idle { background: #F9FAFB; border-color: var(--border); }
.explanation.idle .ex-step { color: var(--text-light); }
.explanation.idle .ex-text { color: var(--text-light); }
.explanation.done-all { background: var(--green-light); border-color: #A7F3D0; }
.explanation.done-all .ex-step { color: var(--green); }
/* INDEX GRID */
.index-section { background: var(--white); border-bottom: 1px solid var(--border); padding: 28px 24px 32px; }
.index-section h2 { font-size: 0.82rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-light); text-align: center; margin-bottom: 20px; }
.index-group { max-width: 960px; margin: 0 auto 20px; }
.index-group-label { font-size: 10px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-light); margin-bottom: 8px; padding-left: 2px; display: flex; align-items: center; gap: 6px; }
.index-group-label::before { content: ''; display: block; height: 1px; flex: 1; background: var(--border); }
.index-group-label::after { content: ''; display: block; height: 1px; flex: 1; background: var(--border); }
.index-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 8px; max-width: 960px; margin: 0 auto; }
.index-card {
display: block;
text-decoration: none;
background: var(--cream);
border: 1.5px solid var(--border);
border-radius: 10px;
padding: 10px 13px;
transition: all 0.18s;
cursor: pointer;
}
.index-card:hover { background: var(--white); border-color: var(--orange); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
.index-card-name { font-family: 'Fira Code', monospace; font-size: 0.8rem; font-weight: 500; color: var(--brown); margin-bottom: 4px; }
.index-card-desc { font-size: 0.75rem; color: var(--text-light); line-height: 1.4; }
/* value highlight inside explanation */
.val { display: inline-block; font-family: 'Fira Code', monospace; background: #1C1917; color: #FDE68A; padding: 1px 7px; border-radius: 4px; font-size: 0.82rem; }
/* STEP CONTROLS */
.step-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.btn-step { background: var(--orange); color: white; border: none; padding: 8px 18px; border-radius: 8px; font-family: 'Noto Sans JP', sans-serif; font-size: 0.82rem; font-weight: 700; cursor: pointer; transition: all 0.15s; }
.btn-step:hover { background: var(--brown); transform: translateY(-1px); }
.btn-step:disabled { background: #D1D5DB; color: #9CA3AF; cursor: not-allowed; transform: none; }
.btn-reset-s { background: transparent; color: var(--text-light); border: 1.5px solid var(--border); padding: 7px 13px; border-radius: 8px; font-family: 'Noto Sans JP', sans-serif; font-size: 0.79rem; font-weight: 600; cursor: pointer; transition: all 0.15s; }
.btn-reset-s:hover { background: var(--white); color: var(--text-dark); }
.step-counter { font-size: 0.77rem; color: var(--text-light); font-family: 'Fira Code', monospace; }
.step-counter b { color: var(--orange); }
.step-progress { height: 3px; background: var(--border); border-radius: 100px; margin-top: 10px; overflow: hidden; }
.step-progress-fill { height: 100%; background: var(--orange); border-radius: 100px; transition: width 0.25s ease; }
/* LIVE ELEMENTS inside output card */
.live-el { font-size: 0.87rem; transition: all 0.3s; padding: 4px 0; }
.live-el.flash { color: var(--green); }
.list-live { list-style: none; }
.list-live li { background: var(--sand); border: 1.5px solid var(--border); border-radius: 6px; padding: 7px 11px; margin-bottom: 5px; font-size: 0.82rem; display: flex; align-items: center; gap: 7px; transition: all 0.3s; }
.list-live li::before { content: "▸"; color: var(--orange); flex-shrink: 0; }
.new-item { background: #FEF3C7 !important; border-color: var(--orange) !important; }
.out-placeholder { color: var(--text-light); font-size: 0.83rem; font-style: italic; }
.divider { height: 1px; background: linear-gradient(90deg, transparent, var(--border), transparent); margin: 44px auto; max-width: 960px; }
.tag { border-radius: 100px; padding: 2px 10px; font-size: 0.71rem; font-weight: 700; }
.tp { background: var(--purple-light); color: var(--purple); }
.ts { background: var(--blue-light); color: var(--blue); }
.tm { background: var(--green-light); color: var(--green); }
.pad { padding: 0 22px; }
</style>
</head>
<body>
<div class="hero">
<div class="badge">JavaScript DOM</div>
<h1>DOM操作を<span>1ステップずつ</span>理解する</h1>
<p>「次のステップへ →」を押すたびにコードが1行ずつ実行され、出力がどう変わるかがわかる。</p>
</div>
<div class="index-section">
<h2>📋 メソッド一覧 — クリックで該当箇所へ</h2>
<div class="index-group">
<div class="index-group-label">📦 プロパティ</div>
<div class="index-grid">
<a class="index-card" href="#card-tc"><span class="index-card-name">textContent</span><span class="index-card-desc">文字だけ変えたい</span></a>
<a class="index-card" href="#card-ih"><span class="index-card-name">innerHTML</span><span class="index-card-desc">HTMLタグを含めて書き換えたい</span></a>
<a class="index-card" href="#card-tg"><span class="index-card-name">tagName / id</span><span class="index-card-desc">要素の種類・識別子を調べたい</span></a>
</div>
</div>
<div class="index-group">
<div class="index-group-label">🔍 検索メソッド</div>
<div class="index-grid">
<a class="index-card" href="#card-qs"><span class="index-card-name">querySelector()</span><span class="index-card-desc">最初の1要素を取得</span></a>
<a class="index-card" href="#card-qsa"><span class="index-card-name">querySelectorAll()</span><span class="index-card-desc">条件に合う全要素を取得</span></a>
</div>
</div>
<div class="index-group" style="margin-bottom:0;">
<div class="index-group-label">⚙️ 操作メソッド</div>
<div class="index-grid">
<a class="index-card" href="#card-ac"><span class="index-card-name">appendChild()</span><span class="index-card-desc">末尾に要素を追加</span></a>
<a class="index-card" href="#card-ib"><span class="index-card-name">insertBefore()</span><span class="index-card-desc">指定要素の前に挿入</span></a>
<a class="index-card" href="#card-rc"><span class="index-card-name">removeChild()</span><span class="index-card-desc">子要素を削除</span></a>
<a class="index-card" href="#card-rp"><span class="index-card-name">replaceChild()</span><span class="index-card-desc">別の要素と置き換え</span></a>
</div>
</div>
</div>
<nav class="toc">
<a href="#props">📦 プロパティ</a>
<a href="#search">🔍 検索メソッド</a>
<a href="#manip">⚙️ 操作メソッド</a>
<a href="#summary">📋 まとめ</a>
</nav>
<!-- ================================================================ PROPERTIES -->
<section id="props">
<div class="section-intro">
<div class="section-label">📦 Properties</div>
<h2>プロパティ — 情報を取得・設定する</h2>
<p>要素が持つデータを読み書きする「窓口」。代入するだけで画面が変わる。</p>
</div>
<!-- textContent -->
<div class="card container pad" id="card-tc">
<div class="card-header">
<div class="method-icon icon-prop">📝</div>
<div class="card-header-text">
<h3><span class="mname">textContent</span> — テキストを書き換える</h3>
<p>タグを含まない純粋なテキスト内容を取得・設定。HTMLは無視される。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「ボタンを押したら<em>ステータス文字</em>を変えたい!」→ textContent</span>
</div>
<!-- OUTPUT CARD -->
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— el.textContent の現在値</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><div id="status">読み込み中...</div></span>
</div>
<div class="output-card-body">
<div class="live-el" id="tc-out">読み込み中...</div>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="tc-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="tc-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="tc-next" onclick="step('tc')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('tc')">リセット</button>
<span class="step-counter">ステップ <b id="tc-cur">0</b> / <b id="tc-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="tc-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">el</span> = document.<span class="fn">querySelector</span>(<span class="str">'#status'</span>);</span></div>
<div class="cl" id="tc-s1"><span class="ln">2</span><span class="lb"><span class="vr">el</span>.<span class="pp">textContent</span> = <span class="str">'データ取得しました ✓'</span>;</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- innerHTML -->
<div class="card container pad" id="card-ih">
<div class="card-header">
<div class="method-icon icon-prop">🏗️</div>
<div class="card-header-text">
<h3><span class="mname">innerHTML</span> — HTMLごと書き換える</h3>
<p>内部のHTML文字列を丸ごと取得・設定。タグも自由に挿入できる。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「タグ付きHTMLを<em>そのままレンダリング</em>したい!」→ innerHTML</span>
</div>
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— card.innerHTML のレンダリング結果</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><div id="result">コンテンツなし</div></span>
</div>
<div class="output-card-body">
<div class="live-el" id="ih-out">コンテンツなし</div>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="ih-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="ih-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="ih-next" onclick="step('ih')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('ih')">リセット</button>
<span class="step-counter">ステップ <b id="ih-cur">0</b> / <b id="ih-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="ih-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">card</span> = document.<span class="fn">querySelector</span>(<span class="str">'#result'</span>);</span></div>
<div class="cl" id="ih-s1"><span class="ln">2</span><span class="lb"><span class="vr">card</span>.<span class="pp">innerHTML</span> = <span class="str">`<b>🎉 成功!</b> `</span></span></div>
<div class="cl" id="ih-s2"><span class="ln">3</span><span class="lb"> + <span class="str">`<em>@taro</em> がログイン`</span>;</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- tagName / id -->
<div class="card container pad" id="card-tg">
<div class="card-header">
<div class="method-icon icon-prop">🏷️</div>
<div class="card-header-text">
<h3><span class="mname">tagName</span> / <span class="mname">id</span> — 要素の素性を調べる</h3>
<p>tagName はタグ種別("DIV" / "P"...)、id は識別子を返す。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「クリックされた要素が<em>何のタグか</em>によって処理を分けたい!」→ tagName</span>
</div>
<!-- clickable elements -->
<div style="padding: 14px 22px 0; display:flex; gap:8px; flex-wrap:wrap;">
<button id="probe-btn" style="background:var(--brown);color:white;border:none;padding:7px 15px;border-radius:7px;font-size:0.82rem;font-weight:600;cursor:pointer;" onclick="probeClick(this)">ボタン要素</button>
<p id="probe-p" style="cursor:pointer;padding:7px 15px;background:var(--white);border:1.5px solid var(--border);border-radius:7px;font-size:0.82rem;" onclick="probeClick(this)">P 要素</p>
<div id="probe-div" style="cursor:pointer;padding:7px 15px;background:var(--orange-light);border:1.5px solid var(--border);border-radius:7px;font-size:0.82rem;" onclick="probeClick(this)">DIV 要素</div>
</div>
<div class="output-card" style="margin-top:14px;">
<div class="output-card-label">出力 <span class="output-card-desc">— e.target.tagName / e.target.id の値</span></div>
<div class="output-initial">
<span class="initial-badge">クリック待ち</span>
<span class="initial-code">上の要素をクリックすると tagName と id の値が確認できる</span>
</div>
<div class="output-card-body">
<div id="tg-out" class="out-placeholder">← 上の要素をクリックすると tagName と id が表示される</div>
</div>
</div>
<div class="demo-area">
<!-- explanation only — no steps needed -->
<div id="tg-explain" class="explanation idle" style="margin-bottom:0;">
<div class="ex-step">解説</div>
<div class="ex-text">上の要素をクリックすると <span class="val">e.target.tagName</span> と <span class="val">e.target.id</span> がどんな値を持つか確認できる。</div>
</div>
<div style="margin-top:14px;">
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl"><span class="ln">1</span><span class="lb"><span class="vr">el</span>.<span class="fn">addEventListener</span>(<span class="str">'click'</span>, (e) => {</span></div>
<div class="cl"><span class="ln">2</span><span class="lb"> <span class="kw">const</span> <span class="vr">tag</span> = e.<span class="pp">target</span>.<span class="pp">tagName</span>;</span></div>
<div class="cl"><span class="ln">3</span><span class="lb"> <span class="kw">const</span> <span class="vr">id</span> = e.<span class="pp">target</span>.<span class="pp">id</span>;</span></div>
<div class="cl"><span class="ln">4</span><span class="lb"> console.<span class="fn">log</span>(<span class="vr">tag</span>, <span class="vr">id</span>);</span></div>
<div class="cl"><span class="ln">5</span><span class="lb">});</span></div>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- ================================================================ SEARCH -->
<section id="search">
<div class="section-intro">
<div class="section-label">🔍 Search Methods</div>
<h2>検索メソッド — 要素を見つける</h2>
<p>CSSセレクタを使ってページ内の要素を自在に取得する。</p>
</div>
<!-- querySelector -->
<div class="card container pad" id="card-qs">
<div class="card-header">
<div class="method-icon icon-search">🎯</div>
<div class="card-header-text">
<h3><span class="mname">querySelector()</span> — 最初の1つを取得</h3>
<p>CSSセレクタに一致する最初の要素を返す。見つからなければ null。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「フォームの<em>送信ボタン1つ</em>を取得して disabled にしたい!」→ querySelector</span>
</div>
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— querySelector で取得したボタンの状態</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><button class="submit-btn">✉️ メイン送信</button></span>
</div>
<div class="output-card-body" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
<button style="background:#78716C;color:white;border:none;padding:7px 14px;border-radius:7px;font-size:0.8rem;font-weight:600;">送信</button>
<button style="background:#78716C;color:white;border:none;padding:7px 14px;border-radius:7px;font-size:0.8rem;font-weight:600;">キャンセル</button>
<button id="qs-main" class="submit-btn" style="background:var(--orange);color:white;border:none;padding:7px 14px;border-radius:7px;font-size:0.8rem;font-weight:600;">✉️ メイン送信</button>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="qs-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="qs-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="qs-next" onclick="step('qs')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('qs')">リセット</button>
<span class="step-counter">ステップ <b id="qs-cur">0</b> / <b id="qs-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="qs-s0"><span class="ln">1</span><span class="lb"><span class="com">// .submit-btn の最初の1つだけ取得</span></span></div>
<div class="cl" id="qs-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">btn</span> = document.<span class="fn">querySelector</span>(<span class="str">'.submit-btn'</span>);</span></div>
<div class="cl" id="qs-s2"><span class="ln">3</span><span class="lb"><span class="vr">btn</span>.<span class="pp">disabled</span> = <span class="kw">true</span>;</span></div>
<div class="cl" id="qs-s3"><span class="ln">4</span><span class="lb"><span class="vr">btn</span>.<span class="pp">textContent</span> = <span class="str">'送信済み ✓'</span>;</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- querySelectorAll -->
<div class="card container pad" id="card-qsa">
<div class="card-header">
<div class="method-icon icon-search">🔦</div>
<div class="card-header-text">
<h3><span class="mname">querySelectorAll()</span> — 全てを取得</h3>
<p>CSSセレクタに一致する全要素を NodeList で返す。forEach でまとめて処理できる。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「未完了の<em>全タスクをまとめて</em>グレーアウトしたい!」→ querySelectorAll</span>
</div>
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— querySelectorAll で取得した要素への適用結果</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><li class="task-item">コードレビュー</li> など、done なしが3件</span>
</div>
<div class="output-card-body">
<ul class="list-live" id="qsa-list">
<li class="task-item done">✅ デザイン確認</li>
<li class="task-item">コードレビュー</li>
<li class="task-item done">✅ テスト実行</li>
<li class="task-item">デプロイ</li>
<li class="task-item">ドキュメント更新</li>
</ul>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="qsa-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="qsa-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="qsa-next" onclick="step('qsa')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('qsa')">リセット</button>
<span class="step-counter">ステップ <b id="qsa-cur">0</b> / <b id="qsa-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="qsa-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">items</span> = document.<span class="fn">querySelectorAll</span>(</span></div>
<div class="cl" id="qsa-s1"><span class="ln">2</span><span class="lb"> <span class="str">'.task-item:not(.done)'</span>);</span></div>
<div class="cl" id="qsa-s2"><span class="ln">3</span><span class="lb"><span class="vr">items</span>.<span class="fn">forEach</span>((<span class="vr">item</span>) => {</span></div>
<div class="cl" id="qsa-s3"><span class="ln">4</span><span class="lb"> <span class="vr">item</span>.<span class="pp">style</span>.opacity = <span class="str">'0.3'</span>;</span></div>
<div class="cl" id="qsa-s4"><span class="ln">5</span><span class="lb"> <span class="vr">item</span>.<span class="pp">style</span>.textDecoration = <span class="str">'line-through'</span>;</span></div>
<div class="cl"><span class="ln">6</span><span class="lb">});</span></div>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- ================================================================ MANIPULATION -->
<section id="manip">
<div class="section-intro">
<div class="section-label">⚙️ Manipulation Methods</div>
<h2>操作メソッド — 要素を追加・削除・移動する</h2>
<p>DOMツリーを自在に組み替えて、動的なUIを作る。</p>
</div>
<!-- appendChild -->
<div class="card container pad" id="card-ac">
<div class="card-header">
<div class="method-icon icon-manip">➕</div>
<div class="card-header-text">
<h3><span class="mname">appendChild()</span> — 末尾に追加</h3>
<p>指定した親要素の子として、末尾に新しい要素を追加する。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「ボタンを押すたびに<em>コメントを末尾へ追加</em>したい!」→ appendChild</span>
</div>
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— appendChild 後の <ul> の中身</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><ul id="comments"><li>最初のコメント 👋</li></ul></span>
</div>
<div class="output-card-body">
<ul class="list-live" id="ac-list">
<li>最初のコメント 👋</li>
</ul>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="ac-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="ac-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="ac-next" onclick="step('ac')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('ac')">リセット</button>
<span class="step-counter">ステップ <b id="ac-cur">0</b> / <b id="ac-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="ac-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">list</span> = document.<span class="fn">querySelector</span>(<span class="str">'#comments'</span>);</span></div>
<div class="cl" id="ac-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">li</span> = document.<span class="fn">createElement</span>(<span class="str">'li'</span>);</span></div>
<div class="cl" id="ac-s2"><span class="ln">3</span><span class="lb"><span class="vr">li</span>.<span class="pp">textContent</span> = <span class="str">'新しいコメント 🗨️'</span>;</span></div>
<div class="cl" id="ac-s3"><span class="ln">4</span><span class="lb"><span class="vr">list</span>.<span class="fn">appendChild</span>(<span class="vr">li</span>);</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- insertBefore -->
<div class="card container pad" id="card-ib">
<div class="card-header">
<div class="method-icon icon-manip">⬆️</div>
<div class="card-header-text">
<h3><span class="mname">insertBefore()</span> — 指定要素の前に挿入</h3>
<p>親要素に対して「新要素を、この既存要素の直前に入れろ」と指示する。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「緊急通知を<em>リストの先頭</em>に割り込みで入れたい!」→ insertBefore</span>
</div>
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— insertBefore 後の <ul> の中身</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><ul id="notices"><li>📌 通常のお知らせ A</li> ...</ul></span>
</div>
<div class="output-card-body">
<ul class="list-live" id="ib-list">
<li id="ib-first">📌 通常のお知らせ A</li>
<li>📌 通常のお知らせ B</li>
<li>📌 通常のお知らせ C</li>
</ul>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="ib-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="ib-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="ib-next" onclick="step('ib')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('ib')">リセット</button>
<span class="step-counter">ステップ <b id="ib-cur">0</b> / <b id="ib-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="ib-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">list</span> = document.<span class="fn">querySelector</span>(<span class="str">'#notices'</span>);</span></div>
<div class="cl" id="ib-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">first</span> = list.<span class="fn">querySelector</span>(<span class="str">'li:first-child'</span>);</span></div>
<div class="cl" id="ib-s2"><span class="ln">3</span><span class="lb"><span class="kw">const</span> <span class="vr">urgent</span> = document.<span class="fn">createElement</span>(<span class="str">'li'</span>);</span></div>
<div class="cl" id="ib-s3"><span class="ln">4</span><span class="lb"><span class="vr">urgent</span>.<span class="pp">textContent</span> = <span class="str">'🚨 緊急メンテナンス 22:00〜'</span>;</span></div>
<div class="cl" id="ib-s4"><span class="ln">5</span><span class="lb"><span class="vr">list</span>.<span class="fn">insertBefore</span>(<span class="vr">urgent</span>, <span class="vr">first</span>);</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- removeChild -->
<div class="card container pad" id="card-rc">
<div class="card-header">
<div class="method-icon icon-manip">🗑️</div>
<div class="card-header-text">
<h3><span class="mname">removeChild()</span> — 子要素を削除</h3>
<p>親要素から指定した子要素を取り除く。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「完了ボタンを押した<em>タスク行を消したい!</em>」→ removeChild</span>
</div>
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— removeChild 後の <ul> の中身</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><ul><li id="rc-1">📧 メールに返信</li> ...</ul></span>
</div>
<div class="output-card-body">
<ul class="list-live" id="rc-list">
<li id="rc-1">📧 メールに返信</li>
<li id="rc-2">📅 会議資料を準備</li>
<li id="rc-3">📊 レポートを提出</li>
</ul>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="rc-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="rc-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="rc-next" onclick="step('rc')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('rc')">リセット</button>
<span class="step-counter">ステップ <b id="rc-cur">0</b> / <b id="rc-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="rc-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">el</span> = document.<span class="fn">getElementById</span>(<span class="str">'rc-1'</span>);</span></div>
<div class="cl" id="rc-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">parent</span> = <span class="vr">el</span>.<span class="pp">parentElement</span>;</span></div>
<div class="cl" id="rc-s2"><span class="ln">3</span><span class="lb"><span class="vr">parent</span>.<span class="fn">removeChild</span>(<span class="vr">el</span>);</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- replaceChild -->
<div class="card container pad" id="card-rp">
<div class="card-header">
<div class="method-icon icon-manip">🔄</div>
<div class="card-header-text">
<h3><span class="mname">replaceChild()</span> — 別の要素と置き換え</h3>
<p>既存の子要素を新しい要素と入れ替える。</p>
</div>
</div>
<div class="usecase">
<span class="usecase-text">💡 「「読み込み中」スピナーを<em>実際のカード</em>に差し替えたい!」→ replaceChild</span>
</div>
<div class="output-card">
<div class="output-card-label">出力 <span class="output-card-desc">— replaceChild 前後のコンテンツエリア</span></div>
<div class="output-initial">
<span class="initial-badge">初期HTML</span>
<span class="initial-code"><div id="loading">⏳ データを読み込み中...</div></span>
</div>
<div class="output-card-body">
<div id="rp-wrap">
<div id="rp-loader" style="color:var(--text-light);font-size:0.86rem;">⏳ データを読み込み中...</div>
</div>
</div>
</div>
<div class="demo-area">
<div class="split">
<div class="steps-panel">
<div class="panel-lbl sp"><span class="dot"></span> 手順</div>
<div class="explanation idle" id="rp-ex">
<div class="ex-step">開始前</div>
<div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>
</div>
<div class="step-progress"><div class="step-progress-fill" id="rp-prog" style="width:0%"></div></div>
<div class="step-controls" style="margin-top:10px;">
<button class="btn-step" id="rp-next" onclick="step('rp')">次のステップへ →</button>
<button class="btn-reset-s" onclick="reset('rp')">リセット</button>
<span class="step-counter">ステップ <b id="rp-cur">0</b> / <b id="rp-tot"></b></span>
</div>
</div>
<div class="code-panel">
<div class="panel-lbl cd"><span class="dot"></span> 実行されるコード</div>
<div class="code-lines">
<div class="cl" id="rp-s0"><span class="ln">1</span><span class="lb"><span class="kw">const</span> <span class="vr">wrap</span> = document.<span class="fn">querySelector</span>(<span class="str">'#wrapper'</span>);</span></div>
<div class="cl" id="rp-s1"><span class="ln">2</span><span class="lb"><span class="kw">const</span> <span class="vr">loader</span> = document.<span class="fn">querySelector</span>(<span class="str">'#loading'</span>);</span></div>
<div class="cl" id="rp-s2"><span class="ln">3</span><span class="lb"><span class="kw">const</span> <span class="vr">card</span> = document.<span class="fn">createElement</span>(<span class="str">'div'</span>);</span></div>
<div class="cl" id="rp-s3"><span class="ln">4</span><span class="lb"><span class="vr">card</span>.<span class="pp">innerHTML</span> = <span class="str">'<b>👤 山田太郎</b>...'</span>;</span></div>
<div class="cl" id="rp-s4"><span class="ln">5</span><span class="lb"><span class="vr">wrap</span>.<span class="fn">replaceChild</span>(<span class="vr">card</span>, <span class="vr">loader</span>);</span></div>
</div>
</div>
</div>
</div>
</div>
</section>
<div style="height:60px;"></div>
<script>
// ================================================================
// STEP DEFINITIONS
// ================================================================
const DEMOS = {};
// ── textContent ──
DEMOS['tc'] = {
steps: [
{
sid: 'tc-s0',
exStep: 'Step 1 — querySelector で要素を取得',
exText: '<code>#status</code> セレクタに一致する要素を探して変数 <span class="val">el</span> に格納した。まだ出力は変わらない。'
},
{
sid: 'tc-s1',
exStep: 'Step 2 — textContent に代入',
exText: '<span class="val">el.textContent</span> に文字列を代入した瞬間、ブラウザが自動でテキストを書き換える。HTMLタグは挿入できないが、その分安全。',
action: () => {
const el = document.getElementById('tc-out');
el.textContent = 'データ取得しました ✓';
el.style.color = 'var(--green)';
el.style.fontWeight = '600';
}
}
],
reset: () => {
const el = document.getElementById('tc-out');
el.textContent = '読み込み中...';
el.style.color = '';
el.style.fontWeight = '';
}
};
// ── innerHTML ──
DEMOS['ih'] = {
steps: [
{
sid: 'ih-s0',
exStep: 'Step 1 — querySelector で要素を取得',
exText: '書き換え先の要素を変数 <span class="val">card</span> に格納した。まだ出力は変わらない。'
},
{
sid: 'ih-s1',
exStep: 'Step 2 — innerHTML への代入が始まった',
exText: '文字列の中に <span class="val"><b></span> や <span class="val"><em></span> などのHTMLタグが含まれている。これは textContent では不可能。'
},
{
sid: 'ih-s2',
exStep: 'Step 3 — 代入完了、HTMLとしてレンダリング',
exText: 'ブラウザが文字列をHTMLとして解釈し描画した。<span class="val"><b></span> は太字、<span class="val"><em></span> はオレンジのイタリックとして表示されている。',
action: () => {
const el = document.getElementById('ih-out');
el.innerHTML = '<b style="color:#2563EB;">🎉 成功!</b> ユーザー <em style="color:var(--orange);">@taro</em> がログインしました。';
}
}
],
reset: () => {
document.getElementById('ih-out').innerHTML = 'コンテンツなし';
}
};
// ── tagName / id ── (no step engine — click shows result directly)
function probeClick(el) {
const out = document.getElementById('tg-out');
out.className = '';
out.innerHTML =
`<div style="font-family:'Fira Code',monospace;font-size:0.82rem;line-height:2.2;">` +
`<div><span style="color:var(--text-light)">tag</span> = <span style="background:#1C1917;color:#FDE68A;padding:2px 8px;border-radius:4px;">"${el.tagName}"</span> <span style="color:var(--text-light);font-size:0.75rem;">← tagName には "${el.tagName}" が入っていた</span></div>` +
`<div><span style="color:var(--text-light)">id </span>= <span style="background:#1C1917;color:#A3E635;padding:2px 8px;border-radius:4px;">"${el.id || '(なし)'}"</span> <span style="color:var(--text-light);font-size:0.75rem;">← id には "${el.id || '(なし)'}" が入っていた</span></div>` +
`</div>`;
const exEl = document.getElementById('tg-explain');
exEl.className = 'explanation done-all';
exEl.innerHTML =
`<div class="ex-step">✅ クリック検出</div>` +
`<div class="ex-text">` +
`<span class="val"><${el.tagName.toLowerCase()}></span> がクリックされた。` +
`<span class="val">e.target.tagName</span> には <span class="val">"${el.tagName}"</span> が、` +
`<span class="val">e.target.id</span> には <span class="val">"${el.id || '(なし)'}"</span> が入っている。` +
`タグの種類によって if 分岐したいときに使う。` +
`</div>`;
}
// ── querySelector ──
DEMOS['qs'] = {
steps: [
{
sid: 'qs-s0',
exStep: 'Step 1 — コメント確認',
exText: '<span class="val">.submit-btn</span> クラスを持つ要素を探す。グレーの「送信」「キャンセル」ボタンはこのクラスを持たないので対象外になる。'
},
{
sid: 'qs-s1',
exStep: 'Step 2 — querySelector でスキャン',
exText: 'ページを上から順にスキャンし、<span class="val">.submit-btn</span> に<b>最初にヒットした</b>「✉️ メイン送信」を変数 <span class="val">btn</span> に格納した。'
},
{
sid: 'qs-s2',
exStep: 'Step 3 — disabled = true',
exText: '<span class="val">btn.disabled</span> に <span class="val">true</span> を代入した。出力のボタンがグレーになりクリック不可になった。',
action: () => {
const b = document.getElementById('qs-main');
b.disabled = true;
b.style.background = '#9CA3AF';
}
},
{
sid: 'qs-s3',
exStep: 'Step 4 — textContent も変更',
exText: '<span class="val">btn.textContent</span> を書き換えた。他の2つのボタンは <span class="val">.submit-btn</span> を持たないので無傷のまま。',
action: () => {
document.getElementById('qs-main').textContent = '送信済み ✓';
}
}
],
reset: () => {
const b = document.getElementById('qs-main');
b.disabled = false; b.textContent = '✉️ メイン送信'; b.style.background = 'var(--orange)';
}
};
// ── querySelectorAll ──
DEMOS['qsa'] = {
steps: [
{
sid: 'qsa-s0',
exStep: 'Step 1 — querySelectorAll 開始',
exText: 'ページ全体をスキャンして条件に合う要素を<b>全件</b>集める。querySelector との違いは「1件だけ」か「全件」か。'
},
{
sid: 'qsa-s1',
exStep: 'Step 2 — 絞り込み完了',
exText: '<span class="val">.task-item</span> を持ち、かつ <span class="val">.done</span> を持たない要素を抽出。「コードレビュー」「デプロイ」「ドキュメント更新」の3件がヒットした。'
},
{
sid: 'qsa-s2',
exStep: 'Step 3 — forEach ループ開始',
exText: '取得した3件を1件ずつ処理するループが始まる。'
},
{
sid: 'qsa-s3',
exStep: 'Step 4 — 全件を透明化',
exText: '3件全てに <span class="val">opacity: 0.3</span> を一気に適用した。出力のリストを確認しよう。',
action: () => {
document.querySelectorAll('#qsa-list .task-item:not(.done)').forEach(i => { i.style.opacity = '0.3'; });
}
},
{
sid: 'qsa-s4',
exStep: 'Step 5 — 取り消し線を追加',
exText: '3件全てに <span class="val">text-decoration: line-through</span> も適用。<b>querySelectorAll</b> だから全件まとめて処理できた。',
action: () => {
document.querySelectorAll('#qsa-list .task-item:not(.done)').forEach(i => { i.style.textDecoration = 'line-through'; });
}
}
],
reset: () => {
document.querySelectorAll('#qsa-list .task-item').forEach(i => { i.style.opacity = ''; i.style.textDecoration = ''; });
}
};
// ── appendChild ──
DEMOS['ac'] = {
steps: [
{
sid: 'ac-s0',
exStep: 'Step 1 — 親の ul を取得',
exText: '追加先のリスト(<span class="val"><ul></span>)を変数 <span class="val">list</span> に格納した。出力のリストに変化はない。'
},
{
sid: 'ac-s1',
exStep: 'Step 2 — createElement で li を生成',
exText: '<span class="val">createElement("li")</span> で新しい要素を「作成」したが、まだDOMに追加されていないので出力には現れない。',
action: () => { window._acLi = document.createElement('li'); }
},
{
sid: 'ac-s2',
exStep: 'Step 3 — textContent をセット',
exText: '生成した <span class="val">li</span> にテキストを設定した。まだ非表示。DOMに追加されて初めて見えるようになる。',
action: () => { if (window._acLi) window._acLi.textContent = '新しいコメント 🗨️'; }
},
{
sid: 'ac-s3',
exStep: 'Step 4 — appendChild でDOMに追加',
exText: '<span class="val">appendChild</span> を呼んだ瞬間にDOMに追加された。出力のリスト末尾に現れた!<span class="val">createElement → appendChild</span> の2段階が重要。',
action: () => {
const list = document.getElementById('ac-list');
if (window._acLi) {
window._acLi.classList.add('new-item');
list.appendChild(window._acLi);
setTimeout(() => { if (window._acLi) window._acLi.classList.remove('new-item'); }, 800);
}
}
}
],
reset: () => {
window._acLi = null;
document.getElementById('ac-list').innerHTML = '<li>最初のコメント 👋</li>';
}
};
// ── insertBefore ──
DEMOS['ib'] = {
steps: [
{
sid: 'ib-s0',
exStep: 'Step 1 — 親の ul を取得',
exText: '挿入先の親要素(<span class="val"><ul></span>)を変数 <span class="val">list</span> に格納した。変化なし。'
},
{
sid: 'ib-s1',
exStep: 'Step 2 — 挿入位置の基準を取得',
exText: '「通知A」を変数 <span class="val">first</span> に格納した。これが挿入位置の<b>基準点</b>になる。出力の「通知A」がハイライトされた。',
action: () => {
const f = document.getElementById('ib-first');
if (f) { f.style.background = '#FEF3C7'; f.style.borderColor = 'var(--orange)'; }
}
},
{
sid: 'ib-s2',
exStep: 'Step 3 — 新しい li を生成',
exText: '<span class="val">createElement("li")</span> で緊急通知要素を生成。まだDOMには存在しない。',
action: () => { window._ibUrgent = document.createElement('li'); }
},
{
sid: 'ib-s3',
exStep: 'Step 4 — テキストをセット',
exText: '緊急通知のテキストを設定した。まだ非表示。',
action: () => { if (window._ibUrgent) window._ibUrgent.textContent = '🚨 緊急メンテナンス 22:00〜'; }
},
{
sid: 'ib-s4',
exStep: 'Step 5 — insertBefore で挿入',
exText: '<span class="val">insertBefore(urgent, first)</span> → <span class="val">first</span> の直前に <span class="val">urgent</span> が入った。<b>appendChild</b> なら末尾だが、<b>insertBefore</b> なら任意の位置に挿入できる。',
action: () => {
const list = document.getElementById('ib-list');
const first = document.getElementById('ib-first');
if (first) { first.style.background = ''; first.style.borderColor = ''; }
if (window._ibUrgent && first) {
window._ibUrgent.style.cssText = 'background:#FEF2F2;border-color:#FECACA;color:var(--red);font-weight:700;';
list.insertBefore(window._ibUrgent, first);
}
}
}
],
reset: () => {
window._ibUrgent = null;
document.getElementById('ib-list').innerHTML = `<li id="ib-first">📌 通常のお知らせ A</li><li>📌 通常のお知らせ B</li><li>📌 通常のお知らせ C</li>`;
}
};
// ── removeChild ──
DEMOS['rc'] = {
steps: [
{
sid: 'rc-s0',
exStep: 'Step 1 — 削除対象の要素を取得',
exText: '<span class="val">getElementById("rc-1")</span> で「メールに返信」の <span class="val"><li></span> を取得した。出力でハイライトされた。',
action: () => {
const el = document.getElementById('rc-1');
if (el) { el.style.background = '#FEF3C7'; el.style.borderColor = 'var(--orange)'; }
}
},
{
sid: 'rc-s1',
exStep: 'Step 2 — 親要素を取得',
exText: '<span class="val">el.parentElement</span> で親の <span class="val"><ul></span> を取得した。<b>removeChild は親から呼ぶ</b>必要があるため、先に親を取得しておく。'
},
{
sid: 'rc-s2',
exStep: 'Step 3 — removeChild で削除',
exText: '<span class="val">parent.removeChild(el)</span> を実行した。DOMから完全に取り除かれ、出力のリストから消えた。',
action: () => {
const el = document.getElementById('rc-1');
if (el && el.parentElement) el.parentElement.removeChild(el);
}
}
],
reset: () => {
document.getElementById('rc-list').innerHTML = `<li id="rc-1">📧 メールに返信</li><li id="rc-2">📅 会議資料を準備</li><li id="rc-3">📊 レポートを提出</li>`;
}
};
// ── replaceChild ──
DEMOS['rp'] = {
steps: [
{
sid: 'rp-s0',
exStep: 'Step 1 — 親コンテナを取得',
exText: '親の <span class="val"><div></span> を変数 <span class="val">wrap</span> に格納した。変化なし。'
},
{
sid: 'rp-s1',
exStep: 'Step 2 — ローダー要素を取得',
exText: '「読み込み中」要素を変数 <span class="val">loader</span> に格納した。これが<b>置き換えられる側</b>になる。',
action: () => {
const l = document.getElementById('rp-loader');
if (l) { l.style.color = 'var(--orange)'; l.style.fontWeight = '600'; }
}
},
{
sid: 'rp-s2',
exStep: 'Step 3 — 新しいカードを生成',
exText: '<span class="val">createElement("div")</span> で新しいカード要素を生成した。まだDOMには存在しない。',
action: () => {
window._rpCard = document.createElement('div');
}
},
{
sid: 'rp-s3',
exStep: 'Step 4 — カードに中身をセット',
exText: '<span class="val">innerHTML</span> でリッチなカードの中身を設定した。まだ出力には現れない。',
action: () => {
if (window._rpCard) {
window._rpCard.style.cssText = 'font-size:0.86rem;';
window._rpCard.innerHTML = '<b style="color:#2563EB;">👤 山田 太郎</b><br><span style="color:var(--text-light);font-size:0.82rem;">エンジニア · 東京</span>';
}
}
},
{
sid: 'rp-s4',
exStep: 'Step 5 — replaceChild で差し替え',
exText: '<span class="val">replaceChild(card, loader)</span> を実行。<span class="val">loader</span> が取り除かれ <span class="val">card</span> が同じ位置に挿入された。<b>removeChild + appendChild</b> を1行で行う操作。',
action: () => {
const wrap = document.getElementById('rp-wrap');
const loader = document.getElementById('rp-loader');
if (window._rpCard && loader) wrap.replaceChild(window._rpCard, loader);
}
}
],
reset: () => {
window._rpCard = null;
document.getElementById('rp-wrap').innerHTML = '<div id="rp-loader" style="color:var(--text-light);font-size:0.86rem;">⏳ データを読み込み中...</div>';
}
};
// ================================================================
// STATE ENGINE
// ================================================================
const STATE = {};
function initState(id) {
STATE[id] = { cur: -1, total: DEMOS[id].steps.length };
document.getElementById(id + '-tot').textContent = DEMOS[id].steps.length;
document.getElementById(id + '-cur').textContent = 0;
updateProgress(id);
}
function resetState(id) {
DEMOS[id].steps.forEach(s => {
const el = document.getElementById(s.sid);
if (el) el.classList.remove('cur', 'done');
});
STATE[id] = { cur: -1, total: DEMOS[id].steps.length };
document.getElementById(id + '-cur').textContent = 0;
updateProgress(id);
setExIdle(id);
const nb = document.getElementById(id + '-next');
if (nb) nb.disabled = (id === 'tg');
}
function reset(id) {
if (id === 'tg') {
// tagName has no step engine — just reset output and explanation
const out = document.getElementById('tg-out');
out.className = 'out-placeholder';
out.innerHTML = '← 上の要素をクリックすると tagName と id が表示される';
const exEl = document.getElementById('tg-explain');
exEl.className = 'explanation idle';
exEl.innerHTML = '<div class="ex-step">解説</div><div class="ex-text">上の要素をクリックすると <span class="val">e.target.tagName</span> と <span class="val">e.target.id</span> がどんな値を持つか確認できる。</div>';
return;
}
resetState(id);
DEMOS[id].reset();
}
function step(id) {
if (!STATE[id]) initState(id);
const s = STATE[id];
const d = DEMOS[id];
if (s.cur >= d.steps.length - 1) return;
// mark previous as done
if (s.cur >= 0) {
const prev = d.steps[s.cur];
const pel = document.getElementById(prev.sid);
if (pel) { pel.classList.remove('cur'); pel.classList.add('done'); }
}
s.cur++;
const current = d.steps[s.cur];
// highlight current line
const cel = document.getElementById(current.sid);
if (cel) cel.classList.add('cur');
// run action
if (current.action) current.action();
// update explanation
const exEl = document.getElementById(id + '-ex');
const exStep = typeof current.exStep === 'function' ? current.exStep() : current.exStep;
const exText = typeof current.exText === 'function' ? current.exText() : current.exText;
const isLast = s.cur === d.steps.length - 1;
exEl.className = 'explanation' + (isLast ? ' done-all' : '');
exEl.innerHTML = `<div class="ex-step">${isLast ? '✅ ' : ''}${exStep}</div><div class="ex-text">${exText}</div>`;
document.getElementById(id + '-cur').textContent = s.cur + 1;
updateProgress(id);
if (isLast) {
const nb = document.getElementById(id + '-next');
if (nb) nb.disabled = true;
setTimeout(() => { if (cel) { cel.classList.remove('cur'); cel.classList.add('done'); } }, 300);
}
}
function updateProgress(id) {
const s = STATE[id] || { cur: -1, total: DEMOS[id].steps.length };
const pct = s.total > 0 ? Math.round(((s.cur + 1) / s.total) * 100) : 0;
const fill = document.getElementById(id + '-prog');
if (fill) fill.style.width = pct + '%';
}
function setExIdle(id) {
if (id === 'tg') return;
const exEl = document.getElementById(id + '-ex');
if (!exEl) return;
exEl.className = 'explanation idle';
exEl.innerHTML = `<div class="ex-step">開始前</div><div class="ex-text">「次のステップへ」を押してコードを1行ずつ実行しよう</div>`;
}
// init totals
['tc','ih','qs','qsa','ac','ib','rc','rp'].forEach(id => {
document.getElementById(id + '-tot').textContent = DEMOS[id].steps.length;
});
</script>
</body>
</html>
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme