はじめに
Canvasを利用して、懐かしのビックリマンシールのようなエフェクトを表現してみました。きらきらキレイですね。Gifアニメは重たくアップできなかったため静止画にしましたが、地味にアニメーションして光ります。
なお実際の動作は以下で確認することができます。
http://nekoneko-wanwan.github.io/demo/canvas/effect/bikkuri/
特長・仕様
- Canvasサイズに依存しない
- 1マスのサイズは可変
- アニメーションの色は柔軟に変更可能
- ブレンドモードを変えることで、色々な表現を実現
動作イメージは同じ色に対し、ブレンドモードをスクリーン、乗算と変えただけです。
ソースコード
index.html
<!-- 動作イメージの「テキスト・写真」はabsoluteで重ねているだけなので省略 -->
<canvas id="myCanvas" width="400" height="400"></canvas>
canvas.js
/* 変数定義 */
var cs = document.getElementById('myCanvas');
var ctx = cs.getContext('2d');
var csWidth = cs.width;
var csHeight = cs.height;
/**
* 1マスあたりのサイズを指定
* 割り切れる、かつ割る値はある程度大きくないと汚くなるので注意
*/
var ptnW = csWidth / 10;
var ptnH = csHeight / 10;
/* パターン画像のsrcを格納 */
var ptnSrc;
/**
* パターンとなる1マスの円錐型グラデーション画像を作成
* 生成した画像はdataURLとして返す
* @return dataURL
*/
var createPtnSrc = function() {
/* Canvasサイズを1マスサイズに縮小し操作していく */
cs.width = ptnW;
cs.height = ptnH;
var isReverse = false;
var lightness = 0;
var _csWidth = cs.width;
var _csHeight = cs.height;
/* 中心に移動 */
ctx.translate(_csWidth/2, _csHeight/2);
ctx.lineWidth = 1;
/* 1本ずつ線を引いていき、回転させることで円錐を実現 */
for(var i = 0; i < 360; i++) {
// 45度ごとに折り返し地点を定める
if (i % 45 === 0) {
if (isReverse) {
isReverse = false;
} else {
isReverse = true;
}
}
// isReverseに応じて色の輝度を変更
if(isReverse) {
lightness += 1;
} else {
lightness -= 1;
}
// 輝度の上限・下限を設定
if (lightness < 70) {
lightness = 70;
}
if (lightness > 100) {
lightness = 100;
}
ctx.save();
ctx.rotate(Math.PI * i/180);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.strokeStyle = 'hsl(0, 0%, '+lightness+'%'+')';
ctx.lineTo(0, _csWidth);
ctx.stroke();
ctx.closePath();
ctx.restore();
}
return cs.toDataURL();
};
/**
* キラキラした画像を描画
* @param {src} src: パターンに必要なdataURL
*/
var createBikkuri = function(src) {
/* パターン画像生成時に縮小していたCanvasサイズを戻す */
cs.width = csWidth;
cs.height = csHeight;
var _csWidth = csWidth;
var _csHeight = csHeight;
/* 渡されたパターンで画像を生成 */
var img = new Image();
img.src = src;
/**
* 画像をCanvas内に繰り返し敷き詰める
*/
var drawPtn = function() {
var ptn = ctx.createPattern(img, '');
ctx.fillStyle = ptn;
ctx.fillRect(0, 0, _csWidth, _csHeight);
};
/**
* 彩色グラデーション
* blendModeを変えることで、見え方が変わる
* - multiply
* - screen
* - overlay
*
* @param {obj} obj: グラデーションを以下のcolorsオブジェクトのように指定する
*/
var colors = {
offset: [0, 0.25, 0.5, 0.75, 1], // グラデーションの位置
rgb : ['33,150,243', '3,169,244', '0,188,212', '0,150,136', '76,175,80'],
alpha : [1, 0.8, 0.6, 0.4, 0.2],
rgba : [], // レンダリング時に生成
isReverse: [] // 同上
};
var drawColor = function(obj) {
var blendMode = 'multiply';
ctx.globalCompositeOperation = blendMode;
ctx.beginPath();
var grad = ctx.createLinearGradient(0,0, _csWidth, _csHeight);
for (var i = 0, l = obj.offset.length; i < l; i++) {
grad.addColorStop(obj.offset[i], obj.rgba[i]);
}
ctx.fillStyle = grad;
ctx.rect(0,0, _csWidth, _csHeight);
ctx.fill();
};
/**
* レンダリング
* drawColorと同様に、colorsオブジェクトを渡す
*/
var render = function(obj) {
obj = obj || colors;
// 渡すグラデーションのrgbaを生成する
for (var i = 0, l = obj.rgb.length; i < l; i++) {
obj.rgba[i] = 'rgba('+obj.rgb[i]+', '+obj.alpha[i]+')';
// 一定の範囲で繰り返す
if (obj.alpha[i] < 0.2) {
obj.isReverse[i] = false;
obj.alpha[i] = 0.2;
}
if(obj.alpha[i] > 1) {
obj.isReverse[i] = true;
obj.alpha[i] = 1;
}
// 配列に対応するisReverseをもとに増減
if(obj.isReverse[i]) {
obj.alpha[i] -= 0.01;
} else {
obj.alpha[i] += 0.01;
}
}
ctx.clearRect(0, 0, csWidth, csHeight);
drawPtn();
drawColor(obj);
requestAnimationFrame(function() {
render(obj);
});
};
/**
* パターン画像の読み込み完了後にレンダリングを開始
*/
img.onload = function() {
/* 非表示にしていたCanvasを表示させる(後述) */
$(cs).removeClass('is-hide');
render(colors);
};
};
/**
* パターン画像生成中はCanvasサイズが変わっているため、
* 念のためCanvasを非表示にしておく(is-hide = opacity:0)
*/
$(cs).addClass('is-hide');
/* パターン画像を作成 */
ptnSrc = createPtnSrc();
/* 実行 */
createBikkuri(ptnSrc);
作り方・考え方
- まずはパターンとして敷き詰めるための小さな円錐グラデーションを作成します
- パターンを作成するためにCanvasサイズを一旦小さくします
- 生成後、
canvas.toDataURL()
を返し、パターンを画像として渡せるようにします - ここまでCanvasを見せないようにCSSで
opacity:0
を指定しています - Canvasサイズを元に戻し、パターン画像を**
createPattern()
**で指定すれば基本はおしまいです
- ここでは色を付けるために少し手を加えています
- パターンを敷き詰めた上に、色を重ねてブレンドモードを指定するだけです
- スクリーンや乗算といったブレンドモードは**
context.globalCompositeOperation
**で設定することができます
- 後はアニメーションさせるために、フレームごとにrgbaの透明度を変更させています
- 最後に好きな画像を
position
でCanvasに重ねて完了です
終わりに
作り方は自分で考えてみたので、書き方が微妙だったり非効率な可能性があります。もっと良い方法があればご教授くださいませ。なおパターン画像は素直にPhotoshopなどで作ったほうが簡単でキレイにできるかと思いますw
今まで作成した謎なシリーズ
Canvasで斬!っと画面が斬れるようなエフェクトを書いてみた
Canvasで漫画にあるような吹き出しを書いてみた
Canvasで漫画にあるような "ざわ・・・" を書いてみた
Canvasで漫画にあるような集中線を書いてみた