1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[ネタ] divアートプログラミング

Posted at

HTMLのdivだけで絵?を描くというネタプログラミングの記事です。
数行のHTMLとJSだけで出来るので、実際に試してみると面白いかも?

サンプル
download.gif

大量のdivを用意して1個1個のdivの背景色を変えて表現しています。

一般的に、こういうのはWebGLであったり、シェーダーといわれる分野かとは思いますが
あえてdivを駆使ししてやってみるのもそれはそれで面白いかなと思った次第です。

概要

image.png

一見するとただの黒い四角ですが、1辺が10pxのdivを縦横に10個ずつ並べています。
divの背景色を黒に指定しているので1つの四角に見えますが、実際は100個のdivがあります。

image.png

こちらは1つ1つのdivの背景色を変えています。
とりあえずこの四角を描くまで行ってみたいと思います。

divart.js

このネタプログラミングでは大量のdivを塗りやすくするために
divart.jsというライブラリもどき(100行もない)を作ったので、それを使っています。

(divを塗るための3つのメソッドclearpixeldrawがあるだけの簡素なものです)
ソースは記事の最後の方に載せておくので実際に書いて動かしたい場合はコピってください。

image.png

ひとまずこの黒い四角は以下のコードで描くことができます。

<!DOCTYPE html>
<html>
<head>
  <title>div art</title>
  <script src="divart.js"></script>
</head>
<body>
  <div id="canvas"></div>
  <script>
    const da = new DivArt("canvas", 10, 10);
    da.clear([0, 0, 0]);
  </script>
</body>
</html>

準備
まず最初に、divart.jsを読み込んで、bodyタグ内に適当なdivを作ります。
今回は<div id="canvas"></div>というのがそれになります。
あとはscriptタグの中に処理を書いていきます。

スクリプトの解説

const da = new DivArt("canvas", 10, 10);

DivArtクラスをnewすることでdivで絵を描く準備が整います。

第一引数:準備で用意したdivのidを指定します。
第二引数:divの数を指定します。10を指定すると10x10で100個のdivが用意されます。
第三引数:1つ1つのdivのサイズを指定します。10を指定すると縦横が10pxのdivになります。

da.clear([0, 0, 0]);

DivArtには3つのメソッドが用意されており、その1つがこのclearです。

clearは全てのdivを引数で指定した色で塗ります。
色はRGBの3色を配列で指定するので、黒の場合は[0, 0, 0]になります。

一般的に色は0~255で指定する事が多いですが、めんどくさいので0.0 ~ 1.0で指定できるようにしています。

例えば、赤い四角にする場合であれば以下のようになります。

const da = new DivArt("canvas", 10, 10);
da.clear([1, 0, 0]);

赤い四角がでます。
image.png

指定したdivの色を塗る

特定のdivを指定して色を塗るために、pixel(index, color)というメソッドを用意しています。
引数の意味は以下の通りです。

index
divの位置を示す番号を指定します。
indexは以下のように左上から右下に向かって増加します。(3x3の例)

0 1 2
3 4 5
6 7 8

color
RGBを配列で指定します。clearメソッドと同じです。

上下で色が違う四角を描くサンプルです。

image.png

const da = new DivArt("canvas", 10, 10);
    
// 上半分
for(let i = 0; i < 50; ++i) {
  da.pixel(i, [1, 0, 1]);
}

// 下半分
for(let i = 50; i < 100; ++i) {
  da.pixel(i, [0, 1, 1]);
}

RGBを繰り返す

赤緑青を繰り返すサンプルです。

image.png

const da = new DivArt("canvas", 10, 10);

for(let i = 0; i < 100; ++i) 
{
  switch((i%3)) {
    case 0 : da.pixel(i, [1, 0, 0]); break;
    case 1 : da.pixel(i, [0, 1, 0]); break;
    case 2 : da.pixel(i, [0, 0, 1]); break;
  }
}

iを3で割った余りは0, 1, 2, ...を繰り返すので、それで分岐しています。
指定したdivの色は自由に塗れればドット絵が描けるので、頑張って見るのも面白いかもしれません。

色を決める関数を渡して塗る

draw(func)メソッドを使って色を塗る方法です。
とりあえず以下のコードを見て下さい。

const da = new DivArt("canvas", 10, 10);

da.draw((x, y) => {
  return [1, 0, 0];
});

これで以下の赤い四角が描かれます。

image.png

このdrawメソッドは引数に色を返す関数を渡します。

指定した関数はdivの数だけ実行され、divは指定した関数が返した色で塗られます。(divが100個なら100回)
上記のサンプルではreturn [1, 0, 0];と関数が赤色を返しているので、赤で塗られる事になります。

また指定した関数の引数には、今塗ろうとしているdivのxy座標が渡されます。

この座標は中心あたりを(0, 0)として
-1.0 <= x <= 1.0
-1.0 <= y <= 1.0
の範囲の数値になっています。

一番左上であれば(-1.0, 1.0)、右下であれば(1.0, -1.0)という具合です。

xy座標で色を決める

image.png

コードはコチラになります

const da = new DivArt("canvas", 10, 10);

da.draw((x, y) => {
  return [Math.abs(x), 0, Math.abs(y)];
});

RGBのRにMath.abs(x)を指定しています、xは-1.0 ~ 1.0なので絶対値でマイナスにならないようにしています。
RGBのBはMath.abs(y)を指定しています、xと同様にマイナスにならないように絶対値を付けています。

こんな感じでdivの色を、今塗ろうとしているdivのxy座標を使って決めています。

色をアニメーションしてみる

ダウンロード.gif

const da = new DivArt("canvas", 10, 10);

let timer = 0;

setInterval(() => {
  da.draw((x, y) => {
    return [Math.abs(x), Math.abs(Math.sin(timer)), Math.abs(y)];
  });
  timer += 0.033;
}, 33);

setIntervalなどを使う事で色をアニメーションさせるとまた面白い効果が得られます。

RGBのRとBは先ほどと同じですが、Gの色を時間で変化させています。

timer変数は時間がたつほど値が増えていきます
Math.sin(timer)とすると、sinは-1から1をいったりきたりするので
Gが-1.0~1.0の間をいったり来たりする事になりますが、マイナスにならないように絶対値を付けています。

数学の方程式で塗り分けてみる

image.png

divの数を25x25に増やしてみました。

斜めで白黒を分けてみます。
このように斜めの直線というのは数学だと$y = x$で表されたりします。

これをちょっと移項して$y - x = 0$としてあげると、$y - x$が0と等しければその座標は直線上にあるとも言えます。

また$y - x < 0$とすれば、(x, y)でxの方が大きいと結果はマイナスになります。
これを利用すると直線の上側と下側という判定ができたりします。

const da = new DivArt("canvas", 25, 10);

da.draw((x, y) => {
  return (y - x < 0)? [0, 0, 0] : [1, 1, 1];
});

2次関数でやってみる

$y = x^2$という2次関数は放物線になりますが、実際それっぽくなりますね。

image.png

const da = new DivArt("canvas", 25, 10);

da.draw((x, y) => {
  return (y - (x*x) < 0)? [0, 0, 0] : [1, 1, 1];
});

まとめ

ここまでいくつかやってきましたが、沢山の四角があって、その四角の色を自由に指定できるということは
理論的にはモニタで写せる映像であればどんなものでも表現可能という事です。

やろうと思えば3DCGで描かれるような映像を、大量のdivで表現することも
まぁそれはさすがに重すぎてまともに動かないかもですが

工夫次第ではかなりいろいろな表現が可能ですし、ドット絵の昔ながらのゲームであれば
これでも充分に作る事はできそうですね。

ネタプログラミングではありますが、WebGLなどの技術もようはこの記事でやったことの発展だったりしますので
興味があれば3DCGや数学なども学んでみるともっといろんな表現が出来るようになるかもしれません。

というわけでネタでやったけど結構面白かったので記事書きました。

ではでは、最後にこの記事で使っていたdivart.jsのコードを貼りつけておきます。

divart.js

class DivArt 
{
  /**
   * DivArtのための初期処理、必要なスタイルと大量のdivを生成する。
   * @param {*} id 描画先のdiv 
   * @param {*} cw 1行あたりのdivの数を指定する、10を指定すれば10x10で100個のdivが作られる。
   * @param {*} ds 1つ1つのdivのサイズをpxで指定する、10を指定すれば幅、高さが10pxのdivになる。
   */
  constructor(id, cw, ds) {

    this.node = document.getElementById(id); // メインの要素を取得
    this.cw = cw;                            // canvasのサイズ(1行あたりのdivの数)
    this.ds = ds;                            // divのサイズ
    this.size = cw * cw;                     // divの総数
    this.vram = [];                          // vramといいつつ、ただのdivの配列

    // 指定されたパラメータから必要なstyleタグを作ってヘッダにぶちこむ
    this._initStyle(id);

    // 指定されたパラメータから必要な数だけdivを作る
    this._initVram();
  }

  _initStyle(id) 
  {
    const { cw, ds } = this;
    const styleTag = document.createElement('style');

    styleTag.innerText = `
    #${id} {
      display: flex;
      width: ${cw * ds}px;
      flex-wrap:wrap;
    }

    #${id} div {
      width: ${ds}px;
      height: ${ds}px;
    }    
    `;
    document.getElementsByTagName('head')[0].insertAdjacentElement('beforeend', styleTag);

  }

  _initVram() 
  {
    for(let i = 0; i < this.size; ++i) {
      this.vram[i] = document.createElement("div");
      this.node.appendChild(this.vram[i]);
    }
  }

  /**
   * 指定した色で塗りつぶす
   * @param {number[]}} c 色
   */
  clear(c = [1, 1, 1]) {
    for(let i = 0; i < this.size; ++i) {
      this.pixel(i, c);
    }
  }

  /**
   * indexで指定したdivを指定した色で塗る
   * @param {number} index vramの要素を指定
   * @param {number[]} c 塗る色を指定、色はrgbの順で配列で指定
   */
  pixel(index, c) {
    this.vram[index].style = `background-color:rgb(${c[0]*255}, ${c[1]*255}, ${c[2]*255})`;
  }

  /**
   * divの色を決定するfrag関数を指定して描画する、フラグメントシェーダーもどき
   * @param {*} frag 色を返す関数
   * @returns number[] rgbを配列で返す[r, g, b]
   */
  draw(frag) 
  {
    if (!frag) return;

    const { cw } = this;
    const half = cw / 2;

    for(let i = 0; i < this.size; ++i) {

      // iをx,y座標に変換し、中心から-1.0 ~ 1.0になるように計算
      let x = (i % cw) / half - 1.0;
      let y = (i / cw) / half - 1.0;

      this.pixel(i, frag(x, -y));
    }
  }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?