Canvasでビックリマンなシールを書いてみた（光るゾ！）

# はじめに
Canvasを利用して、懐かしのビックリマンシールのようなエフェクトを表現してみました。きらきらキレイですね。Gifアニメは重たくアップできなかったため静止画にしましたが、地味にアニメーションして光ります。

**[動作イメージ]**
※テキストと写真は普通の画像です。
![img.jpg](https://qiita-image-store.s3.amazonaws.com/0/58436/5f0100ee-b94c-4b27-a0d1-b58e607cce1a.jpeg)

![img2.JPG](https://qiita-image-store.s3.amazonaws.com/0/58436/4ffe087a-16fa-51bb-656a-e35bef9d4ad0.jpeg)



> なお実際の動作は以下で確認することができます。
> 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);
```





# 作り方・考え方
- まずはパターンとして敷き詰めるための小さな円錐グラデーションを作成します

> ![pat.jpg](https://qiita-image-store.s3.amazonaws.com/0/58436/0bc16f35-5de8-4e66-c6ee-32e032bd269b.jpeg)


- パターンを作成するためにCanvasサイズを一旦小さくします
- 生成後、`canvas.toDataURL()`を返し、パターンを画像として渡せるようにします
- ここまでCanvasを見せないようにCSSで`opacity:0`を指定しています
- Canvasサイズを元に戻し、パターン画像を**`createPattern()`**で指定すれば基本はおしまいです

>![pat_base.jpg](https://qiita-image-store.s3.amazonaws.com/0/58436/9c1f343f-5381-64db-b61c-a140a9313bf6.jpeg)


- ここでは色を付けるために少し手を加えています
- パターンを敷き詰めた上に、色を重ねてブレンドモードを指定するだけです
- スクリーンや乗算といったブレンドモードは**`context.globalCompositeOperation`**で設定することができます

> ![pat_color.jpg](https://qiita-image-store.s3.amazonaws.com/0/58436/0a13b805-522e-0ca3-cc6d-a71465f49924.jpeg)


- 後はアニメーションさせるために、フレームごとにrgbaの透明度を変更させています
- 最後に好きな画像を`position`でCanvasに重ねて完了です





# 終わりに
作り方は自分で考えてみたので、書き方が微妙だったり非効率な可能性があります。もっと良い方法があればご教授くださいませ。なおパターン画像は素直にPhotoshopなどで作ったほうが簡単でキレイにできるかと思いますw





**今まで作成した謎なシリーズ**
> [Canvasで斬！っと画面が斬れるようなエフェクトを書いてみた](http://qiita.com/nekoneko-wanwan/items/bdbd0d3cf294fc54f6dd)
> [Canvasで漫画にあるような吹き出しを書いてみた](http://qiita.com/nekoneko-wanwan/items/df5d8eaf33fa477fe7c9)
> [Canvasで漫画にあるような "ざわ・・・" を書いてみた](http://qiita.com/nekoneko-wanwan/items/4b196365183580831758)
> [Canvasで漫画にあるような集中線を書いてみた](http://qiita.com/nekoneko-wanwan/items/0911a59bf835d5b9e35a)