導入
オセロ式ローディングアニメーション
オセロの反転ルールを活用した、LOADINGテキストが順次表示されるローディングアニメーションです。
HTML
<div class="container">
<div class="board" id="board"></div>
</div> CSS
body {
margin: 0;
padding: 0;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Arial', sans-serif;
}
.container {
text-align: center;
}
.board {
display: inline-grid;
grid-template-columns: repeat(8, 50px);
grid-template-rows: repeat(8, 50px);
gap: 2px;
background: #0f3460;
padding: 10px;
border-radius: 8px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5),
0 0 60px rgba(22, 160, 133, 0.15);
}
.cell {
width: 50px;
height: 50px;
background: #16a085;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
}
.stone {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
font-weight: bold;
transition: background-color 0.4s ease, transform 0.4s ease;
}
.stone.black {
background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%);
color: white;
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.3),
0 4px 8px rgba(0, 0, 0, 0.2),
inset 0 -2px 4px rgba(0, 0, 0, 0.3),
inset 0 2px 4px rgba(255, 255, 255, 0.1);
}
.stone.white {
background: linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%);
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.2),
0 4px 8px rgba(0, 0, 0, 0.15),
inset 0 -2px 4px rgba(0, 0, 0, 0.1),
inset 0 2px 4px rgba(255, 255, 255, 0.8);
}
.stone .letter {
opacity: 0;
transition: opacity 0.2s ease;
}
.stone.show-letter .letter {
opacity: 1;
}
@keyframes flip {
0% { transform: rotateY(0deg); }
50% { transform: rotateY(90deg); }
100% { transform: rotateY(0deg); }
}
.stone.flip {
animation: flip 0.4s ease;
}
@keyframes placeStone {
0% {
transform: scale(0);
opacity: 0;
}
50% { transform: scale(1.2); }
100% {
transform: scale(1);
opacity: 1;
}
}
.stone.placing {
animation: placeStone 0.5s ease;
}
@keyframes stonePulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(0.9); }
}
.stone.stone-pulse {
animation: stonePulse 0.6s ease;
} JavaScript
const board = document.getElementById('board');
const BOARD_SIZE = 8;
const HORIZONTAL_POSITIONS = [
{row: 0, col: 0}, // L
{row: 0, col: 1}, // O
{row: 0, col: 2}, // A
{row: 0, col: 3}, // D
{row: 0, col: 4}, // I
{row: 0, col: 5}, // N
{row: 0, col: 6}, // G
{row: 0, col: 7} // ...
];
const letters = ['L', 'O', 'A', 'D', 'I', 'N', 'G', '...'];
let cells = [];
function createBoard() {
board.innerHTML = '';
cells = [];
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
const cell = document.createElement('div');
cell.className = 'cell';
const stone = document.createElement('div');
stone.className = 'stone';
const letter = document.createElement('span');
letter.className = 'letter';
stone.appendChild(letter);
if (row === 0 && col === 0) {
stone.style.display = 'none';
} else if (row === 0 && col >= 1 && col <= 6) {
stone.classList.add('white');
} else {
stone.classList.add('black');
}
cell.appendChild(stone);
board.appendChild(cell);
cells.push({ cell, stone, letter });
}
}
}
async function animate() {
createBoard();
await sleep(800);
// 黒石を置く
const placeIndex = 0;
const placeStone = cells[placeIndex].stone;
const placeLetter = cells[placeIndex].letter;
placeStone.style.display = 'flex';
placeStone.classList.add('black', 'placing', 'show-letter');
placeLetter.textContent = letters[0];
await sleep(500);
// 横一列を反転
for (let i = 1; i <= 6; i++) {
const hPos = HORIZONTAL_POSITIONS[i];
const hIndex = hPos.row * BOARD_SIZE + hPos.col;
const hStone = cells[hIndex].stone;
const hLetter = cells[hIndex].letter;
hLetter.textContent = letters[i];
hStone.classList.add('flip');
await sleep(200);
hStone.classList.remove('white');
hStone.classList.add('black', 'show-letter');
await sleep(350);
}
await sleep(300);
// ...を段階的に表示
const lastIndex = 7;
const lastStone = cells[lastIndex].stone;
const lastLetter = cells[lastIndex].letter;
lastLetter.innerHTML = '<span class="dot" style="opacity: 0;">.</span><span class="dot" style="opacity: 0;">.</span><span class="dot" style="opacity: 0;">.</span>';
lastStone.classList.add('show-letter');
const dots = lastLetter.querySelectorAll('.dot');
dots[0].style.opacity = '1';
await sleep(200);
dots[1].style.opacity = '1';
await sleep(200);
dots[2].style.opacity = '1';
await sleep(1000);
// パルス効果
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
const idx = row * BOARD_SIZE + col;
cells[idx].stone.classList.add('stone-pulse');
}
}
await sleep(600);
for (let row = 1; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
const idx = row * BOARD_SIZE + col;
cells[idx].stone.classList.remove('stone-pulse');
}
}
await sleep(1000);
animate();
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
createBoard();
animate(); AIへのプロンプト例
以下のようなプロンプトをAIに送信します:
オセロゲームのルールを使ったローディングアニメーションを作成してください。
### 盤面の設定
- 8×8のオセロ盤を作成
- 各マスには緑色の背景
- 盤面全体は濃い青の背景で囲む
### 石のデザイン
- 白石と黒石を円形で表現
- グラデーションを使い立体的な質感を演出
- 各石に影を付けて浮き上がったような効果
### アニメーションの流れ
1. 最初に黒石を左上に配置し「L」の文字を表示
2. 横一列の白石を順番に反転させ、「LOADING」の文字を1文字ずつ表示
3. 最後の石には「...」を段階的に表示
4. 完成後、盤面全体が脈打つようなパルスアニメーション
5. ループして繰り返す
### 反転のアニメーション
- Y軸を中心に回転して裏返る動き
- 回転中は少し透明にして奥行き感を演出
- 滑らかな動きで自然な反転を表現
### タイミング調整
- 石の配置から反転まで適度な間隔
- 文字が読みやすいテンポで順次表示
- 完了後のパルスは短めに設定
このパーツの特徴
- ゲームルールの応用 オセロの反転ルールを忠実に再現し、文字表示に活用
- 段階的な演出 石の配置→反転→文字表示の流れで視覚的なストーリーを構築
- 完了アニメーション LOADING表示完了後、盤面全体が脈打つような効果で次の段階を示唆
- 立体的な表現 グラデーションと複数の影を使い、石の質感をリアルに表現
- 視認性の高さ 横一列配置で文字が読みやすく、待機時間のストレスを軽減
コードのポイント
盤面と石の3D表現:
.board {
display: inline-grid;
grid-template-columns: repeat(8, 50px);
grid-template-rows: repeat(8, 50px);
gap: 2px;
background: #0f3460;
padding: 10px;
border-radius: 8px;
}
.stone.black {
background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%);
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.3),
inset 0 -2px 4px rgba(0, 0, 0, 0.3),
inset 0 2px 4px rgba(255, 255, 255, 0.1);
}
.stone.white {
background: linear-gradient(135deg, #ffffff 0%, #e0e0e0 100%);
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.2),
inset 0 -2px 4px rgba(0, 0, 0, 0.1),
inset 0 2px 4px rgba(255, 255, 255, 0.8);
} - グリッドレイアウトで8×8の盤面を構築
- グラデーションで石の光沢感を表現
- 内側と外側の影を組み合わせて立体感を演出
オセロ盤の質感は、複数のbox-shadowを重ねることで実現しています。insetによる内側の影が凹凸を作り、外側の影が浮き上がりを表現します。白石と黒石で異なるグラデーション方向と影の強さを設定し、それぞれの素材感を際立たせています。
反転アニメーションの仕組み:
@keyframes flip {
0% { transform: rotateY(0deg); }
50% { transform: rotateY(90deg); }
100% { transform: rotateY(0deg); }
}
.stone.flip {
animation: flip 0.4s ease;
} // 横一列を反転
for (let i = 1; i <= 6; i++) {
const hPos = HORIZONTAL_POSITIONS[i];
const hIndex = hPos.row * BOARD_SIZE + hPos.col;
const hStone = cells[hIndex].stone;
const hLetter = cells[hIndex].letter;
hLetter.textContent = letters[i];
hStone.classList.add('flip');
await sleep(200);
hStone.classList.remove('white');
hStone.classList.add('black', 'show-letter');
await sleep(350);
} - Y軸回転で石が裏返る動きを実現
- 反転途中(90度)で色を切り替え
async/awaitで順序制御し、タイミングを調整
オセロの反転を視覚的に表現するため、CSS AnimationのrotateY()を使用しています。90度の時点で石の表裏が入れ替わるため、このタイミングでクラスを変更することで白から黒への自然な切り替えを実現しています。JavaScriptのasync/await構文により、各反転が完了するまで次の処理を待機し、リズミカルなアニメーションを作り出しています。
アニメーションシーケンスの制御:
async function animate() {
createBoard();
await sleep(800);
// 1. 黒石を配置(L)
placeStone.classList.add('black', 'placing', 'show-letter');
await sleep(500);
// 2. 白石を順番に反転(O-G)
for (let i = 1; i <= 6; i++) {
// 反転処理
await sleep(350);
}
// 3. ...を段階表示
dots[0].style.opacity = '1';
await sleep(200);
// 4. パルスアニメーション
cells.forEach(c => c.stone.classList.add('stone-pulse'));
await sleep(600);
// 5. ループ
animate();
} - 各ステップを
sleep()で区切り、見やすいテンポを作成 ...は1文字ずつ透明度を変えて段階表示- パルスで完了を視覚的に伝える
アニメーション全体は複数のステップに分かれており、async/awaitと自作のsleep()関数で時間制御を行っています。石の配置、反転、文字表示、パルスという一連の流れを、適切な間隔で実行することで、視聴者が内容を追いやすいリズムを生み出しています。完了時のパルスアニメーションは、「処理が終わった」という明確なフィードバックをユーザーに提供する重要な役割を担っています。
まとめ
オセロ式ローディングの活用ポイント
- ルールの明確さ オセロという誰もが知るゲームを使うことで、直感的に理解しやすい演出に
- タイミング設計 各ステップの待機時間を調整し、見やすくストレスのないテンポを実現
- 完了の合図 パルスアニメーションで処理完了を明示的に伝え、ユーザーの不安を軽減
- 応用の幅 オセロ以外のゲームや身近なルールも、同様にローディング演出に転用可能
ゲームの仕組みをUI演出に活用することで、ユニークで記憶に残るローディング体験を作り出せます。