Help us understand the problem. What is going on with this article?

Canvasでビックリマンなシールを書いてみた(光るゾ!)

More than 3 years have passed since last update.

はじめに

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

[動作イメージ]
※テキストと写真は普通の画像です。
img.jpg

img2.JPG

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

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

pat_base.jpg

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

pat_color.jpg

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

終わりに

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

今まで作成した謎なシリーズ

Canvasで斬!っと画面が斬れるようなエフェクトを書いてみた
Canvasで漫画にあるような吹き出しを書いてみた
Canvasで漫画にあるような "ざわ・・・" を書いてみた
Canvasで漫画にあるような集中線を書いてみた

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした