表エディタをフルスクラッチした話
こんばんは
daiiz.icon daiiz です
活動
生成AIと検索の自由研究
技術系同人誌の執筆
過去のKyoto.jsでの発表
Gyazo, Scrapbox, Helpfeel を作っている会社です
FAQ検索システム「Helpfeel」
Scrapboxに索引文を書いて変換すると賢く検索できるヘルプページになる
? (テーブルエディタ|表作成支援ツール|Table Maker)の開発話
このページをこんな感じで検索できる
https://gyazo.com/18251ebbd1dd027101548a31906aed9f https://helpfeel.com/daiiz/?q=テーブル
モチベーション
ちょっとリッチな表を書きたい
できるだけ直感的に操作したい
見た目の調整にこだわりすぎないツールにはしたい
既存のツールやライブラリを調査
Editor.js
いい線いっている
あともう一息直感的にできそう
https://gyazo.com/a8a7d443307bd34afafed0b4ae37546f
Quill
シンプルで格好いいが、tableは正式には対応していなかった
セル内のコンテンツの編集用エディタとして採用
Spreadsheet
ツールバーを操作をする以外のいいUIは作れないだろうか?
https://gyazo.com/fa3599e941c50e885de210259e0cb167/thumb/600#.png
表現力が豊かすぎる!
つくるぞ!
Table Maker
完全にイチから設計する挑戦のまとめ
デモ
実際に動かしながら紹介する
左に資料、右にデモ
デモ素材
SVGファイルが生成される
これを読み込んで編集を再開できる
https://scrapbox.io/files/647014e9c9c8a7001b2b4163.svg
この程度の表現力がほしい
セル結合
セル内に画像
セルに背景色
セル内の文字のセンタリング
生成物
こんな感じのSVG画像
code:svg
<svg>
<foreignObject>
<html>
<style>...</style>
<table>...</table>
</html>
</foreignObject>
</svg>
外部画像を埋め込む場合はbase64エンコードしておくとポータビリティが高まる
imgタグで普通に表示できるのでScrapboxでもプレビューできる
例: 冒頭のファイル
ここからはデモとともに解説
こだわりとからくり
いかに複雑化せずに作っていくか
table要素で表現
ツールと生成物ともにHTMLの<table>タグを使う
古から使われているtableの方があらゆる環境での表示の差異が小さそう
プレーンなHTMLで完結していると、WordやSpreadsheetなどの他のツールにも取り込みやすい
思わぬ嬉しさ
入出力をHTMLのtableにしたことで、外部のウェブサイトからコピペで取り込む機能が作りやすかった
https://gyazo.com/b8bfcac7fc1e9ed47932920d835b4baa.mp4
ポイント
各セルに操作用のハンドルとしてdiv要素が付いている
セルの右と下の2辺だけに配置すればよい
これらを組み合わせて、以降で解説する操作を実現できる
https://gyazo.com/95fa4a2a52a209ed9bdadd002e217d8f
セル 結合
一番の難所
モードの切替えなく実現できたのがよかった
隣接するセルの仕切りを取り払うことで結合していく
https://gyazo.com/9c78c44fb8c686275afd9078ddd9c031.mp4
セル結合情報の持ち方
セルの状態を表す2次元配列の表現
結論
<td colspan="列の結合数" rowspan="行の結合数"></td>に倣うのが最も扱いやすい
やはりHTMLはよく考えられている
結合範囲の左上のセルで両方向の結合量を持つ
他のセルでも便宜上の値を持っておく
0 or 1
1: 自身が上または左の辺を構成している場合
0: それ以外
例: 括弧内の値は (colspan, rowspan)を表す
列のみ結合
https://gyazo.com/c281a9fd280b4ca0876fbc5a96e5883d/thumb/300#.png
行のみ結合
https://gyazo.com/5849ea8ef9c1dc755d3fe9be027ba843/thumb/300#.png
両方向で結合
https://gyazo.com/ac4d21bdb965b911aa826229f2971e00/thumb/300#.png
https://gyazo.com/c8a0fab419befc06f41993566d65ca89/thumb/390#.png
セル 結合の解除(分割)
一番の難所
結合時に取り払った仕切りを再建する感覚で操作する
https://gyazo.com/0bd783e6c24c70f033509b033b546e21.mp4
セル 選択と移動
クリック
矢印キー
結合されたセルが登場すると難しくなる
一貫性と可逆性
矢印キーでの移動
結合されているセルを跨いだときの挙動に気を遣う
同じ列や行内で一貫した選択操作ができているかが大事
例1: 「Scrapbox」のセルがある3列目での下移動
横方向に結合されたセル(5行目)を通過した直後に、3列目の「Node.js」が選択される
通過後に2列目 (横方向の結合された一番左のセルの位置) に移動しないよう注意
https://gyazo.com/2afac534b516cf415a5582adf2c8fbc2
例2: 「Helpfeel」のセルから下移動を開始して跳ね返った後
元の位置に戻ってこれるか?
https://gyazo.com/2d5a350f84febbb7dea7b26c685334c9
Undo / Redo
Ctrl + Z, Ctrl + Shift + Z で何度でもやり直せる
スナップショット方式
一番簡単な実装方法
各操作後のテーブル全体の結果をすべて蓄積しておき、逆順に適用するだけで実現可能
Undo / Redoに関する一般的でさらに詳しい話はこの資料がおすすめ
セルの結合の分割は「実質Undo」
セルの結合・分割の連続操作はよく観察するとUndo的な挙動をしている
分割は結合の逆操作である
普通に分割すると
結合されていたセル内のテキストや画像は分割後のどちらのセルに属するべきか?
一番シンプルな解決法: 規則的にどちらかに寄せる
分割操作としては間違いではないが、結果に違和感がある
https://gyazo.com/08d583e9b124f8246bc6c6b4e44d9033.mp4
より直感的に分割するには?
Undoの一種として扱って分割する
結合前に所属していたセルへの再割り当てが可能
直前までの内容が復元されて気の利いた賢い動きになる
https://gyazo.com/ac160f5cdff5ec4c2d11233773a4f679.mp4
「実質Undo」の実現方法
分割前後のテキスト履歴を各セルで個別に保持することなく、Undoを応用して実現できる
code:操作履歴 分割前
状態 Data
-------------------
s0 data0
s1 (結合) data1 <-- 最新
状態履歴から復元先(s0)を適用する
この際に分割 (結合の逆操作) の履歴s0'を作って挿入しておくと、さらにUndo/Redoされても辻褄が合う
code:操作履歴 分割後
状態 Data
-------------------
s0' (分割) data0
s1 (結合) data1
s0 data0 <-- 最新
まとめ
操作や挙動を細かく観察する
セルの結合と分割をUndo / Redo操作と対応付ける
シンプルなツールにするためにいろいろ考える
より一般的なデータ表現
ポータビリティ
最高に直感的に使える表エディタができた
おまけ: 比較的簡単な操作集
行や列 追加と削除
外側に不可視状態で行と列が1個ずつある
番兵的に機能している
表の基本構造を維持している
https://gyazo.com/56e7fcdfc94e22e56a28e19309d9dcb9.mp4
目的のセルの仕切りの位置にカーソルを持っていくだけでよい
その位置でできる操作だけがサジェストされる
必要なときだけ見せる
ボタンを作用点の近くに出す
「いま何ができるのか」が明確になる
セル内の編集
セルごとに文字や画像を編集できる
https://gyazo.com/3c82c4d62f58f925a06c11cb5d5f3d05/thumb/400#.png
画像の大きさを調整する機能は自前で実装
https://gyazo.com/6aa5bec9e4c1e804afad18ada404e64d.mp4
横幅をいい感じにする
作成した表全体の横幅を統一したいときに手軽に調整できる
各列の横幅が、それぞれの割合に応じていい感じに伸び縮みする
https://gyazo.com/e8cbf0ba61a3ba3e8e46d6588f9c75ac.mp4