3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SVGを書くのが面倒なので入力補完できるようにしてみる 2

Last updated at Posted at 2020-07-12

シリーズ

  1. SVGを書くのが面倒なので入力補完できるようにしてみる 1
    内部に持った状態を変更させるオブジェクト、メソッドチェイン、基本シェイプ、<path>のd要素
  2. SVGを書くのが面倒なので入力補完できるようにしてみる 2
    useに潜んでいた罠、<animate>
  3. SVGを書くのが面倒なので入力補完できるようにしてみる 3
    render周りを便利にしたい
  4. SVGを書くのが面倒なので入力補完できるようにしてみる 4

引き続き ↓ コレ作ってるときの話です。
GitHub: mafumafuultu/svg.js dev

useでコピーして使いたい

複雑なシェイプを何度も何度も書くのは愚策、コピペで済むけども修正が面倒なので<use> <symbol> <g> <defs> を作っていく

image.png

こう書いて
svg(200, 200).$(
    defs().$(
        group().attrs({id: "foo"}).$(
            rect(0, 0, 10, 10)
        )
    ),
    symbol().attrs({id: "bar", width: 20, height: 20, viewBox: "0 0 20 20"}).$(
        circle(10, 10, 10)
    ),
    use("#foo").attrs({x: 50, y: 0, style: "fill:red;"}),
    use("#foo").attrs({x: 100, y: 50, style: "fill:green;"}),
    use("#foo").attrs({x: 150, y: 100, style: "fill:pink;"}),
    use("#bar").attrs({x: 0, y: 0, fill: "red"}),
    use("#bar").attrs({x: 50, y: 50, fill: "green"}),
    use("#bar").attrs({x: 100, y: 100, fill: "pink"}),
);
こんな出力がほしい
<svg width="200" height="200" viewBox="0 0 200 200">
    <defs>
        <g id="foo">
            <rect x="0" y="0" width="10" height="10" />
        </g>
    </defs>
    <symbol id="bar" width="20" height="20" viewBox="0 0 20 20">
        <circle cx="10" cy="10" r="10" />
    </symbol>
    <use xlink:href="#foo" x="50" y="0" style="fill:red;" />
    <use xlink:href="#foo" x="100" y="50"" style="fill:green;" />
    <use xlink:href="#foo" x="150" y="100" style="fill:pink;" />
    <use xlink:href="#bar" x="0" y="0" fill="red" />
    <use xlink:href="#bar" x="50" y="50"" fill="green" />
    <use xlink:href="#bar" x="100" y="100" fill="pink" />
</svg>

定義の追加

まず定義を追加しないと始まらない

const defs = () => wrapper(_svgTag('defs'));
const symbol = () => wrapper(_svgTag('symbol'));
const group = () => wrapper(_svgTag('g'));
const use = (id, attr={}) => wrapper(_svgTag('use')).attrs({href: id, ...attr});

xlink:href はSVG2で非推奨になるので、xlinkを外したものだけ書いておく
xlink:href MDN

要素.append の罠

svg(200, 200).$(...child)で要素を追加するとき

const __BASE_PROTO__ = {
    '@parse' : {
        /* 要素にappendできるように、子要素をunwrap */
        value (arr) {return arr.filter(v => null != v).map(v => '@' in v ? v['@'] : v)}
    },
    $: {
        value(...child) {
            /* 状態として持った要素に子要素を追加 */
            return this['@parse'](child).reduce((el, ch) => (el['@'].append(ch), el), this);
        }
    },
    /* 略 */
}

こんな感じでappendしていたら<use>は動かなかった。

要素.appenduse動かなかった原因

<use>がDOMに追加されるときに、参照したい要素がDOMに入っていないことが原因だと思われる。

解決方法

要素.innerHTMLにつっこむ。参照したい要素も同時にDOMに入るようになるので無事解決。

const __BASE_PROTO__ = {
    '@parse' : {
        /* 要素にappendできるように、子要素をunwrap */
        value (arr) {return arr.filter(v => null != v).map(v => '@' in v ? v['@'] : v)}
    },
    $: {
        value(...child) {
            /* 状態として持った要素に子要素を追加 */
            return this['@'].innerHTML += this['@parse'](child).reduce((t, v) => 'outerHTML' in v ? t + v.outerHTML : t + v, ''), this;
        }
    },
    /* 略 */
}

だいたい覚えていないanimation

「あっちの要素のアニメーション終了時にこっちのアニメーションを開始して、500msでこの座標、そこからたっぷり3000msかけてこのパスに沿って1周200msで時計回りに回転しながら移動、それ3回繰り返してから、こっちのアニメーションを...」
はい、タグ打つのを辞めたくなる感じの要求ですね。
こんなものだって、入力補完があれば何とかなるんじゃないかなと考えて作ってる最中です。

const animate = (id, ms = 1000, repeat='indefinite', target) => ({
    op : {id: id, dur: `${ms}ms`, repeatCount: repeat, attributeName: target, attributeType:"XML",},
    timeFunc: [],
    // キーフレームの指定
    value (...val) {return delete this.op.keyTimes, this.op.values = val.join(';'), this;},
    timeLine (timetable = {'0' : 0, '1': 1}) {return Object.assign(this.op, {values: Object.values(timetable).join(';'), keyTimes : Object.keys(timetable).join(';')}), this;},
    rotate (r = '0') {return this.op.rotate = r, this;},
    spline(...splines) {return Object.assign(this.op, {calcMode : 'spline', keySplines: splines.join(';')}), this;},
    /* 略 */
    close() {return vg(_svgTag('animate')).attrs(this.op).$(this.timeFunc.map((tag, opt) => wrapper(_svgTag(tag)).attrs(opt)))}
});

<animatePath>とか<animateTransform>も同様に属性を状態として持たせて、状態を変化させる関数をチェインする仕組みにしています。

以下の仕様例は<animateTransform>ですが、こんな具合になればいいなと

line(0, 0, 0,-200).attrs({stroke: 'black'}).$(
    animateTransform('time_sec', 60000, none,'transform').rotate().values(0, [0, 0], 360, [0, 0]).close()
)
<line x1="0" y1="0" x2="0" y2="-200" stroke="black">
    <!-- 線の片側の(0, 0)を原点にして1分かけて360°回転 = 秒針 -->
    <animateTransform id="time_sec" dur="60000ms" repeatCount="indefinite" attributeName="transform" attributeType="XML" type="rotate" from="0 0,0" to="360 0,0"></animateTransform>
</line>

今回は、これら<use> <animate>周りでおなか一杯になる感じですね。
3次元的なアニメーションをするときは4x4の行列の計算も出てくるので、そのあたりをどう実装してやろうか考えている最中です。

(@╹ω╹@) < ぎょーれつのしょりはたぶんできていたきがする

ここまでやって75%完成というところでしょうか?では、また次回

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?