HTML
JavaScript
canvas

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

More than 3 years have passed since last update.


はじめに

前回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/