感動するUIの作り方 3連発
こんばんは
daiiz

です
Notaでの開発
Scrapboxチーム 2年
Helpfeelチーム 2年
趣味での活動
GUI勉強会に参加
本日の3本
履歴を遡るスライダー
タッチデバイスでの文字選択
表作成ツール
履歴を遡るスライダー
一定期間が経っている状態で編集されるとスナップショットが撮られる
行単位での編集差分を確認できる
履歴をパラパラ見ていくための操作パーツを作りたい
真っ先に思いつくUIの例
履歴が多くなったときに選びづらくなる
スナップショットの総数がひと目で分からない
課題
スナップショットが撮られた瞬間と、そのバージョンが継続していた期間の両方を示したい
素早く前後のバージョンを比較したい
本文に集中してほしいので細かい設定をいじったりするのは無し
こんな感じでどうだろう
等間隔な目盛りを表示する
1目盛りずつ移動するためのPrev/Nextボタンも置いてみる
目盛りの間隔の調整が難しい
スライダーを操作できるくらいの余裕が欲しい
等間隔にしてしまうと時間感覚が無くなってしまう
どういうことだろう?

等間隔ではなく、対数スケールで表示したほうがいいとか?
版の生存期間を表現したいということだと思う

あーなるほど!生存期間は版によって異なりますね

勝手に全部同じだと勘違いしてしまった
版の生存期間を長さで表現するのはよさそうだ
たしかに、スクロールUIってツマミで1点を表現するけど、本来は期間を示す場合は長さに意味が出るはずですよね

さらに改良できる?
編集履歴は多くなるだろうからツマミで期間調整するのは難しそう
→ 複雑さが増すので思い切って機能を削る
history一つの差分がそこそこ大きいのも、これができた理由の一つかな

一気にジャンプできるUIでないと操作できる
表示領域が狭いので細かい調整をするのには向いていない
一気に数年前のを見たいときは現状のUIだと無理そう

完成形
Prev/Nextだけを操作できるボタンを置くことにした
両者を近くに配置することでどちらの方向にもめくりやすい
スライダー上の点は棘状にすることで摘めない雰囲気を醸し出している
どれだけ間隔が狭くなっても、1pxの棘ならば密集しても重なることなく整列できる
タッチデバイスでの文字選択
Scrapboxのエディタはかなり特殊な実装
これにあわせて文字選択操作も自前で作っている
表示倍率を縮小したときに消えてしまうことがあったので、いまはSVGのrectになってる
課題
マウスがない環境ではどうやって操作すればいいだろうか
狭い画面でいかに狙い通り選択してもらうか
縦と横のスクロールで微調整していくのが難しいのだけど、なんとかなるものか?
まずは身近な例を観察
iOS
範囲を拡縮するツマミの位置が、選択領域の始点では上側、終点では下側
Android
ツマミはともに下側
OSでのつまみの位置の違いに着目したことがなかったので面白いです


iOSだとそうなるんだ!

思いつく限りいろいろ試す
想像やモックに頼らずに実際のコンテンツで動かしてみるのがよい
文字、画像、リンクが多いなど、どんなページでも操作しやすいか?
旅行先での移動中にさくっと書けるか?
画像を選択中に記法表示モードに切り替えないほうがスムーズでいい、とか気付ける
画面が広いPC版では問題なかったが、スマホではガタつきが気になる
PC
スマホ
ツマミの設計に苦戦
触れる領域が狭すぎるのが問題
複数行選択されたときの見せ方が難しい問題
いろんな形状を検討したけれどどれも納得がいかなかった
そもそもツマミは必要か?
小さい画面で小さいツマミを出すと誤タップを誘発しやすい問題
システムUIではなく、HTMLの一要素として表現するしかないので他の要素と衝突しがち
かなしい


選択範囲の両端を直接触ってもらえばよい!
ツマミの色を分ける必要もないのでは?
緑色に同化した結果、見事にツマミが無くなった
→ 不要なものを出さないことでシンプルになった
ツマミが飛んで来てくれるのとても使いやすくて助かっています。

初めて触ったときOSレベルでもそうなってほしいと思いました
ツマミを付けなかったのはツマミでうまくいかなかったからか

正直ツマミのほうがいいんじゃないかと思っていた
必ずしもツマミにこだわらなくてもいいのではないか?
ツマミをなくすとしたら?
今後「やはりツマミが必要なのでは?」という問いが戻ってくる可能性もありますね
文字選択がかなり難しい
両端をつかむと、指でどこをつかんでいるのかわからなくなる
選択解除と誤認識してしまうことがある
1文字単位の微調整で多いかも
Firefox for Androidだとほとんど動かなかった覚えがある
1年前のことだから、今改善したかはわからない
「微調整が難しい」に立ち向かう
無意識に次の操作を連続して行っている
① 長押しで初期範囲をつくる
② 目的の行までざっくりと縦スクロールする(画面外までスクロールするときもある)
③ 行内の目的の文字まで横スクロールする
② は不要なのではないか?
選択範囲は連続しているのだから、範囲の両端が決まればそれで十分なはず
指で隠れていて正確な位置を把握しづらい問題も解消される
最終的にこうなった
目的の両端付近の文字をちょっとなぞるだけでよい
初期範囲との位置関係から始点 or 終点を特定できる
終点が画面外にあるときは普通にスクロールすればいい
→ 不要な操作を求めないことで、無意識のうちにこなされるタスクが減った!
仕上げ
たしかに便利なんだけど、初見でユーザーに理解してもらえるか?
→ よく知られている一般的な操作方法もサポートする
長押しは理解出来なさそう


このまえ部活で使ったときに、スマホからどう文字を入力したらいいかわからないという話が出た
すぐカーソルがでて入力されるようになってもいいのかも
ですね、長押しで編集開始するのは難しい。改善していきたいですね

表作成ツール
既存のライブラリを試したが、あと一歩改良したい気持ちがあった
既存ライブラリでいいかんじのやつあんまりない

React Tableとか個人で試したことがありますが挫折しました
完全にイチから設計する、という挑戦
ほぼプロジェクトX

Scrapboxでキレイな表を作りたいときがあるので欲しい!!!!

目標
こういう表を簡単に作れるようになる
課題
極限まで直感的に操作したい
特にセルの結合と分割が難しい
狭いセルでストレスなく編集するには?
マウスや矢印キーでのセルの選択など
こういうメニューは避けたい
「この行を削除」「C列を削除」「3行目を削除」
「この下に1行追加」
「横方向に3マス結合」
あるあるw

セルに対する操作ボタンが並んだツールバーもやめたい
どうする?
各セルに操作用のハンドルを付ける
セルの右と下だけにdiv要素を配置する
これから紹介する操作がこれで綺麗に収まる
最初は4辺のすべてに必要だと思い込んでいた
上と下、左と右の当たり判定が厳しくて困る
色々考え込んだ後の休憩中に2辺だけでいけると確信した
喫茶店のお手拭き?
ナプキン!!

サルバドール・ダリみたい
心の余裕も大事
行や列の追加、削除
まずは最も基本的な操作から
目的のセルの仕切りの位置にカーソルを持っていくだけでいい
その位置でできる操作だけがサジェストされる
必要なときだけ見せる
ボタンを作用点の近くに出すことで「いま何ができるのか」が明瞭になる
すごく高速で機能にアクセスできる

セルの結合、分割
一番難しく、もっともこだわりたかった部分
対象のセルをチェックボックスで選んだ後にボタン操作する?
モードの切替も発生する
編集と選択のモードが必要になる
「見た目が悪い」だけでなく、一気に難しいツールになってしまう
ありそうな上にヤバいUIで良い

ホラーUIじゃん

目的の仕切りの位置にカーソルを持っていくだけでいい
結合: 仕切りを取り払いたい場所
分割: 仕切りを作りたい場所
ボタン操作によって影響を及ぼす範囲が事前に示されるので戸惑わない
名付けるならpreview UI?

どんな順番で結合や解除をしても思い通りの形状を作れる
→ 順不同で同じ結果が得られる
セルを直接触れている感覚が得られてとても良い
Excelを倒せるのでは?!


なんで今までこうなっていなかったんだろうっていうレベルの自然さを感じてすごい!

むちゃくちゃ既視感がある

どこかのなにかで見たことあるような……
いずれにせよどうなるかがすぐわかるUIは好きです
すぐに結果が出てくる
リンク入力補完も、次どうなるかがわかってから補完する
Tips: 結合されたセルの形状情報はどうやって持っておくのがいい?
<td colspan="列の結合数" rowspan="行の結合数"></td>
に倣うのが結局一番扱いやすかった。HTMLはよくできてる

結合範囲の左上のセルで両方向の結合量を持っておく
他のセルでも便宜上の値を持っておく
結合されて独立していないセルでは結合量を0にする
結合の座標情報は左上のセルだけが持っていればいい
他のセルは自身が上または左の辺を構成しているかを0/1で持っておく
例: 括弧内の値は (colspan, rowspan)
を表す
列のみ結合
行のみ結合
両方向で結合
テキスト編集とセルの横幅調整
選択したセルのそばにエディタが現れる
テキストを書いていると横幅を調整したくなる
編集中にセルの仕切りをドラッグできるといい
横方向だけの移動だが手は縦方向にもブレるものなので、画面全体で移動量を補足できるようにしておく
カーソルがエディタ内部に侵入してしまってもキャンセルされないように
大胆に操作できるような配慮をしておく
セルの高さの調整は?
シンプルさを保つため敢えて実装せず、内部のコンテンツの高さに任せることにした
→ 機能を削る
調整中は縦の青い補助線は出ない?
出さないことにした
セル内に画像が含まれている場合など、高さが激しく変化して再計算するのが大変なので諦める
矢印キーでのセルの移動
結合されているセルを跨いだときの挙動に気を遣う必要がある
同じ列や行内で一貫した選択操作ができているかが大事
例: 「Scrapbox」のセルがある3列目での下移動
横方向に結合されたセル(5行目)を通過した直後に、3列目の「Node.js」が選択される
実装時にサボると通過後に2列目 (横方向の結合された一番左のセルの位置) に移動してしまう
可逆性を保つ
例: 「Helpfeel」のセルから下移動を開始して跳ね返った後に、元の位置に戻ってこれる
Undo / Redo
Ctrl + Z, Ctrl + Shift + Z で何度でもやり直しができる
一番簡単な実装はスナップショット方式
各操作後のテーブル全体の結果をすべて蓄積しておき、逆順に適用するだけで実現できる
セル結合(2回)→ Ctrl+Z(2回)→ Ctrl+Shift+Z(2回)
実質Undo
セルの結合・分割の連続操作はよく観察するとUndo的な挙動をしている
= 分割は結合の逆操作とも捉えられる
同じ作用点で結合と分割が連続して行われたときのみ発動する
結合されたテキストの再分割の結果が直感的になる
普通に分割すると
結合されていたセル内のテキストや画像は分割後のどちらのセルに属するべきか?
一番シンプルな解決法はどちらかに寄せてしまうこと
これでも分割操作としては間違いではないが違和感がある
Undoの一種として扱って分割すると
結合前に所属していたセルに再分割することが可能になる
直前までの内容が復元されるので気の利いた賢い動きになる
富豪的プログラミングの恩恵?

Tips: 「実質Undo」の実現方法
分割前後のテキスト履歴を各セルで個別に保持することなく、Undoを応用して実現できる
操作履歴 Unmerge前Op Data
-------------------
Init data0
MergeCol data1 <-- 最新
操作履歴から復元先(Init)を適用する
この際にUnmergeとその逆操作の履歴を作って挿入しておくと、さらにUndo/Redoされても辻褄が合う
操作履歴 Unmerge後Op Data
-------------------
UnmergeCol data0
MergeCol data1
Init data0 <-- 最新
おまけ: 生成物の出力形式
このようなSVG画像
svg <svg>
<foreignObject>
<html>
<style>...</style>
<table>...</table>
</html>
</foreignObject>
</svg>
画像を埋め込む場合はbase64エンコードしておくとよい
嬉しさポイント
HTMLのtableタグとして保持しておけるので他のツールでも使いまわしやすい
SVG画像なのでimgタグでも表示できる
Scrapbox上でプレビューできる!
まとめ
感動するUIを作るには
UIパーツや各操作の本質を考える
身近な例も観察する
オリジナルな快適さを探究するものの、一般的な使い勝手と乖離しすぎないよう気をつける
この塩梅が難しいけれど、頑張ってこだわりを届けていこう
触りながら調整を繰り返していくしかない
複雑さを回避するために実装しないという決断を下す勇気を持つ
すごいなー

論文になるレベル...

Scrapbox上でどう展開するんだろう
期待

Scrapboxにもこの表ほしい

ぱっと思いつくとこだとコードブロックにデータ展開?

快適さを追求するにはもっと練らないとだけど