前回
-
SVGを書くのが面倒なので入力補完できるようにしてみる 1
内部に持った状態を変更させるオブジェクト、メソッドチェイン、基本シェイプ、<path>
のd要素 -
SVGを書くのが面倒なので入力補完できるようにしてみる 2
useに潜んでいた罠、<animate>
- SVGを書くのが面倒なので入力補完できるようにしてみる 3
- SVGを書くのが面倒なので入力補完できるようにしてみる 4
引き続き ↓ コレ作ってるときの話です。
GitHub: mafumafuultu/svg.js dev
Render 描画品質
図形、画像、テキストの描画品質を設定できるんですが、プロパティ名は長いし値も長いのでスペルミスとか怖いし、比較に時間をかけるのも嫌なので、プロパティ名とそれに紐づく値も設定できるようにしたいなー。
↑ こんなこと考えたおかげで新しい仕組を考えることになった。
そして、今回はこのRenderでおなかいっぱいになりそうな予感がする。
条件
- 図形、画像、テキストのレンダーの設定を一か所にまとめたい
- レンダーのプロパティ名は長いので補完したい
shape-rendering
text-rendering
image-rendering
- 各レンダープロパティの値も長い。補完したい
optimizeSpeed
crispEdges
geometricPrecision
optimizeLegibility
- etc.etc...
-
render().shape().optimizeSpeed().text().geometricPrecision()
な感じで補完したい
↑ 鬼の所業 - 面倒でも可読性は確保したい
↑ 鬼の所業
問題点
まず、この図式
Render => Text => Value-A => Render
Render => Shape => Value-B => Render
Render => Image => Value-C => Render
Render
を呼び出し、各レンダープロパティ名を補完、各レンダープロパティは設定可能なValueを補完し、Render
のもとに帰る。
つまりこんな書き方が成立しなければダメだということになる。
render().Text().auto().Text().auto().Text().auto()
.Shape().auto().Shape().auto()
.Image().auto().Text().auto().Image().auto().Text().auto();
はてさて、どうしたものか...
デカルト「困難は分割せよ」
まずは理解できる単位に、構造を分解・整理してやればいい。
Render => [Text => Value-A] => Render
Render => [Shape => Value-B] => Render
Render => [Image => Value-C] => Render
Render => [property => Value] => Render
T = [property => Value]
Render => T => Render
他人が作ったスパゲッティを読み解くよりは楽です。
見えてくるのはT
がRender
のthis
を受け取り、中身を変更して返せばいい。
const __shapeRender = o => ({
auto() {return o.render['shape-rendering'] = 'auto', o;}
});
const renderGen = () => ({
render: {'shape-rendering': 'optimizeSpeed'},
Shape() {return __shapeRender(this);}
});
まぁ、これで renderGen().Shape().auto().Shape().auto()
はできるようになった。
よしよし、イケるイケる。
そんなわけなかった
ダサい。
const __shapeRender = o => ({
auto() {return o.render['shape-rendering'] = 'auto', o;},
optimizeSpeed() {return o.render['shape-rendering'] = 'optimizeSpeed', o},
crispEdges() {return o.render['shape-rendering'] = 'crispEdges', o;},
geometricPrecision() {return o.render['shape-rendering'] ='geometricPrecision', o;},
});
-
o.render['shape-rendering']
何度も書いてる -
shape-rendering
に 値を入れてo
を返す構造は共通している - そして、その構造はほかのrenderにも出てくる
というわけでその辺を解決していく
renderGen().Shape()
と呼んだ時点で分かっている情報は
-
shape-rendering
に設定される値が後で選ばれる -
renderGen
のthis
も渡ってくる
なので、こんな仕組みが必要だろう。
const vmod = key => (val, obj) => (obj.render[key] = val, obj);
このままでもいいのだけれども、もう少し疎結合にしておく。
// 役割がわかればいいので、頭文字だけにする
const vmod = (k, f) => (v, o) -> (f(o, k, v), o);
const __renderMap = (o, k, v) => o.render[k] = v;
任意の (o, k, v)
を引数に取る関数を渡せるように変更した。
使うかどうかはわからないけれども、ほかの処理作るときに再利用しやすいようにはなった。
使用する側は、変更したいプロパティと変更用の関数をvmod
に渡して、適当な名前を付けて、閉じ込めておき、どの値を使用するか確定したらその値とrenderGen
のthis
を受け取ってrender
を変更し、renderGen
のthis
を returnする。
const __shapeRender = o => ({
auto() {return this._set('auto', o)},
_set: vmod('shape-rendering', __renderMap)
});
const renderGen = () => ({
render: {'shape-rendering': 'optimizeSpeed'},
Shape() {return __shapeRender(this);}
});
これで renderGen().Shape().auto().Shape().auto();
ができるようになった。
あとは残りのRenderを仕様とにらめっこしながら書いていくだけなので楽なものです。
面倒な割にはこれで80%というところでしょうか?
次回は、「あったらいいな」「これ、ほしいな」な便利系の整理です。
使い勝手を決める重要な部分ですね。