関数スコープ・クロージャの動作を図解で解説(アニメーション付き)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>関数スコープとクロージャの図解</title>
<script src="https://unpkg.com/framer-motion@10.16.4/dist/framer-motion.umd.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center p-6">
<h1 class="text-2xl font-bold mb-4">🧩 関数スコープとクロージャの動作図解</h1>
<div id="animation" class="relative w-[600px] h-[400px] bg-white rounded-2xl shadow-lg overflow-hidden">
<div id="outerScope" class="absolute top-4 left-4 bg-blue-100 border border-blue-400 rounded-xl p-4 w-[260px]">
<p class="font-bold text-blue-700">外側スコープ (Global)</p>
<p>let count = 0;</p>
</div>
<div id="functionBox" class="absolute top-[160px] left-[40px] bg-green-100 border border-green-400 rounded-xl p-4 w-[250px]">
<p class="font-bold text-green-700">makeCounter()</p>
<p>return function() { count++; }</p>
</div>
<div id="innerScope" class="absolute top-[120px] right-[40px] bg-yellow-100 border border-yellow-400 rounded-xl p-4 w-[250px] opacity-0">
<p class="font-bold text-yellow-700">クロージャ(記憶された環境)</p>
<p>count が保存される</p>
</div>
</div>
<button id="play" class="mt-6 bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">▶ 再生</button>
<script>
const outer = document.getElementById('outerScope');
const func = document.getElementById('functionBox');
const inner = document.getElementById('innerScope');
const play = document.getElementById('play');
let step = 0;
function animateStep() {
step++;
if (step === 1) {
func.style.transition = 'all 1s';
func.style.transform = 'translateX(250px)';
} else if (step === 2) {
inner.style.transition = 'opacity 1s';
inner.style.opacity = '1';
} else if (step === 3) {
outer.style.backgroundColor = '#bfdbfe';
inner.style.backgroundColor = '#fde68a';
func.style.backgroundColor = '#bbf7d0';
} else {
step = 0;
func.style.transform = 'translateX(0)';
inner.style.opacity = '0';
outer.style.backgroundColor = '#dbeafe';
inner.style.backgroundColor = '#fef9c3';
func.style.backgroundColor = '#dcfce7';
}
}
play.addEventListener('click', animateStep);
</script>
<div class="mt-8 text-left max-w-lg bg-white p-4 rounded-xl shadow">
<h2 class="text-lg font-bold mb-2">💡 解説</h2>
<p class="mb-2">関数 <code>makeCounter</code> は、内部で <code>count</code> という変数を定義します。この変数は <strong>外部から直接は見えません</strong>。</p>
<p class="mb-2">しかし <code>return function()</code> の部分で、<strong>そのスコープ内の変数を覚えたまま</strong> 新しい関数が作られます。これが「クロージャ」です。</p>
<p>関数が呼び出されるたびに <code>count</code> の値が更新され、外部でもその状態を保ち続けます。</p>
</div>
</body>
</html>
HTMLこちらが、関数スコープとクロージャの動作を図解アニメーション付きで理解できるHTMLデモです。
「▶ 再生」ボタンを押すと、makeCounter() 関数 → クロージャが生成 → スコープが保持される様子が順番に視覚化されます。
カウントアップ機能を追加
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>関数スコープとクロージャの図解+カウンター実演</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center p-6">
<h1 class="text-2xl font-bold mb-4">🧩 関数スコープとクロージャの動作図解+カウンター実演</h1>
<div id="animation" class="relative w-[600px] h-[400px] bg-white rounded-2xl shadow-lg overflow-hidden">
<div id="outerScope" class="absolute top-4 left-4 bg-blue-100 border border-blue-400 rounded-xl p-4 w-[260px]">
<p class="font-bold text-blue-700">外側スコープ (Global)</p>
<p>let count = 0;</p>
</div>
<div id="functionBox" class="absolute top-[160px] left-[40px] bg-green-100 border border-green-400 rounded-xl p-4 w-[250px]">
<p class="font-bold text-green-700">makeCounter()</p>
<p>return function() { count++; }</p>
</div>
<div id="innerScope" class="absolute top-[120px] right-[40px] bg-yellow-100 border border-yellow-400 rounded-xl p-4 w-[250px] opacity-0">
<p class="font-bold text-yellow-700">クロージャ(記憶された環境)</p>
<p>count が保存される</p>
</div>
</div>
<button id="play" class="mt-6 bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">▶ 再生</button>
<script>
const outer = document.getElementById('outerScope');
const func = document.getElementById('functionBox');
const inner = document.getElementById('innerScope');
const play = document.getElementById('play');
let step = 0;
function animateStep() {
step++;
if (step === 1) {
func.style.transition = 'all 1s';
func.style.transform = 'translateX(250px)';
} else if (step === 2) {
inner.style.transition = 'opacity 1s';
inner.style.opacity = '1';
} else if (step === 3) {
outer.style.backgroundColor = '#bfdbfe';
inner.style.backgroundColor = '#fde68a';
func.style.backgroundColor = '#bbf7d0';
} else {
step = 0;
func.style.transform = 'translateX(0)';
inner.style.opacity = '0';
outer.style.backgroundColor = '#dbeafe';
inner.style.backgroundColor = '#fef9c3';
func.style.backgroundColor = '#dcfce7';
}
}
play.addEventListener('click', animateStep);
</script>
<div class="mt-10 text-left max-w-lg bg-white p-4 rounded-xl shadow">
<h2 class="text-lg font-bold mb-2">💡 クロージャの実際の動作を体験</h2>
<p class="mb-3">下のボタンをクリックして、<code>makeCounter()</code> が内部の変数を保持している様子を体験してみましょう。</p>
<div class="flex items-center gap-4 mt-3">
<button id="countBtn" class="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600">+1 カウント</button>
<span id="countValue" class="text-xl font-bold text-green-700">0</span>
</div>
</div>
<script>
function makeCounter() {
let count = 0; // 外部からは見えない変数
return function() {
count++;
return count;
};
}
const counter = makeCounter();
const countBtn = document.getElementById('countBtn');
const countValue = document.getElementById('countValue');
countBtn.addEventListener('click', () => {
const newCount = counter();
countValue.textContent = newCount;
});
</script>
<div class="mt-8 text-left max-w-lg bg-white p-4 rounded-xl shadow">
<h2 class="text-lg font-bold mb-2">🧠 解説</h2>
<p class="mb-2"><code>makeCounter</code> は内部に <code>count</code> 変数を持ち、それを更新する関数を返します。</p>
<p class="mb-2">この関数が外で使われても、<code>count</code> の値は保持され続けます(これがクロージャ)。</p>
<p>クリックしても変数がリセットされないのは、「関数の中の変数が記憶されている」ためです。</p>
</div>
</body>
</html>
HTMLボタンをクリックすると、クロージャによって count が保持され、実際に数字が1ずつ増えていく動作が確認できます。
