19
20

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 5 years have passed since last update.

Canvasで漫画にあるような "ざわ・・・" を書いてみた

Last updated at Posted at 2015-10-01

#はじめに
前回Canvasで漫画にあるような集中線を書いてみたという記事を書きました。

漫画シリーズ第二弾として、私の好きな漫画でよく出てくるようなオノマトペをCanvasで再現してみました。「ざわざわ」させたい時には役に立つかもしれません。

[動作イメージ]
※Gifアニメが重くなってしまったので、静止画を使っています。本当は動きます。

zawazawa1.png

zawazawa2.png

なお実際の動作は以下で確認することができます
http://nekoneko-wanwan.github.io/demo/canvas/effect/zawazawa/
http://codepen.io/nekoneko-wanwan/pen/XmpxoB

#特長

  • Canvasサイズに依存しない
  • **ざわ・・・**の大きさと位置は一定範囲内でランダム
  • **ざわ・・・**の小さいものはゆっくり、大きい物は早くアニメーションする(奥行きを表現)
  • **ざわ・・・**の数が変更できる
  • **ざわ・・・**には4つのパターンを用意
    • 黒文字
    • 白文字
    • 黒文字・白フチ
    • 白文字・黒フチ
  • **ざわ・・・**の流れる方向が左右で変更できる ←[NEW!]

**ざわ・・・**の大きさはスクリプト内で設定しているため、変更する場合は少し手を加える必要があります

#ソースコード

index.html
<canvas id="myCanvas" width="500" height="500"></canvas>
canvas.js
/**
 * ざわ・・・メーカー
 * @param {obj} cs: canvas object
 * @param {number} zawaNum: 生成する「ざわ・・・」の数
 * @param {boolean} isBordering: 縁取りするかどうか
 *     黒文字の場合は白フチに、白文字の場合は黒フチに
 * @param {boolean} isColorBlack: 黒文字にするかどうか(falseで白文字)
 * @param {boolean} isRightFlow: 右から流れるようにするか
 */
var zawaMaker = function(cs, zawaNum, isBordering, isColorBlack, isRightFlow ) {
    var ctx      = cs.getContext('2d');
    var csWidth  = cs.width;
    var csHeight = cs.height;
    var zawas    = [];

    /**
     * ランダムな整数を返す
     * @param max 最大値
     * @param min 最小値
     * @return min ~ max
     */
    var getRandomInt = function(max, min) {
        return Math.floor(Math.random() * (max - min)) + min;
    };

    /**
     * @constructor
     */
    var ZawaZawa = function() {
        this.initialize();
    };

    ZawaZawa.prototype = {
        initialize: function() {
            this.scale     = getRandomInt(10, 5) / 10; // 1 ~ 0.5の倍率
            this.width     = this.scale * 160;  // 元サイズを大体150pxくらいと規定
            this.height    = this.scale * 60;   // 元サイズを大体60pxくらいと規定
            this.moveX     = getRandomInt(csWidth  - this.width, 0);
            this.moveY     = getRandomInt(csHeight - this.height, 0);
            this.addMoveX  = this.scale * 5;  // 奥行きを出すために、小さい要素は移動量を少なくする
            this.alpha     = 1;
        },
        setStatus: function() {
            /* 変換マトリクスを初期化 */
            ctx.setTransform(1, 0, 0, 1, 0, 0);

            ctx.scale(this.scale, this.scale);
            ctx.translate(this.moveX, this.moveY);
            ctx.globalAlpha = this.alpha;
        },
        update: function() {
            /* scaleで座標軸がズレているので、scaleした分を割った値をCanvasサイズとする */
            var _csWidth = csWidth / this.scale;

            /* 流れる向きの条件分岐 */
            if (isRightFlow) {
                if (this.moveX + this.width + 50 < 0 ) {
                    this.alpha = 0;
                    this.moveX = _csWidth + this.width;

                    /* scaleを変えるため再設定 */
                    this.scale    = getRandomInt(10, 5) / 10;
                    this.width    = this.scale * 160;
                    this.height   = this.scale * 60;
                    this.moveY    = getRandomInt(csHeight - this.height, 0);
                    this.addMoveX = this.scale * 5;
                } else {
                    this.moveX -= this.addMoveX;
                    this.alpha += 0.01;
                }
            } else {
                if (this.moveX >_csWidth) {
                    this.alpha = 0;
                    this.moveX = - this.width;

                    /* scaleを変えるため再設定 */
                    this.scale    = getRandomInt(10, 5) / 10;
                    this.width    = this.scale * 160;
                    this.height   = this.scale * 60;
                    this.moveY    = getRandomInt(csHeight - this.height, 0);
                    this.addMoveX = this.scale * 5;
                } else {
                    this.moveX += this.addMoveX;
                    this.alpha += 0.01;
                }
            }

            /* 透明度が1以上の場合は透明度の増加を止める */
            if (this.alpha > 1) {
                this.alpha = 1;
            }
        },
        draw: function() {
            /**
             * @param {number} width: 線の太さ
             * @param {string} color: 線の色
             */
            function createZawaPath(width, color) {
                ctx.lineWidth = width;
                ctx.strokeStyle = color;
                ctx.beginPath();
                ctx.lineCap = "round";
                ctx.lineJoin = "round";

                // ざ
                ctx.moveTo(10, 45);
                ctx.lineTo(50, 45);

                ctx.moveTo(25, 35);
                ctx.lineTo(50, 60);
                ctx.lineTo(20, 60);
                ctx.bezierCurveTo(5, 60, 5, 75, 20, 75);
                ctx.lineTo(50, 75);

                ctx.moveTo(38, 30);
                ctx.lineTo(38, 36);

                ctx.moveTo(46, 30);
                ctx.lineTo(46, 36);

                // わ
                ctx.moveTo(70, 35);
                ctx.lineTo(70, 75);

                ctx.moveTo(60, 45);
                ctx.lineTo(80, 45);
                ctx.lineTo(60, 75);
                ctx.bezierCurveTo(80, 25, 120, 70, 85, 75);

                // ・・・
                ctx.moveTo(110, 60);
                ctx.lineTo(115, 60);

                ctx.moveTo(125, 60);
                ctx.lineTo(130, 60);

                ctx.moveTo(140, 60);
                ctx.lineTo(145, 60);

                ctx.closePath();
                ctx.stroke();
            }

            /* 縁取と黒・白文字の条件分岐 */
            if (isBordering && isColorBlack) {
                createZawaPath(15, 'white');
                createZawaPath(6, 'black');
            }
            if (isBordering && !isColorBlack) {
                createZawaPath(15, 'black');
                createZawaPath(6, 'white');
            }
            if (!isBordering && isColorBlack) {
                createZawaPath(6, 'black');
            }
            if (!isBordering && !isColorBlack) {
                createZawaPath(6, 'white');
            }

        },
        render: function() {
            this.setStatus();
            this.draw();
            this.update();
        }
    };


    /**
     * ざわインスタンスの作成
     * @return zawas[instance, instance...];
     */
    function createZawas(num) {
        var i = 0;
        for (; i < num; i++) {
            zawas[zawas.length] = new ZawaZawa();
        }
    }

   /**
     * 描画
     */
    function render() {
        var i = 0;
        var l = zawas.length;
        ctx.setTransform(1,0,0,1,0,0);
        ctx.clearRect(0, 0, csWidth, csHeight);
        for (; i < l; i++) {
            zawas[i].render();
        }
        requestAnimationFrame(render);
    }

    /* 実行 */
    createZawas(zawaNum);
    render();
};

var cs = document.getElementById('myCanvas1');

/* args: canvasオブジェクト, ざわの数, 縁取りか, 黒文字か, 右から流すか */
zawaMaker(cs, 10, false, true, false);

#作り方・考え方

  1. Canvas上で **ざわ・・・**の文字を表現する
  2. **ざわ・・・**の大きさと位置をランダムに設定する
  3. 一定時間ごとに左→右へループさせる

##Canvas上で ざわ・・・の文字を表現する
この程度であればstroke()でも再現できるため、画像やSVGは使っていません。moveTo()
**lineTo()**で線を細く引いていきます。

結果的に画像を使った方が動作が軽くなるかもしれませんが・・・

##ざわ・・・の大きさと位置をランダムに設定する
1.のように作成したため、座標位置や大きさはscale()
**translate()**で表現しています。

上記メソッドを使うと座標軸が変更されるため、再描画の際には座標軸のリセットやCanvasの実サイズに注意します

##一定時間ごとに左or右へループさせる
どちらへ流れるかは引数で設定できるようにしました。座標軸のズレに気をつけていれば、特に難しいことは無いと思います。

#終わりに
作り方は自分で考えてみたので、細かいところが微妙だったり非効率な可能性があります。もっと良い方法があればご教授くださいませ。

大きさによってスピードを変えることで視差効果を付けたのは良いものの、縁取りした時の文字の重なり順が・・・。x(

[おまけ]
super_zawazawa.png

Canvasで漫画にあるような集中線を書いてみたと組み合わせて、すごくうるさい画像を作成してみました(Gifアニメが大変重たいので静止画です)

http://nekoneko-wanwan.github.io/demo/canvas/effect/zawazawa2/

19
20
2

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
19
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?