素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: twitter: @javascripter にどうぞ。

Web Inspector/Chromeのデバッガで動画を再生する

普段、Web Inspectorはデバッガとして使っていると思うが、Web Inspectorは実は様々な物を表示できる。

例えば、コンソールでライフゲームを表示することができる。 Web Inspector Life Gameに、コードを置いた。タイミングによっては上手く動かないので、コンソール上でCmd+Rなどを押してリロードすると上手くいく。

以下に、実装に使ったハックや技術を紹介する

console.logで色付き文字を表示する

まず、console.warnやconsole.infoなどを使うと、黒字以外の文字を表示できることを思い出してほしい。console画面は、HTMLで実装されているのである。

console.log("%s", "aa");

のように、printf形式で文字を表示できる機能があることを知っているだろうか。 この中に、色指定をするためのものがある。%cである。

console.log("%chello", "color: red;");

とすると、

hello

と表示される。

CSSを悪用する

同様に、 console.logの%cには、 font-sizeやline-height、margin, backgroundなどが使用できる。

console.log("%c.", "font-size: 100px; border: 1px solid black;");

とすると、大きな文字と、ボーダーの枠が表示される。では、画像表示はできるだろうか?

console.log("%c.", "font-size: 10em; background-image: url('http://example.com/image.jpg')");

なんとできるのである。あとは、想像の通り、CSSを微調整すれば背景画像だけを表示することができる。

アニメーション

アニメーションに必要なのは、リフレッシュ、つまり次の画像を、前と同じ位置に表示することである。 これはどうするかというと、

console.clear()

を使用する。

しかし、ここで大きな問題がある。 console関数は全て非同期に動作するのだ。 すなわち、

console.clear();console.log('a');

とすると、aが表示される保障はない。

今回のハックでは、console.clear()のタイミングを20ms程度、console.logより前にずらすことで、clear()がlog()より前に呼ばれることをある程度保障している。

アニメーション自体は、canvasを使用した。toDataURL()を使用して得た画像を、background-imageとしてセットしている。 念のため以下に、全コードを載せておく。

<script>
var TheGameOfLife = function (options) {
  this.width = options.width + 1;
  this.height = options.height + 1;
  this.firstLives = options.firstLives;
  this.cells = this.clearCells([]);
  this.afterUpdates = [];
  this.randomize();
};

TheGameOfLife.prototype = {
  clearCells: function (cells) {
    var i, j;
    for (i = 0; i <= this.width; i++) {
      cells[i] = [];
      for (j = 0; j <= this.height; j++) {
        cells[i][j] = 0;
      }
    }
    return cells;
  },
  update: function () {
    var i, j, n;
    var newCells = this.clearCells([]);
    for (i = 1; i < this.width; i++) {
      for (j = 1; j < this.height; j++) {
        n = 0;
        n += this.cells[i - 1][j - 1];
        n += this.cells[i - 1][j];
        n += this.cells[i - 1][j + 1];
        n += this.cells[i][j - 1];
        n += this.cells[i][j + 1];
        n += this.cells[i + 1][j - 1];
        n += this.cells[i + 1][j];
        n += this.cells[i + 1][j + 1];
        if (this.cells[i][j] == 0 ? n == 3 : (n == 2 || n == 3)) {
          newCells[i][j] = 1;
          } else {
          newCells[i][j] = 0;
        }
      }
    }
    this.cells = newCells;
    for (i = 0; i < this.afterUpdates.length; i++) {
      this.afterUpdates[i]();
    }
  },
  afterUpdate: function (hook) {
    this.afterUpdates.push(hook);
  },
  randomize: function () {
    var remains = this.firstLives;
    var i, j;
    while (remains--) {
      i = Math.floor(Math.random() * (this.width - 1) * (this.height - 1));
      j = i % (this.height - 1);
      i = (i - j) / (this.height - 1);
      i++;
      j++;
      this.cells[i][j] = 1;
    }
  }
};

document.addEventListener("DOMContentLoaded", function () {
  var width = 16;
  var height = 16;
  function reproduce() {
    var url = canvas.toDataURL();




console.log('Reload to reset: %c.', 'font-size: 0; padding: 150px; background-image: url(' + url + '); background-repeat: no-repeat;');

  }

  function render () {
    var wScale = canvas.width / width;
    var hScale = canvas.height / height;
    context.clearRect(0, 0, canvas.width, canvas.height);
    var i, j;
    for (i = 1; i < tgol.width; i++) {
      for (j = 1; j < tgol.height; j++) {
        if (tgol.cells[i][j] & 1) {
          context.fillRect(i * wScale, j * hScale, wScale, hScale);
        }
      }
    }
  }

  var canvas = document.getElementById("canvas");
  var context = canvas.getContext("2d");
  var options = {
    width: width,
    height: height,
    firstLives: 50,
    canvas: canvas
  };
  var tgol = new TheGameOfLife(options);
  tgol.afterUpdate(function () {
    render();
    reproduce();
  });
  render();
  setInterval(function () {
    tgol.update();
  }, 200);
  document.addEventListener("click", function () {
    tgol.clearCells(tgol.cells);
    tgol.randomize();
  }, false);
}, false);

setTimeout(function () {
  setInterval(function (){ console.clear();}, 200);
}, 180);

</script>
<canvas id="canvas" width="300" height="300" style="display: none;"></canvas>