🖼 Fabric.js の使い方メモ 2022/07/06 に公開 2024/03/30 JavaScript HTML 5 canvas fabric tech Canvas 内に画像やテキストを配置したりするのに便利な Fabric.js の自分用メモです。 インストール yarn add fabric いちばん簡単な表示の例 <template lang="pug"> canvas(ref="canvas_el" width="200" height="200") </template> <script> import { fabric } from "fabric" export default { mounted() { const canvas = new fabric.Canvas(this.$refs.canvas_el) const rect = new fabric. Rect({width: 50, height: 50, fill: "blue"}) canvas.add(rect) rect.center() </script> <style lang="sass"> canvas border: 1px dashed blue </style> Vue.js のオールドスタイルで書いてる 最初に fabric.Canvas のインスタンスを生成する それに対して他の要素を add していく center() は add してからでないと効かなかった (それはそう) ライブラリのお約束的なこと インタンスには生成時のオプションと同じアクセサがあるっぽい。なので次の2つは同じこと(だと勝手に思っている)。 const canvas = new fabric.Canvas(html_element, {selection: false}) const canvas = new fabric.Canvas(html_element) canvas.selection = false 背景画像の読み込み 方法1. とにかく読み込む const url = "https://dummyimage.com/600x400/008/FFF.png" canvas.setBackgroundImage(url, () => canvas.renderAll()) fabric.Image.fromURL で読み込んだものを canvas.setBackgroundImage に渡してもよい 第二引数の謎 マニュアルには canvas.renderAll.bind(canvas) と書いてある ググってもそればかりでてくる 意味がわからないので () => canvas.renderAll() に変えたら動いたのでよしとする renderAll() を呼ばないと表示されない toDataURL できない罠あり (後述) 方法2. 背景画像にキャンバスを合わせる const url = "https://dummyimage.com/600x400/008/FFF.png" canvas.setBackgroundImage(url, e => { canvas.setDimensions({width: e.width, height: e.height}) canvas.renderAll() キャンバスのサイズは変わるけど隙間はできない キャンバスが巨大化する恐れあり キャンバスのサイズはあとから変更できないと思ってたけど勘違いだったようだ 方法3. キャンバスに背景画像を合わせる const url = "https://dummyimage.com/600x400/008/FFF.png" fabric.Image.fromURL(url, e => { e.scaleToWidth(canvas.width) canvas.setBackgroundImage(e, () => canvas.renderAll()) 上は横幅をキャンバスに収める場合の例 縦幅をキャンバスに収める場合は e.scaleToHeight(canvas.height) とする toDataURL が失敗する問題 const url = "https://dummyimage.com/600x400/008/FFF.png" canvas.setBackgroundImage(url, e => { canvas.renderAll() const data_url = canvas.toDataURL() 上のように外部の画像を読み込むと toDataURL が失敗する 「キャンバスが汚染されている」というだけの不親切なエラーメッセージが出る 対策1. 要素に crossOrigin 指定 const url = "https://dummyimage.com/600x400/008/FFF.png" canvas.setBackgroundImage(url, e => { e.crossOrigin = "anonymous" canvas.renderAll() 対策2. setBackgroundImage のオプションに crossOrigin 指定 const url = "https://dummyimage.com/600x400/008/FFF.png" canvas.setBackgroundImage(url, e => { canvas.renderAll() }, { crossOrigin: "anonymous", 上のどちらもでも回避できた。が、似た対処方が複数あってなんともすっきりしない。 参考 🙏 </span><a href="https://blog.np-sys.com/tainted-canvases-may-not-be-exported/" style="" target="_blank" rel="nofollow noopener noreferrer">https://blog.np-sys.com/tainted-canvases-may-not-be-exported/</a></p> <a class="header-anchor-link" href="#originx-originy-%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%AA%E3%81%AE%E3%81%8B%EF%BC%9F" aria-hidden="true"/> originX originY とは何なのか?</h1> <li>公式サイトの<a href="http://fabricjs.com/test/misc/origin.html" target="_blank" rel="nofollow noopener noreferrer">サンプル</a>がわかりやすかった</li> <li>要素の座標は要素のどこを基点としているかを表わす</li> <li>組み合わせ的には多いが考慮するのは<strong>左上</strong>と<strong>中央</strong>の二つしかない</li> <a class="header-anchor-link" href="#%E8%A6%81%E7%B4%A0%E3%81%AE%E4%B8%AD%E5%A4%AE%E3%81%AE%E5%BA%A7%E6%A8%99%E3%82%92%E6%B1%82%E3%82%81%E3%82%8B%E3%81%AE%E3%81%AB%E7%84%A1%E9%A7%84%E3%81%AB%E3%81%8C%E3%82%93%E3%81%B0%E3%81%A3%E3%81%A6%E3%81%AF%E3%81%84%E3%81%91%E3%81%AA%E3%81%84" aria-hidden="true"/> 要素の中央の座標を求めるのに無駄にがんばってはいけない</h2> <p>仮に 100x100 のキャンバスの中央付近に 50x50 の要素が配置されているときの要素の <code>aCoords</code> はこんな感じになっている。</p> <span class="token literal-property property">tl</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">24.5</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">24.5</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">tr</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">75.5</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">24.5</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">br</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">75.5</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">75.5</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">bl</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">x</span><span class="token operator">:</span> <span class="token number">24.5</span><span class="token punctuation">,</span> <span class="token literal-property property">y</span><span class="token operator">:</span> <span class="token number">75.5</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> </code></pre></div><p><code>tl</code> は左上で <code>br</code> は右下なので X 座標の中心を求めるなら、</p> <li><code>tl.x + (br.x - tl.x) / 2</code></li> <li><code>24.5 + (75.5 - 24.5) / 2</code></li> <p>として 50 になる。同様に Y 座標も計算すると 50 になる。</p> <p>と、こんな感じでいかにも間違えそうな計算をしないでいいように originX と originY には center を指定しておく。</p> <div class="code-block-container"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">new</span> <span class="token class-name">fabric<span class="token punctuation">.</span>Rect</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">originX</span><span class="token operator">:</span> <span class="token string">"center"</span><span class="token punctuation">,</span> <span class="token literal-property property">originY</span><span class="token operator">:</span> <span class="token string">"center"</span><span class="token punctuation">,</span> </code></pre></div><p>すると <code>(left, top)</code> は最初から <code>(50, 50)</code> になっている。</p> <a class="header-anchor-link" href="#%E8%A6%8B%E3%81%88%E3%82%8B%E5%8C%96%E3%81%99%E3%82%8B" aria-hidden="true"/> 見える化する</h1> <div class="code-block-container"><pre class="language-javascript"><code class="language-javascript"><span class="token function">template</span><span class="token punctuation">(</span>v<span class="token operator">-</span><span class="token keyword control-flow">if</span><span class="token operator">=</span><span class="token string">"canvas"</span><span class="token punctuation">)</span> p <span class="token punctuation">{</span><span class="token punctuation">{</span>canvas<span class="token punctuation">.</span><span class="token method function property-access">getActiveObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">}</span> b<span class="token operator">-</span><span class="token function">table</span><span class="token punctuation">(</span><span class="token operator">:</span>data<span class="token operator">=</span><span class="token string">"canvas.getObjects()"</span><span class="token punctuation">)</span> <span class="token function">template</span><span class="token punctuation">(</span>slot<span class="token operator">-</span>scope<span class="token operator">=</span><span class="token string">"{row}"</span><span class="token punctuation">)</span> b<span class="token operator">-</span>table<span class="token operator">-</span><span class="token function">column</span><span class="token punctuation">(</span>label<span class="token operator">=</span><span class="token string">"type"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">{</span>row<span class="token punctuation">.</span><span class="token property-access">type</span><span class="token punctuation">}</span><span class="token punctuation">}</span> b<span class="token operator">-</span>table<span class="token operator">-</span><span class="token function">column</span><span class="token punctuation">(</span>label<span class="token operator">=</span><span class="token string">"x"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">{</span>row<span class="token punctuation">.</span><span class="token property-access">top</span><span class="token punctuation">}</span><span class="token punctuation">}</span> b<span class="token operator">-</span>table<span class="token operator">-</span><span class="token function">column</span><span class="token punctuation">(</span>label<span class="token operator">=</span><span class="token string">"y"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">{</span>row<span class="token punctuation">.</span><span class="token property-access">left</span><span class="token punctuation">}</span><span class="token punctuation">}</span> b<span class="token operator">-</span>table<span class="token operator">-</span><span class="token function">column</span><span class="token punctuation">(</span>label<span class="token operator">=</span><span class="token string">"w"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">{</span>row<span class="token punctuation">.</span><span class="token property-access">width</span><span class="token punctuation">}</span><span class="token punctuation">}</span> b<span class="token operator">-</span>table<span class="token operator">-</span><span class="token function">column</span><span class="token punctuation">(</span>label<span class="token operator">=</span><span class="token string">"h"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">{</span>row<span class="token punctuation">.</span><span class="token property-access">height</span><span class="token punctuation">}</span><span class="token punctuation">}</span> b<span class="token operator">-</span>table<span class="token operator">-</span><span class="token function">column</span><span class="token punctuation">(</span>label<span class="token operator">=</span><span class="token string">"raw"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">{</span>row<span class="token punctuation">}</span><span class="token punctuation">}</span> b<span class="token operator">-</span>table<span class="token operator">-</span>column button<span class="token punctuation">.</span><span class="token method function property-access">button</span><span class="token punctuation">(</span>@click<span class="token operator">=</span><span class="token string">"canvas.remove(row)"</span><span class="token punctuation">)</span> remove </code></pre></div><p>動作検証している環境が大昔の Vue.js + Buefy で何かをアップデートすると何かが壊れるという悲しい状況なので、かなり古めかしい書き方になってしまった。とりあえず、いろいろ表示しとかないと何がどうなっているのかさっぱりわからない。<code>getObjects()</code> で保持している要素の配列が返る。</p> <p>ただ、このままだと再描画されないのでマウス操作を変更のトリガーとして再描画する。</p> <div class="code-block-container"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">canvas</span><span class="token punctuation">.</span><span class="token method function property-access">on</span><span class="token punctuation">(</span><span class="token string">"mouse:move"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">$forceUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> </code></pre></div><p>もしくは <code>deep: true</code> で変更を監視して再描画する。</p> <div class="code-block-container"><pre class="language-javascript"><code class="language-javascript"><span class="token literal-property property">watch</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">canvas</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token function">handler</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token method function property-access">$forceUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">deep</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <a class="header-anchor-link" href="#%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B" aria-hidden="true"/> テキストを配置する</h1> <div class="code-block-container"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> element <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">fabric<span class="token punctuation">.</span>Text</span><span class="token punctuation">(</span><span class="token string">"こんにちは"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">originX</span><span class="token operator">:</span> <span class="token string">"center"</span><span class="token punctuation">,</span> <span class="token literal-property property">originY</span><span class="token operator">:</span> <span class="token string">"center"</span><span class="token punctuation">,</span> <span class="token literal-property property">fill</span><span class="token operator">:</span> <span class="token string">"black"</span><span class="token punctuation">,</span> <span class="token literal-property property">fontSize</span><span class="token operator">:</span> <span class="token number">64</span><span class="token punctuation">,</span> <span class="token literal-property property">fontFamily</span><span class="token operator">:</span> <span class="token string">"Ariel"</span><span class="token punctuation">,</span> canvas<span class="token punctuation">.</span><span class="token method function property-access">add</span><span class="token punctuation">(</span>element<span class="token punctuation">)</span> element<span class="token punctuation">.</span><span class="token method function property-access">center</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <code>origin(X|Y)</code> で「こんにちは」の「に」の中心を座標の基点とする(重要)</li> <li>背景画像の上に配置する場合は読みやすいようにこれを追加して縁取る</li> <div class="code-block-container"><pre class="language-javascript"><code class="language-javascript"><span class="token literal-property property">stroke</span><span class="token operator">:</span> <span class="token string">"white"</span><span class="token punctuation">,</span> <span class="token comment">// 縁取り色</span> <span class="token literal-property property">strokeWidth</span><span class="token operator">:</span> <span class="token number">1.0</span><span class="token punctuation">,</span> <span class="token comment">// 縁取り幅</span> <a class="header-anchor-link" href="#%E3%82%88%E3%81%8F%E4%BD%BF%E3%81%86%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89" aria-hidden="true"/> よく使うメソッド</h1> <p>Canvas</p>