ElmでウェブブラウザのAPIを直接叩けるのは公式のパッケージだけで、サードパーティのパッケージではネイティブなモジュールもportも使うこともできません。でも、joakin/elm-canvas というパッケージが存在していて、公式で対応していないはずのCanvas APIを叩いているのです。どないなっとんねん!と思って調べたので、どんな方法でやっているのか簡単に紹介しておきます。
1. 外部に送信したいデータを、property
を使って要素の"cmds"というプロパティに突っ込む
commands : Commands -> Attribute msg
commands list =
list
|> Encode.list identity
|> property "cmds"
既存のプロパティでなければどんな名前でもいいはずですが、em-canvasでは"cmds"というプロパティに突っ込んでいるようです。ちなみのこのデータの中身は、次のような感じのJSONになっていました。
[
{type: "function", name: "restore", args: []},
{type: "function", name: "fill", args: ["nonzero"]},
{type: "field", name: "fillStyle", value: "rgba(0%,0%,0%,0.3)"},
...
2. elm-canvas
というCustom Elementsを描画する
toHtml : ( Int, Int ) -> List (Attribute msg) -> List Renderable -> Html msg
toHtml ( w, h ) attrs entities =
Html.node "elm-canvas"
[ commands (render entities) ]
[ canvas (height h :: width w :: attrs) []
]
1で定義したcommands
を使って、そのelm-canvas
のcmds
プロパティにデータを突っ込んでいるわけです。ついでに、その内側に実際の描画先となるcanvas
要素も作ってあります。
3. そのCustom Elementsの定義のset
アクセサで、Elm側から送られたデータを受け取る
customElements.define(
"elm-canvas",
class extends HTMLElement {
...
set cmds(values) {
this.commands = values;
this.render();
}
...
}
);
JavaScript側のelm-canvas
要素の定義で、cmds
のset
アクセサを用意して、そこでデータを受け取ります。あとは受け取ったデータを元にCanvasの描画を行うだけです。
まとめ
というわけで、Custom Elementsのプロパティ越しにデータを送れば、elm-canvas.js
というスクリプトを別に読み込む必要はあるものの、ネイティブモジュールもportも使わずに、Elmのパッケージから外部のAPIを叩くことができるということがわかりました。このやり方ではJavaScript側からElm側へデータを送ることはできないので、measureText
のような機能は実現できないという制限はあるでしょうが、簡単な描画であればこれで十分でしょう。
……闇の魔術じゃん!
追記
……と思いきや、akira_さんから カスタムイベントでJSからElmへデータを送る方法が提案され、ElmからJSだけでなくJSからElmへとデータを送ることも問題なくできることがわかりました。portガン無視でデータを送りまくれるぞ! 悪の枢軸ッ!