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
Tukudani
May 23, 2026
Programming
90
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
個人開発者よ、Honoを使え(おじさんver)
tanahiro2010
2
240
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
160
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
280
Unity6 URPによる海底をテーマにしたVJシステムの解説 | Unity Shader 完全に理解した 勉強会
gam0022
0
350
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
370
Verilogで学ぶCPU自作入門
uyuki234
8
4.1k
setup-vp GitLab対応の裏側
naokihaba
0
160
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
760
ソニーのクラウド共通基盤の変遷とAI時代の開発スタイルに合わせた進化 / The Journey of Sony’s Common Cloud Platform and Its Evolution for AI-Native Development
kenjiyoneyama
0
250
SREの越境 / SRE Collaboration
y0hgi
2
300
【加筆修正版】Laravel のアプリケーションをどこにデプロイするか #phpcon_ehime
akase244
0
150
JRuby: Past, Present, and Future
headius
0
230
Featured
See All Featured
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
510
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
1k
So, you think you're a good person
axbom
PRO
2
2.2k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
620
Color Theory Basics | Prateek | Gurzu
gurzu
1
480
The SEO identity crisis: Don't let AI make you average
varn
0
580
Test your architecture with Archunit
thirion
2
2.4k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
360
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
260
A Modern Web Designer's Workflow
chriscoyier
699
190k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.9k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
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箇所に集約する ・明確な課題が出ていないなら、実装はシンプルに始める ・スマホに対応するなら、操作感にこだわる 振り返って思ったこと: 「動くものを作る」と「正しく作る」は別 →動いたことに満足せず、 設計・計測・振り返りをセットにして初めて完成
ご清聴ありがとうございました! ご紹介したもの以外に興味のある機能があれば、 ぜひ懇親会等でお話ししましょう!