Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
TypeScriptでドット絵エディタ実装録: 状態設計と実装判断
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Tukudani
May 23, 2026
Programming
80
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
モバイル交通系ICへのチャージ実例から考える、クロスプラットフォーム開発におけるiOS実機テスト設計とCI運用
yusuga
1
470
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
570
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
370
Building an Out-of-Order CPU
latte72
1
780
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
4
2.5k
Omarchy Tokyo やると聞いて UMPC 買ってセットアップしてきた
mtsmfm
0
140
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
1
270
技術的負債の返済は、AI時代の複利で効く投資 — 経営としての意思決定とその遂行
curekoshimizu
0
1.3k
A2UI for Android: Safely Rendering AI-Generated UI with Jetpack Compose - DroidKaigi 2026
itsmedreamwalker
0
140
TiDB Cloudのカスタムコントローラーによるオートスケール対応
takaidohigasi
0
120
GraphRAGのKnowledge Graphを 直接!見る/View-GraphRAG's-KnowledgeGraph-directly!
tyumugi1113
1
320
20260828_品質と開発生産性を両立させる、AI時代のE2Eテストの考え方
magicpod
0
210
Featured
See All Featured
A Tale of Four Properties
chriscoyier
163
24k
Why You Should Never Use an ORM
jnunemaker
PRO
61
10k
VelocityConf: Rendering Performance Case Studies
addyosmani
331
25k
Un-Boring Meetings
codingconduct
0
420
Side Projects
sachag
456
43k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
The browser strikes back
jonoalderson
0
1.7k
Odyssey Design
rkendrick25
PRO
2
810
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
First, design no harm
axbom
PRO
2
1.3k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.5k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
240
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箇所に集約する ・明確な課題が出ていないなら、実装はシンプルに始める ・スマホに対応するなら、操作感にこだわる 振り返って思ったこと: 「動くものを作る」と「正しく作る」は別 →動いたことに満足せず、 設計・計測・振り返りをセットにして初めて完成
ご清聴ありがとうございました! ご紹介したもの以外に興味のある機能があれば、 ぜひ懇親会等でお話ししましょう!