離島プログラマの雑記

島根県の離島、隠岐・西ノ島に移住して子育て中のフリープログラマです。

ScratchブロックをHTMLで描画する方法

プログラミング教室の教材を作るために Scratch ブロックの画像が欲しかったんですが、いちいちスクリーンショットを撮るのも面倒だし、ラスタ画像ファイルだと解像度の問題で取り回しが不便なので、ベクタ画像出力かつ更新が簡単な方法を探していたらドンピシャのライブラリがありました。

ScratchのWikiやフォーラムでも使われている blob8108 氏作の Block Plugin(scratchblocks)です。このライブラリはブラウザ上で動作するブロック画像ジェネレータとしても公開されていますが、Webページに組み込めば Scratchスクリプトからブロック画像を生成してHTMLにSVGとして埋め込むことができます。

教材の製本も視野に入れると、画像編集ツールを介さずにHTMLで完結できればCSS組版とも相性が良さそうです。

使い方

ライブラリのインクルード

現在の最新バージョンはver3.1なので、以下のJSファイルをインクルードします。

<script src="https://scratchblocks.github.io/js/scratchblocks-v3.1-min.js"></script>

ブロック表記を英語以外(今回は日本語)にするためにはさらに以下の翻訳データをインクルードします。

<script src="https://scratchblocks.github.io/js/translations-v3.1-min.js"></script>
描画

実際に描画してみたサンプルを GitHub Pages で公開しておきました。

https://ag-k.github.io/scratchblocks-html-sample-ja/

サンプルコード全体は以下で参照できます。

https://github.com/ag-k/scratchblocks-html-sample-ja/blob/master/index.html

以下は各描画方法の説明です。

ベーシックな描画方法

HTMLの要素としてScratchスクリプトを記述しておき、scratchblocks.renderMatching() で指定したセレクタに該当する要素をまとめて描画します。 指定するセレクタは自由ですが、preタグだとそのままスクリプトが書けるので便利です。

<pre id="blocks1" class="blocks">
  @greenFlag がクリックされたとき
  x座標を (0) 、y座標を (0) にする
  ずっと
    (10)回繰り返す
      @turnright (10) 度回す
      (5) 歩動かす
    end
    次のコスチュームにする
  end
</pre>
<pre id="blocks2" class="blocks">
  このスプライトがクリックされたとき
  もし <(向き) = [90]> なら
    ペンを下ろす
  でなければ
    ペンを上げる
  end
</pre>
// 'pre.blocks' セレクタで指定された要素をまとめて描画
scratchblocks.renderMatching('pre.blocks', {
  languages: ['ja', 'en'],
  render: function(doc, cb) {
    doc.render(function(svg) {
      var el = document.createElement('div');
      el.appendChild(svg);
      cb(el);
    });
  },
});
インラインで描画

scratchblocks.renderMatching() の第2引数 option に inline: true を指定するとインラインでブロックを描画できます。

scratchblocks.renderMatching() の第2引数 option に inline: true を指定すると、<code class=b>スタンプ</code>のようにインラインでブロックを描画できます。
// インラインで描画
scratchblocks.renderMatching("code.b", {
  languages: ['ja', 'en'],
  inline: true
});
サイズを拡大して描画

今のところブロックが描画サイズは固定になっているようですが、大きくして使いたい場面があったので、描画結果のサイズを変更する方法を考えました。ブロックはSVG形式で描画されるので、Document.render() から取得できるSVG要素を拡大してやれば良さそうなのですが、scratchblocks.renderMatching() に渡した場合、SVG要素のサイズが取得できませんでした(clientWidth, clientHeight, viewBox が0)。

一方、Document.render() を単体で使う場合は SVG要素のサイズが取得できたので、scratchblocks.parse() でスクリプトをパースしたオブジェクトを Document.render() でレンダリングし、結果のSVG要素を指定されたスケールに拡大するようにしました。

<code id="big_blocks">
  @greenFlag がクリックされたとき
  x座標を (0) 、y座標を (0) にする
  ずっと
    (10)回繰り返す
      @turnright (10) 度回す
      (5) 歩動かす
    end
    次のコスチュームにする
  end
</code>
<code id="middle_blocks">
  @greenFlag がクリックされたとき
  x座標を (0) 、y座標を (0) にする
  ずっと
    (10)回繰り返す
      @turnright (10) 度回す
      (5) 歩動かす
    end
    次のコスチュームにする
  end
</code>
<code id="small_blocks">
  @greenFlag がクリックされたとき
  x座標を (0) 、y座標を (0) にする
  ずっと
    (10)回繰り返す
      @turnright (10) 度回す
      (5) 歩動かす
    end
    次のコスチュームにする
  end
</code>
// スケールを変更する場合のブロック描画関数
//   renderMatching()で呼び出される doc.render()ではSVGのサイズが取得できないため、
//   parse() と doc.render() を使用して描画する。
var renderScaledBlocks = function(target, scale) {
  var doc = scratchblocks.parse(target.innerHTML, {
    languages: ['ja', 'en'],
  });
  doc.render(function(svg) {
    target.innerHTML = "";
    target.appendChild(svg);
    //SVGの拡大処理
    svg.setAttribute("width", svg.clientWidth * scale);
    svg.setAttribute("height", svg.clientHeight * scale);
    svg.setAttribute("viewBox", "0 0 " + svg.clientWidth / scale + " " + svg.clientHeight / scale );
  });
};

//各HTML要素に記述したスクリプトを元にブロックを描画
var titleBlocks = document.getElementById('big_blocks');
renderScaledBlocks(titleBlocks, 2.0);
var middleBlocks = document.getElementById('middle_blocks');
renderScaledBlocks(middleBlocks, 1.5);
var smallBlocks = document.getElementById('small_blocks');
renderScaledBlocks(smallBlocks, 1.0);

SVGの拡大方法については以下の記事を参照しました。

ASCII.jp:HTML5のInline SVGをJavaScriptで操作 (2/5)

[SVG] viewBoxについての考察 - Qiita

つまづきどころ

  • 画像を含むブロックの入力方法

    これらのブロックはブロック名に画像が含まれているので、文字列で表現する時はエイリアス文字列が設定されています。

    ブロック 日本語スクリプト
    f:id:k-aeg:20170227224919p:plain @greenflag がクリックされたとき
    f:id:k-aeg:20170227225645p:plain @turnright (15) 度回す
    f:id:k-aeg:20170227225720p:plain @turnleft (15) 度回す
  • 日本語に翻訳されていないブロックがある

    「() 回繰り返す」などのループは閉じるコマンド(英語版では「end」)が必要なんですが、日本語に翻訳されていないので、languages に"ja"と"en"の両方を指定した上で「end」と記述する必要があります。

おすすめ作業フロー

HTMLでスクリプト手書きは結構面倒なので、Scratch プロジェクトのブロックからscratchblockのスクリプトを生成するジェネレータを活用するとラクです。以下の手順で作業するのが良さそうです。

  1. Scratchでプロジェクトを作成
  2. ブロックを組んで動作確認
  3. generator :: scratchblocks でScratchプロジェクトをscratchblockスクリプトに変換
  4. scratchblockスクリプトをHTMLに貼り付け