Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
TypeScriptでドット絵エディタ実装録: 状態設計と実装判断
Search
Tukudani
May 23, 2026
Programming
42
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
TypeScriptでドット絵エディタ実装録: 状態設計と実装判断
https://2026.tskaigi.org/talks/49
TSKaigi 2026の10分LT登壇資料です
Tukudani
May 23, 2026
Other Decks in Programming
See All in Programming
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
130
net-httpのHTTP/2対応について
naruse
0
480
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
540
Datadog × OpenTelemetry 入門と実践のあいだ
kn_to_maxpno
1
160
「なぜそう決めたのか」を残し続ける仕組み ― Notion AI カスタムエージェント × Slack連携による設計判断の自動記録 - NIKKEI Tech Talk #47
niftycorp
PRO
0
170
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
4k
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
140
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
330
依存関係から依存物へ―Dependencyという言葉の歴史をひも解く
j_lee
0
120
Go1.27で導入されるジェネリクスメソッドでできること
mackee
0
120
Creating Composable Callables in Contemporary C++
rollbear
0
130
代数的データ型って何が嬉しいの? #frontend_phpcon_do
kajitack
8
3.7k
Featured
See All Featured
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How GitHub (no longer) Works
holman
316
150k
The Spectacular Lies of Maps
axbom
PRO
1
810
Un-Boring Meetings
codingconduct
0
310
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
850
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.6k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Bash Introduction
62gerente
615
220k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
210
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Why Our Code Smells
bkeepers
PRO
340
58k
Ruling the World: When Life Gets Gamed
codingconduct
0
250
Transcript
TypeScriptでドット絵エディタ実装録 ~状態設計と実装判断~
自己紹介
自己紹介 つくだに / 佐川 • 出身:福島県 • 所属:株式会社 オプティム フロントエンド推進室
運営メンバー • 趣味:サウナ・ゲーム・酒・ドット絵
4
5 私たちは、AI・IoT技術を社会の隅々に行き渡らせ、 日本のあらゆる産業を根底から変革する 「AXの中核を担う企業」を目指しています。
6
アジェンダ • 作ったものについて • 機能と実装判断 • まとめ 話す 話さない •
フレームワークとか、その他のこと • Canvas API の詳細 • Nuxt / Vue の詳細 • ドット絵エディタアプリ実装のロジック判断 • データ構造 • 直線・塗りつぶし • レイヤー管理 • モバイル操作の工夫
作ったもの WEBアプリ「DotArt」 • 機能 • ドット絵エディタ • ペン / 消しゴム
/ 直線 / 塗りつぶし • Undo・Redo • レイヤー機能(最大3層) • 画像保存・共有(共有機能は停止中) • モバイル・タブレット対応 • 技術スタック • Nuxt (Vue.js)
作ったもの 作った経緯 • 当時、iPhoneにモノクロ4色で簡単にドット絵が描けるアプリがあり、 とても気に入っていた • iOS11 での 32Bitアプリのサポート終了により起動不可に •
このアプリからドット絵を描き始めた人が色つきの絵を描きたくなった時に、 高機能なエディターに移行する前に触りやすい簡単なエディタを作りたかった • AIエージェントが使われだす前だったのもあり、ロジックを自前で実装していった
型システムとライフサイクルの時間軸の衝突 Nullアサーション問題 • 基本的に Canvas 要素で絵を描いているため、DOM参照が必須 • Strict モードだと初期化時は null
許容型にせざるを得ない → canvasRef.value! のような null アサーションが至る所に散らばった • nullチェックを1回行った後は、 non-nullable な変数に再アサインするのがよさそう const canvas = document.querySelector<HTMLCanvasElement>('#drawcanvas'); const ctx = canvas?.getContext('2d'); if (!canvas || !ctx) throw new Error('canvas init failed’); canvasState.canvas = canvas; canvasState.canvasCtx = ctx;
Point型 ── 座標を守る型 Point型 • ドット絵エディタは座標計算だらけ • ドット絵を描く処理だけでなく、マウス操作のカーソル位置監視でも座標を受け取る • 座標情報を一元的に型指定することで広範囲の安全性を担保できた
• 小さい型定義だが、影響範囲が広いほど恩恵も大きい • 型定義のありがたみ、TSありがとう type Point = { X: number; Y: number } // 座標を受け取る関数すべてに適用 const getMousePoint = (wholeCoor: Point): Point => { ... } const figureToolsState = reactive<{ // 直線描画の状態管理 drawingFigure: Point[]; // ドラッグ中の一時表示 figureToolsStart: Point; // 直線の始点 }>({...})
基本データ構造 キャンバスデータ • ドット絵の内容と、パレットデータ等を管理する基本的な概念
基本データ構造 • 塗られた色をパレット配列のインデックスで管理 • ドット絵上の座標と1次元配列の対応は Y座標 × 横幅 + X座標
canvasIndexData[ Y × N + X ] = paletteIndex 0 9 9 9 9 9 9 9 0 0 8 8 8 8 0 0 0 0 0 0 9 0 0 0 0 8 0 0 0 0 8 0 … … [ Y × 0 + X ] [ Y × 1 + X ] … 0 1 2 3 4 5 6 7 8 9 … 仕様
描画ツール 直線ツール • 直線を引く機能
描画ツール 実装 • ブレゼンハムのアルゴリズムを採用 • 一般的な線分描画のアルゴリズム • 直線状で横に1マス進むごとに縦に何マス進むか? を、誤差を積み上げるという手法で解決した let
e = -xdiff; for (let i = 0; i <= xdiff; i++) { Line.push({ ...cell }); cell.X += Xvek; e += ydiff2; if (e >= 0) { cell.Y += Yvek; e -= xdiff2; } }
描画ツール • 例: (0,0) → (6,3) の直線 (※詳細は後ほど確認ください) 横方向に 7
, 縦方向に 3 マス進む ↓ 閾値 = 7(横の距離) 加算量 = 3(縦の距離) 横移動のたびに誤差 += 3 を積み上げ、 誤差が 7 以上になったら縦に1マス進み、誤差 -= 7 する X=0: 誤差= 3 → 7未満 → Y=0 ・・・( 0, 0 ) X=1: 誤差= 6 → 7未満 → Y=0 ・・・( 1, 0 ) X=2: 誤差= 9 → 7以上! -7 → Y=1 ・・・( 2, 1 ) X=3: 誤差= 5 → 7未満 → Y=1 ・・・ ( 3, 1 ) X=4: 誤差= 8 → 7以上! -7 → Y=2 ・・・ ( 4, 2 ) X=5: 誤差= 4 → 7未満 → Y=2 ・・・ ( 5, 2 ) X=6: 誤差= 7 → 7以上! -7 → Y=3 ・・・ ( 6, 3 )
描画ツール バケツツール • 囲われた中を塗りつぶす機能
描画ツール 実装 • 再帰処理(4方向)を採用 • 特にひねりなく、別の色がぶつかるまで上下左右へ走査する const fill = (cell,
color, target) => { if (範囲外) return; if (target[cell.Y * N + cell.X] !== color) return; drawDot(cell); fill({ X: cell.X - 1, Y: cell.Y }, color, target); fill({ X: cell.X + 1, Y: cell.Y }, color, target); fill({ X: cell.X, Y: cell.Y - 1 }, color, target); fill({ X: cell.X, Y: cell.Y + 1 }, color, target); };
描画ツール シンプルさを重視 • どちらも一般的な方式で実装 • ドット絵のスケールでは再帰処理の呼び出し回数もそこまで多くならないので、十分と判断 o 96 × 96
程度であれば問題なく動作 実装判断
レイヤー機能 レイヤー機能 • ドット絵をレイヤー管理する機能
レイヤー機能 実装 • 最大3層、キャンバスデータを積み重ねる layerdCanvasData: { layerName: string // レイヤー名
layerIndex: number // レイヤーの重なり順 active: boolean // 表示/非表示 canvasIndexData: number[] // キャンバスデータ } canvasesIndexData: layerdCanvasData[] // 全レイヤーの配列
レイヤー機能 • ドット絵の表示処理で3層のレイヤーを下から重ねるようにすると、 何か書くたびにキャンバス × 3の比較処理が走ってしまう • 「今描いているレイヤーの上の同じ位置に色があるか」の判定があれば、 レイヤー1枚走査するだけで描画可能なのでは? 思い付き
レイヤー機能 思い付きの結果 レイヤーの合成関数→ (約150行)
レイヤー機能 • やってることはこんな感じ • 元々分岐が多い上に、 「現在描画されている最上位レイヤー」の更新処理等で複雑化している • シンプルな実装から入り、ボトルネックになってから最適化するべきだった What is
this 描画したピクセルは最前面のレイヤーか? ├─ Yes(このレイヤーが最前面) │ ├─ 背景色 かつ 下に色あり → 透過と見なす 下レイヤーの色で描画 │ ├─ 背景色 かつ 下に色なし → 背景と見なす 背景色で描画 │ └─ それ以外 → そのまま描画 └─ No(上位レイヤーが前面) → 描画スキップ、データ更新のみ
モバイル対応 ─ カーソル方式 スマホ操作モード • PC操作をそのままスマホ版に持ち込むと、 指で直接描く形になる →描いているところが指で隠れてしまう • タッチペンが無いと手を放すまでミスに気づけ
ないため、改善したい 指で隠れるので ものすごい見づらい
モバイル対応 ─ カーソル方式 実装 • 「スマホモード」として、 直接タップではなくカーソル操作形式で描画できるように
モバイル対応 ─ カーソル方式 画面内を スワイプすると カーソルも動く ボタン押下中は 描画を行う
モバイル対応 ─ カーソル方式 カーソル方式 • 既存スマホアプリで実装されており、非常に快適な操作が実現されていた • 代案が全く思いつかず、WEBで挙動を再現した! → より良い操作方式があれば知りたい
実装判断
まとめ
まとめ ・nullチェックは1箇所に集約する ・明確な課題が出ていないなら、実装はシンプルに始める ・スマホに対応するなら、操作感にこだわる 振り返って思ったこと: 「動くものを作る」と「正しく作る」は別 →動いたことに満足せず、 設計・計測・振り返りをセットにして初めて完成
ご清聴ありがとうございました! ご紹介したもの以外に興味のある機能があれば、 ぜひ懇親会等でお話ししましょう!