アイコンWeb制作実験

HTML/CSS/jQuery/Fabric.js を試した様子と結果です.

canvasをレイヤー化して選択層のオブジェクトのみを操作

イメージ

公開:

HTML5のcanvasをレイヤー(多層)化する方法です。レイヤーとは、透明なシートを何重にも重ねたイメージで、人物と背景のように特定の層のみを動かしたり消したりなどが可能になります。

Fabric.jsというJavaScriptライブラリを使用し、選択レイヤーの要素(オブジェクト)のみを操作できるようにします。

canvasをレイヤー(多層)化させる方法

canvasをdivで囲い、absoluteで絶対配置します。後に説明するFabric.jsを使わなければこれだけで完了です。

<div class="layer-wrap">
  <canvas id="cvs1" width="300" height="240"></canvas>
  <canvas id="cvs2" width="300" height="240"></canvas>
</div>
.layer-wrap	{
  width: 300px;
  height: 240px;
  position: relative;
}

.layer-wrap > canvas	{
  position: absolute;
}

#cvs1	{
  border-right: 20px solid orangered;
}

#cvs2	{
  border-left: 20px solid royalblue;
}

CSS 11~17行目のborderは重なっていることを視認するためです。

レイヤー化したcanvasにFabric.jsを組む

上記にFabric.jsという図形などをオブジェクト化するJavaScriptライブラリを組み入れます。

HTMLは先程と同じで、CSSの赤と青のborderは以後消しています。

var	canvas1 = new fabric.Canvas('cvs1'),
    canvas2 = new fabric.Canvas('cvs2');

//レイヤー1
canvas1.add(new fabric.Text('1', {
  fontFamily: 'Meyrio',
  fontSize: 72,
  fill: 'blue',
  textBackgroundColor: 'lightblue',
  left: 80,
  top: 60
}));

//レイヤー2
canvas2.add(new fabric.Text('2', {
  fontFamily: 'Meyrio',
  fontSize: 72,
  fill: 'blue',
  textBackgroundColor: 'lightblue',
  left: 160,
  top: 100
}));

すると2つのcanvasの重なりがなくなって縦並びになってしまいました。absoluteが利いていません。

開発ツール(F12)でインスペクターを見ると、各canvasが.lower-canvasと.upper-canvasの2枚になり、更にdiv.canvas-containerでラップされていました。

他にstyleがいくつかかかり、canvas全てposition: relativeのままです。

Fabric.jsは元のcanvasの構造をかなり変えています。おおまかにこんな感じです。

<div class="layer-wrap">
  <div class="canvas-container">
    <canvas class="lower-canvas" id="cvs1"></canvas>
    <canvas class="upper-canvas"></canvas>
  </div>
  <div class="canvas-container">
    <canvas class="lower-canvas" id="cvs2"></canvas>
    <canvas class="upper-canvas"></canvas>
  </div>
</div>

そこでCSSを変更。

.layer-wrap > div.canvas-container	{
  position: absolute !important;
}

これで重なりました。!importantが必用です。

ところがユーザーは、レイヤー2しか操作できず、レイヤー1が操作できません。(プログラム上のコードからはどちらも操作できます。)

開発ツールを見ると、各canvasにイベントがかけられていますが、これはさすがにいじれません。

canvasを3層、4層と増やしてみると、どうやら最後のレイヤー(canvas)のみが反応します

選択レイヤーを動的に入れ替える

どのレイヤーを操作するか切り替えるようにします。

レイヤー切替

覆っているdivにidを付け、操作するレイヤーをappend で親要素内の最後に動的に移動させています。

<div class="layer-wrap" id="demo3">
  <canvas id="cvs1" width="300" height="240"></canvas>
  <canvas id="cvs2" width="300" height="240"></canvas>
  <canvas id="cvs3" width="300" height="240"></canvas>
</div>

<label><input type="radio" name="mode" value="1">レイヤー1</label>
<label><input type="radio" name="mode" value="2">レイヤー2</label>
<label><input type="radio" name="mode" value="3" checked>レイヤー3</label>
var numLayers = $('#demo3').children('canvas').length,
    aryCvs = [];

$('[name=mode][value=' + numLayers + ']').prop('checked', true);

for (var i = 1; i <= numLayers; i++) {
  //各レイヤーの初期化とid付加
  aryCvs[i - 1] = new fabric.Canvas('cvs' + i);
  $('#cvs' + i).parent('div.canvas-container').attr('id', 'layer' + i);

  //各レイヤーに要素生成
  aryCvs[i - 1].add(new fabric.Text(
    i + '', {
    fontFamily: 'Meyrio',
    fontSize: 72,
    fill: 'blue',
    textBackgroundColor: 'lightblue',
    left: 80 * i - 20,
    top: 50 * i - 20
  }));
}

//----- レイヤー切替 -----
$('[name=mode]').click(function(){
  //現在=最後のレイヤー番号取得
  var n = $('#demo3').children('div.canvas-container').last().attr('id').slice(-1);

  //現在のレイヤーの選択枠解除
  aryCvs[n - 1].discardActiveObject().renderAll();

  //選択レイヤーを最後に移動
  $('#demo3').append($('#layer' + $('[name=mode]:checked').val()));
});

「現在のレイヤーの選択枠解除」とは、選択枠があるまま操作レイヤーを変えると、枠が残ってしまうためです。

ユーザーがどのレイヤーを操作するか選択し、そのレイヤーのみ操作させることはできました。

Fabric.jsの公式サイトにレイヤー化について現時点では例が見当たりません。上記の方法で少なくともここ1年は使えていますが、今後も可能とは限りません。当サイト独自の手法ですのでご了承願います。