画面左の領域に配列のイメージを表示し、そこにボックスを追加・削除することでスタックやキューのイメージが掴める教材をJavaScriptで作成します。 追加されるボックスはアニメーション動作をCSSで設定できるようにするため、個々のHTML要素として扱い、DOM操作により追加、削除を行います。 画面右にはボックスの追加や削除を行う操作ボタンと、そのボックス内に描画するテキストを指定するテキストボックスを配置します。ボックスを追加するたびにテキストボックスの内容は一度リセットしてください。 追加削除のための操作は、エンキュー、デキュー、プッシュ、ポップの四つとします。全ての操作において、ボックスは追加削除される方向に移動しながらフェードイン・フェードアウトされるようなアニメーションをしながら追加削除されるようにしてください。 また、操作はリセットボタンを押すまですべて記録され、それをシナリオデータとして一括エクスポートできるようにしてください。 エクスポート時の形式はJSONとし、画面最下の読み取り専用テキストボックスに表示してください。
以下のサンプルは HTML / CSS / JavaScript を分離 した最小構成です。すべて 2 スペースインデントで統一していますので、そのままコピー&ペーストして動かしてください。要件に合わせたカスタマイズもしやすいようにコメントを付けています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>スタック & キュー可視化教材</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="wrapper">
<!-- 配列(可視化領域) -->
<section id="array-container" aria-label="可視化エリア"></section>
<!-- 操作パネル -->
<aside class="control-panel">
<label class="input-label">
テキスト:
<input type="text" id="value-input" placeholder="ボックス内の文字列" />
</label>
<div class="btn-group">
<button id="enqueue-btn">エンキュー</button>
<button id="dequeue-btn">デキュー</button>
<button id="push-btn">プッシュ</button>
<button id="pop-btn">ポップ</button>
</div>
<button id="reset-btn" class="reset">リセット</button>
</aside>
</div>
<!-- シナリオの JSON 出力 -->
<textarea id="export-area" readonly
aria-label="操作ログ(JSON)"
placeholder="エクスポートを行うと JSON が表示されます"></textarea>
<script src="script.js"></script>
</body>
</html>
/* レイアウト -------------------------------------------------------------- */
* { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
}
.wrapper {
flex: 1;
display: flex;
}
/* 左: 配列表示領域 */
#array-container {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 1rem;
gap: 0.5rem;
overflow-x: auto;
border-right: 2px solid #ddd;
background: #fafafa;
}
/* 右: 操作パネル */
.control-panel {
width: 220px;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.btn-group { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
button {
padding: 0.5rem;
font-size: 0.9rem;
cursor: pointer;
}
.reset { background: #f44336; color: #fff; }
.input-label { display: flex; flex-direction: column; gap: 0.25rem; }
input[type=text] { padding: 0.4rem; }
/* JSON 出力欄 ----------------------------------------------------------- */
#export-area {
height: 120px;
border: none;
border-top: 2px solid #ddd;
padding: 0.75rem;
font-family: "Cascadia Code", monospace;
resize: none;
}
/* ボックス -------------------------------------------------------------- */
.box {
min-width: 70px;
padding: 0.5rem;
text-align: center;
border: 2px solid #3f51b5;
border-radius: 4px;
background: #e8eaf6;
font-weight: 600;
position: relative;
opacity: 0; /* Animation でフェードイン */
}
/* 追加アニメーション */
@keyframes slideInRight {
from { transform: translateX(40px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideInLeft {
from { transform: translateX(-40px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.box.add-right { animation: slideInRight 0.3s forwards; }
.box.add-left { animation: slideInLeft 0.3s forwards; }
/* 削除アニメーション */
@keyframes slideOutRight {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(40px); opacity: 0; }
}
@keyframes slideOutLeft {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(-40px); opacity: 0; }
}
.box.remove-right { animation: slideOutRight 0.3s forwards; }
.box.remove-left { animation: slideOutLeft 0.3s forwards; }
/* =========================================================================
* スタック/キュー可視化 – script.js
* ========================================================================= */
const arrayContainer = document.getElementById('array-container');
const valueInput = document.getElementById('value-input');
const exportArea = document.getElementById('export-area');
const log = []; // 操作ログ(JSON エクスポート用)
/* ユーティリティ -------------------------------------------------------- */
// ボックス生成
function createBox(text) {
const div = document.createElement('div');
div.className = 'box';
div.textContent = text;
return div;
}
// ログ記録
function addLog(action, value = null) {
log.push({ time: Date.now(), action, value });
}
/* 追加系 ------------------------------------------------------------ */
// enqueue: 末尾に追加(キュー)
document.getElementById('enqueue-btn').addEventListener('click', () => {
const val = valueInput.value.trim();
if (!val) return;
const box = createBox(val);
box.classList.add('add-right');
arrayContainer.appendChild(box);
addLog('enqueue', val);
valueInput.value = '';
});
// push: 末尾に追加(スタック)
document.getElementById('push-btn').addEventListener('click', () => {
const val = valueInput.value.trim();
if (!val) return;
const box = createBox(val);
box.classList.add('add-right');
arrayContainer.appendChild(box);
addLog('push', val);
valueInput.value = '';
});
/* 削除系 ------------------------------------------------------------ */
// dequeue: 先頭から削除(キュー)
document.getElementById('dequeue-btn').addEventListener('click', () => {
const first = arrayContainer.firstElementChild;
if (!first) return;
first.classList.add('remove-left');
first.addEventListener('animationend', () => first.remove(), { once: true });
addLog('dequeue');
});
// pop: 末尾から削除(スタック)
document.getElementById('pop-btn').addEventListener('click', () => {
const last = arrayContainer.lastElementChild;
if (!last) return;
last.classList.add('remove-right');
last.addEventListener('animationend', () => last.remove(), { once: true });
addLog('pop');
});
/* リセット ----------------------------------------------------------- */
document.getElementById('reset-btn').addEventListener('click', () => {
arrayContainer.innerHTML = '';
log.length = 0;
valueInput.value = '';
exportArea.value = '';
});
/* エクスポート(Ctrl+E) --------------------------------------------- */
document.addEventListener('keydown', (e) => {
if (e.key.toLowerCase() === 'e' && (e.ctrlKey || e.metaKey)) {
exportArea.value = JSON.stringify(log, null, 2);
}
});
操作 | 動き |
---|---|
エンキュー | 右端に追加 → キュー用 |
デキュー | 左端から削除 |
プッシュ | 右端に追加 → スタック用 |
ポップ | 右端から削除 |
リセット | 画面と操作ログを完全初期化 |
JSON エクスポート | Ctrl + E で操作ログを下部テキストエリアに出力 |
@keyframes
を書き換えるだけでスライド方向や時間、フェード量などを自由に調整できます。time
, action
, value
)は必要に応じて追加フィールドを持たせることも簡単です。keydown
ハンドラを修正してください。「まずは動く物」を意識したシンプル実装なので、カスタマイズしながら教材にフィットさせてみてください。