@ledsun blog

無味の味は佳境に入らざればすなわち知れず

TextAEのリレーション描画を独自SVGに置き換えた

お仕事の話です。 長年GitHub - pubannotation/textaeの改修に携わっています。

リレーション(下の図の矢印)の描画にjsPlumb Toolkit - build connectivity quicklyというコネクション描画用のライブラリを使っていました。 汎用ライブラリですので、少しオーバースペックなところがあります。

f:id:ledsun:20210418181110p:plain
TextAEのリレーション描画例 Before

これをピュアなJavaScriptSVG要素を描画する実装に置き換えました。

f:id:ledsun:20210418181107p:plain
TextAEのリレーション描画例 After

かっこいいでしょ? 自慢したかったんです。 この後は全部蛇足です。

課題

jsPlumbは、僕が開発に参加した2013年以前から、8年以上使っていました。 元々SVGで曲線を描く知見がなかったので、大変よく役立っていました。 大きな不満点は次の3つです。

  1. jsPlumbのバージョンアップに失敗した
  2. jsPlumbのパッケージサイズがそれなりに大きい
  3. 非同期の描画が必要

1番目は使い方の問題だと思いますが、バージョンを上げると意図しない描画がされることがありバージョンアップに失敗しました。やっかいな点は依存するjQueryのバージョンも固定されることです。現在のjsPlmumはjQueryに依存していません。どうにかしてjsPlumbのバージョンを上げないと特定バージョンのjQueryへの依存が解消できませんでした。

2番目はそのままです。2013年頃は、依存ライブラリはCDNで入れる時代でした。当時は複数のサイトから同じライブラリを使っているときに、CDNからダウンロードしておけば、ブラウザのキャッシュを有効に使えるもくろみでした。

  • 同じライブラリを使っているとしてもバージョンまで一致することが希
  • 依存ライブラリが増え、並行ダウンロード数がブラウザの上限にひっかかる

などの理由で、現在では配布パッケージに依存ライブラリも同梱するのが主流になっています。 textaeもその流儀に従って、textaeを配布するときにjsPlumbを同梱しています。 jsPlumbのサイズが大きいと、textaeの配布サイズも大きくなります。

3番目は、jsPlumbで曲線を描くには「両端の座標が異なる」という制約があります。 線でつなぐ要素は、タイミングよっては、位置を決める前に0, 0に配置していることがありました。 これを回避するために、線のレンダリングタイミングを非同期で行っていました。 ほかにも、過去には要素の位置決めをブラウザのレンダリングに頼っている実装であったため、要素と線をすべて同期的にレンダリングするとブラウザが固まる問題を避けるためでもありました。 この問題はしばらく前に位置決めロジックをJavaScriptで実装することで解消しています。 しかし、jsPlumbを使っているとレンダリングを同期的に行えない部分がのこります。 非同期にレンダリングをしていると、

  1. 要素をつなぐ線を追加
  2. 要素を削除
  3. 線をレンダリング
  4. 基準となる要素がない!!

のような、タイミングの入れ替わりを考慮する必要があります。 すべてのレンダリングを同期にすると、レンダリングにまつわるロジックを簡単にすることができます。

効果

曲線をピュアなJavaScriptで実装した結果、次の3つの効果が得られました。

  1. レンダリングの高速化
  2. 線の表現力の向上
  3. 依存ライブラリの最新化

1番目。jsPlumbは、汎用ライブラリであるため、一本の線が一つのSVG要素です。今回は専用ロジックを書いたので、エディターに対してSVG要素を一つ用意しました。一つのSVGの要素にPath要素を追加・削除して線を描画しています。これによってブラウザ側でSVG要素の位置を決める計算が無くなりました。ブラウザはHTML要素の位置決めに大変多くの処理をします。また一度にすべてのHTML要素の位置を計算します。この処理が重いとブラウザが固まります。

2番目。jsPlumbは、3次ベジェ曲線を描画しますが、設定できるパラメータが曲率だけです。制御点を操作することができないため曲がる場所を操作することができません。また、一つの曲線は一つの3次ベジェ曲線で構成されるため、はてなマークのようなカーブは描けません。線と他のHTML要素がかぶると線が見分けにくくなります。これを避けるための工夫の余地がありませんでした。

また、線を他のHTML要素とかぶらないようにするため、なるべく細い線で描きたいです。ですが細い線はクリックしにくい問題があります。今回は、線のPath要素の周りに半透明の極太のPath要素を描きました。半透明の部分をクリックして線を選択できるようにして、この問題を回避できました。

3番目。前述のように使用していたバージョンのjsPlumbに、jQueryの特定のバージョンへの依存がありました。これが解消されたため、すべての依存ライブラリを最新バージョンに更新しました。

手法

jsPlumbを置き換えるために次の3つの作戦を実施しました。

  1. 僕のSVG力を高める
  2. jsPlumbを利用するクラスを一つにまとめる
  3. MVVM化

1番目。

少なくとも一つの仕事を請け負ってご飯を食べたので、成功しました。

2番目。依存ライブラリのファサードクラスを作っておいて、ファサードクラスと同じインターフェースの独自実装に置き換えました。

リファクタリング」の旧版に書いてあった記憶があります。 読んだのは10年以上前なのでうろ覚えです。 大きめのリファクタリングの常道だと思います。

3番目。TextAEは長らくオブザーバー方式のMVC準拠で頑張ってきました。モデルの変更イベントで、ビューのメソッドがレンダリングするやつです。

デザインパターン」の最初の方で、ちょろっと紹介されているMVCです。 それをMVVMっぽくモデルの中にレンダリングロジックを移動しました。 これによってレンダリングロジックの呼び出し元がモデルに集約されました。 レンダリングロジックのIFがはっきりし、ファサードクラスが作れるようになりました。

移動したのはモデルをHTMLに変換するロジックのみです。イベント監視などは旧来のMVCのままのこしてあります。 ですので、MVVMに着想は得ていますが、MVVMそのものではありません。