From 50c29c1743ee3611a8334dafca1b85728d68c258 Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:48:37 +0900 Subject: [PATCH 1/5] perf : optimize board rendering with DOM diffing --- src/iframe/life-game.js | 79 ++++++++++++++++++++++++----------------- src/routes/+page.svelte | 4 +-- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index 8ce2604..8493a98 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -30,41 +30,56 @@ function isNextAlive(around, self) { //Boardの初期化 let board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => false)); const table = document.getElementById("game-board"); +//盤面をBoardに従って変更する関数(Boardを変更したら必ず実行する) function renderBoard() { - //盤面をBoardに従って変更する関数(Boardを変更したら必ず実行する) - table.innerHTML = ""; - for (let i = 0; i < boardSize; i++) { - const tr = document.createElement("tr"); - tr.style.padding = "0"; - for (let j = 0; j < boardSize; j++) { - const td = document.createElement("td"); - td.style.padding = "0"; - const button = document.createElement("button"); - button.style.backgroundColor = board[i][j] ? "black" : "white"; //Boardの対応する値によって色を変更 - button.style.border = "0.5px solid black"; - button.style.width = `${cellSize}px`; - button.style.height = `${cellSize}px`; - button.style.padding = "0"; //cellSizeが小さいとき、セルが横長になることを防ぐ - button.style.display = "block"; //cellSizeが小さいとき、行間が空きすぎるのを防ぐ - button.onmousedown = (e) => { - e.preventDefault(); - if (timer === "stop") { - isDragging = true; - board[i][j] = !board[i][j]; - dragMode = board[i][j]; - button.style.backgroundColor = board[i][j] ? "black" : "white"; - } - }; - button.onmouseenter = () => { - if (isDragging && timer === "stop" && board[i][j] !== dragMode) { - board[i][j] = dragMode; - button.style.backgroundColor = board[i][j] ? "black" : "white"; + // 初回の処理 + if (table.children.length === 0) { + table.innerHTML = ""; + for (let i = 0; i < boardSize; i++) { + const tr = document.createElement("tr"); + tr.style.padding = "0"; + for (let j = 0; j < boardSize; j++) { + const td = document.createElement("td"); + td.style.padding = "0"; + const button = document.createElement("button"); + button.style.backgroundColor = board[i][j] ? "black" : "white"; //Boardの対応する値によって色を変更 + button.style.border = "0.5px solid black"; + button.style.width = `${cellSize}px`; + button.style.height = `${cellSize}px`; + button.style.padding = "0"; //cellSizeが小さいとき、セルが横長になることを防ぐ + button.style.display = "block"; //cellSizeが小さいとき、行間が空きすぎるのを防ぐ + button.onmousedown = (e) => { + e.preventDefault(); + if (timer === "stop") { + isDragging = true; + board[i][j] = !board[i][j]; + dragMode = board[i][j]; + button.style.backgroundColor = board[i][j] ? "black" : "white"; + } + }; + button.onmouseenter = () => { + if (isDragging && timer === "stop" && board[i][j] !== dragMode) { + board[i][j] = dragMode; + button.style.backgroundColor = board[i][j] ? "black" : "white"; + } + }; + td.appendChild(button); + tr.appendChild(td); + } + table.appendChild(tr); + } + } else { + // 2回目以降の処理 + for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < boardSize; j++) { + const button = table.children[i].children[j].children[0]; + const shouldBeBlack = board[i][j]; + const isBlack = button.style.backgroundColor === "black"; + if (shouldBeBlack !== isBlack) { + button.style.backgroundColor = shouldBeBlack ? "black" : "white"; } - }; - td.appendChild(button); - tr.appendChild(td); + } } - table.appendChild(tr); } } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index f2ec61f..a712ccb 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -148,7 +148,7 @@
@@ -329,7 +329,7 @@ srcdoc={previewDoc} title="Preview" sandbox="allow-scripts" - class="w-[80%] h-[90%] rounded-lg mx-auto my-5 shadow-lg" + class="w-[80%] h-[90%] rounded-lg mx-auto my-5 bg-white shadow-lg" onload={() => { setTimeout(() => { sendEvent("state_update"); From 4e9226384d2129c8d2d114fa0402ffda745556d7 Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:15:52 +0900 Subject: [PATCH 2/5] fix: apply cellSize calculation when code is reloaded --- src/iframe/life-game.js | 22 +++++++++++----------- src/routes/+page.svelte | 4 +--- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index 8493a98..bbd8cd8 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -11,8 +11,8 @@ const DEFAULT_BOARD_SIZE = 20; const DEFAULT_CELL_SIZE = 30; //変数設定 -let boardSize = 20; -let cellSize = 30; +let boardSize = 20; //盤面の大きさ(20x20) +let cellScale = 1.0; //セルの大きさの倍率 // around: 周囲の生きたセル数 self: 自身が生きているかどうか function isNextAlive(around, self) { @@ -32,6 +32,7 @@ let board = Array.from({ length: boardSize }, () => Array.from({ length: boardSi const table = document.getElementById("game-board"); //盤面をBoardに従って変更する関数(Boardを変更したら必ず実行する) function renderBoard() { + const cellSize = Math.floor(cellScale * DEFAULT_CELL_SIZE * (DEFAULT_BOARD_SIZE / boardSize)); // 初回の処理 if (table.children.length === 0) { table.innerHTML = ""; @@ -73,11 +74,19 @@ function renderBoard() { for (let i = 0; i < boardSize; i++) { for (let j = 0; j < boardSize; j++) { const button = table.children[i].children[j].children[0]; + const shouldBeBlack = board[i][j]; const isBlack = button.style.backgroundColor === "black"; if (shouldBeBlack !== isBlack) { button.style.backgroundColor = shouldBeBlack ? "black" : "white"; } + + const currentCellsize = button.style.width; + const expectedCellsize = `${cellSize}px`; + if (currentCellsize !== expectedCellsize) { + button.style.width = expectedCellsize; + button.style.height = expectedCellsize; + } } } } @@ -162,15 +171,6 @@ on.pause = () => { resetTimer(); }; -on.board_resize = (newSize) => { - boardSize = newSize; - cellSize = Math.floor(DEFAULT_CELL_SIZE * (DEFAULT_BOARD_SIZE / newSize)); - board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => false)); - renderBoard(); - generationChange(0); - resetTimer(); -}; - on.board_reset = () => { //すべて白にBoardを変更 board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => false)); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a712ccb..07217a7 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -42,9 +42,7 @@ | "place_template" | "save_board" | "apply_board" - | "request_sync" - // unused events - | "board_resize"; + | "request_sync"; type IncomingEvent = "generation_change" | "sync" | "save_board"; From 0e3dd99a25241576cecef0070d0bbea472f97589 Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:57:03 +0900 Subject: [PATCH 3/5] add rerender function --- src/iframe/life-game.js | 118 +++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index bbd8cd8..b9917f2 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -14,6 +14,8 @@ const DEFAULT_CELL_SIZE = 30; let boardSize = 20; //盤面の大きさ(20x20) let cellScale = 1.0; //セルの大きさの倍率 +let cellSize = Math.floor(cellScale * DEFAULT_CELL_SIZE * (DEFAULT_BOARD_SIZE / boardSize)); //セルの大きさ(px) + // around: 周囲の生きたセル数 self: 自身が生きているかどうか function isNextAlive(around, self) { // 自身が生きている & 周囲が 2 か 3 で生存 @@ -30,63 +32,65 @@ function isNextAlive(around, self) { //Boardの初期化 let board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => false)); const table = document.getElementById("game-board"); -//盤面をBoardに従って変更する関数(Boardを変更したら必ず実行する) + +//盤面をBoardに従って変更する関数達(Boardを変更したら実行する) function renderBoard() { - const cellSize = Math.floor(cellScale * DEFAULT_CELL_SIZE * (DEFAULT_BOARD_SIZE / boardSize)); - // 初回の処理 - if (table.children.length === 0) { - table.innerHTML = ""; - for (let i = 0; i < boardSize; i++) { - const tr = document.createElement("tr"); - tr.style.padding = "0"; - for (let j = 0; j < boardSize; j++) { - const td = document.createElement("td"); - td.style.padding = "0"; - const button = document.createElement("button"); - button.style.backgroundColor = board[i][j] ? "black" : "white"; //Boardの対応する値によって色を変更 - button.style.border = "0.5px solid black"; - button.style.width = `${cellSize}px`; - button.style.height = `${cellSize}px`; - button.style.padding = "0"; //cellSizeが小さいとき、セルが横長になることを防ぐ - button.style.display = "block"; //cellSizeが小さいとき、行間が空きすぎるのを防ぐ - button.onmousedown = (e) => { - e.preventDefault(); - if (timer === "stop") { - isDragging = true; - board[i][j] = !board[i][j]; - dragMode = board[i][j]; - button.style.backgroundColor = board[i][j] ? "black" : "white"; - } - }; - button.onmouseenter = () => { - if (isDragging && timer === "stop" && board[i][j] !== dragMode) { - board[i][j] = dragMode; - button.style.backgroundColor = board[i][j] ? "black" : "white"; - } - }; - td.appendChild(button); - tr.appendChild(td); - } - table.appendChild(tr); - } - } else { - // 2回目以降の処理 - for (let i = 0; i < boardSize; i++) { - for (let j = 0; j < boardSize; j++) { - const button = table.children[i].children[j].children[0]; - - const shouldBeBlack = board[i][j]; - const isBlack = button.style.backgroundColor === "black"; - if (shouldBeBlack !== isBlack) { - button.style.backgroundColor = shouldBeBlack ? "black" : "white"; + // 初回の盤面生成 + table.innerHTML = ""; + for (let i = 0; i < boardSize; i++) { + const tr = document.createElement("tr"); + tr.style.padding = "0"; + for (let j = 0; j < boardSize; j++) { + const td = document.createElement("td"); + td.style.padding = "0"; + const button = document.createElement("button"); + button.style.backgroundColor = board[i][j] ? "black" : "white"; //Boardの対応する値によって色を変更 + button.style.border = "0.5px solid black"; + button.style.width = `${cellSize}px`; + button.style.height = `${cellSize}px`; + button.style.padding = "0"; //cellSizeが小さいとき、セルが横長になることを防ぐ + button.style.display = "block"; //cellSizeが小さいとき、行間が空きすぎるのを防ぐ + button.onmousedown = (e) => { + e.preventDefault(); + if (timer === "stop") { + isDragging = true; + board[i][j] = !board[i][j]; + dragMode = board[i][j]; + button.style.backgroundColor = board[i][j] ? "black" : "white"; } - - const currentCellsize = button.style.width; - const expectedCellsize = `${cellSize}px`; - if (currentCellsize !== expectedCellsize) { - button.style.width = expectedCellsize; - button.style.height = expectedCellsize; + }; + button.onmouseenter = () => { + if (isDragging && timer === "stop" && board[i][j] !== dragMode) { + board[i][j] = dragMode; + button.style.backgroundColor = board[i][j] ? "black" : "white"; } + }; + td.appendChild(button); + tr.appendChild(td); + } + table.appendChild(tr); + } +} + +function rerender() { + // 2回目以降の盤面生成 + for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < boardSize; j++) { + const button = table.children[i].children[j].children[0]; + + // 色の更新 + const currentCellColor = button.style.backgroundColor; + const expectedCellColor = getStyle(board[i][j]); + if (currentCellColor !== expectedCellColor) { + button.style.backgroundColor = expectedCellColor; + } + + // セルサイズの更新 + const currentCellsize = button.style.width; + const expectedCellsize = `${cellSize}px`; + if (currentCellsize !== expectedCellsize) { + button.style.width = expectedCellsize; + button.style.height = expectedCellsize; } } } @@ -96,6 +100,10 @@ document.addEventListener("mouseup", () => { isDragging = false; }); +function getStyle(cell) { + return cell ? "black" : "white"; +} + renderBoard(); progressBoard(); @@ -150,7 +158,7 @@ function progressBoard() { } board = newBoard; generationChange(generationFigure + 1); - renderBoard(); + rerender(); } function resetTimer() { From 0e0eee78fc7c6bcadfe4eb8fbaf9b68e211ce4a9 Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:41:53 +0900 Subject: [PATCH 4/5] Refactor: Reorder functions --- src/iframe/life-game.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index b9917f2..8b85edd 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -29,6 +29,12 @@ function isNextAlive(around, self) { return false; } +// cellの状態に応じた色を返す関数 +function getStyle(cell) { + // cellがtrueなら黒、falseなら白を返す + return cell ? "black" : "white"; +} + //Boardの初期化 let board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => false)); const table = document.getElementById("game-board"); @@ -100,10 +106,6 @@ document.addEventListener("mouseup", () => { isDragging = false; }); -function getStyle(cell) { - return cell ? "black" : "white"; -} - renderBoard(); progressBoard(); From a6c1e2d56dddbf7651b18f905f69be0d743eb5ed Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:48:44 +0900 Subject: [PATCH 5/5] improve cellsize calculation --- src/iframe/life-game.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index 8b85edd..0d79283 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -7,14 +7,9 @@ let timerTime = 1000; let isDragging = false; let dragMode = false; // true: 黒にする, false: 白にする -const DEFAULT_BOARD_SIZE = 20; -const DEFAULT_CELL_SIZE = 30; - //変数設定 let boardSize = 20; //盤面の大きさ(20x20) -let cellScale = 1.0; //セルの大きさの倍率 - -let cellSize = Math.floor(cellScale * DEFAULT_CELL_SIZE * (DEFAULT_BOARD_SIZE / boardSize)); //セルの大きさ(px) +const cellSize = 600 / boardSize; //セルの大きさ(px) // around: 周囲の生きたセル数 self: 自身が生きているかどうか function isNextAlive(around, self) {