Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Canvasで漫画にあるような集中線を書いてみた

More than 5 years have passed since last update.

はじめに

集中線GIFメーカーという面白いサービスにインスパイアを受け、Canvasで真似してみました。本家様のコードは一切見ずに作成したため、書き方やアニメーションの動作はまた違ったものになっているかと思います。結構良い感じにできたので公開してみます。

何かの参考にでもなれば幸いです。

[動作イメージ]
line_anim1.gif
line_anim2.gif

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

特長

なるべく拡張性・汎用性が高くなるように作成しました。

  • Canvasサイズに依存しない
  • 集中線の数が変更できる
  • 集中線の色と太さが変更できる
  • 集中線の中心部の大きさと位置が変更できる(位置については少しスクリプトの修正が必要)

ソースコード

index.html
<canvas id="myCanvas" width="500" height="500"></canvas>
canvas.js
/**
 * 集中線メーカー
 * @param {obj} canvas object
 * @param {number} centralX: 集中線を配置するx座標
 * @param {number} centralY: 集中線を配置するy座標
 * @param {number} lineWidth: 線の太さ(ランダムの上限)
 * @param {number} lineNum: 線の数
 * @param {number} circleRadiusMax: 集中線の円形の半径上限
 * @param {number} circleRadiusMin: 集中線の円形の半径下限
 * @param {string} lineColor: 集中線の色
 */

var focusLine = function(cs, centralX, centralY, lineWidth, lineNum, circleRadiusMax, circleRadiusMin, lineColor) {

    var ctx = cs.getContext('2d');
    var lines = [];

    // canvasの中心から角までの斜辺距離を円の半径とする
    // 中心をズラす場合は、もっと大きな値を設定してやる(引数でできるようにすべきか)
    var csRadius = Math.sqrt(Math.pow(cs.width / 2, 2) + Math.pow(cs.height / 2, 2)) | 0;

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

    /**
     * 円周上の座標を返す
     * @param d 角度
     * @param r 半径
     * @param cx, cy 中心座標
     */
    var getCircumPos = {
        // x座標
        x: function(d, r, cx) {
            return Math.cos(Math.PI / 180 * d) * r + cx;
        },
        // y座標
        y: function(d, r, cy) {
            return Math.sin(Math.PI / 180 * d) * r + cy;
        }
    };

    /**
     * @constructor
     */
    var Liner = function() {
        this.initialize();
    };
    Liner.prototype = {
        /* initialize()内の設定をsetPos()内へ移すと、アニメーションの動きが変わる */
        initialize: function() {
            this.deg       = getRandomInt(360, 0);
        },
        setPos: function() {
            this.moveDeg   = this.deg + (getRandomInt(lineWidth, 1) / 10);
            this.endRadius = getRandomInt(circleRadiusMax, circleRadiusMin);

            // 開始座標
            this.startPos = {
                x: getCircumPos.x(this.deg, csRadius, centralX),
                y: getCircumPos.y(this.deg, csRadius, centralY)
            };

            // 移動座標
            this.movePos = {
                x: getCircumPos.x(this.moveDeg, csRadius, centralX),
                y: getCircumPos.y(this.moveDeg, csRadius, centralY)
            };

            // 終了座標
            this.endPos = {
                x: getCircumPos.x(this.moveDeg, this.endRadius, centralX),
                y: getCircumPos.y(this.moveDeg, this.endRadius, centralY)
            };
        },
        update: function() {
            this.setPos();
        },
        draw: function() {
            ctx.beginPath();
            ctx.lineWidth = 1;
            ctx.fillStyle = lineColor;
            ctx.moveTo(this.startPos.x, this.startPos.y);
            ctx.lineTo(this.movePos.x,  this.movePos.y);
            ctx.lineTo(this.endPos.x,   this.endPos.y);
            ctx.fill();
            ctx.closePath();
        },
        render: function() {
            this.update();
            this.draw();
        }
    };

    /**
     * 線インスタンスの作成
     * @return lines[instance, instance...];
     */
    function createLines(num) {
        var i = 0;
        for (; i < num; i++) {
            lines[lines.length] = new Liner();
        }
    }

    /**
     * 描画
     */
    function render() {
        var i = 0;
        var l = lines.length;
        ctx.clearRect(0, 0, cs.width, cs.height);
        for (; i < l; i++) {
            lines[i].render();
        }
        setTimeout(function() {
            render();
        }, 100);
    }

    createLines(lineNum);
    render();
};


/**
 * focusLine()に渡す引数の設定
 */
var cs = document.getElementById('myCanvas');
var conf = {
    cx: cs.width / 2,
    cy: cs.height / 2,
    lineWidth: 30,
    lineNum: 200,
    crMax: 200, // 集中線の円形の半径上限
    crMin: 150, // 集中線の円形の半径下限
    color: 'black'
};

focusLine(cs, conf.cx, conf.cy, conf.lineWidth, conf.lineNum, conf.crMax, conf.crMin, conf.color);

作り方・考え方

  1. canvas要素の外側に点Aをランダムに配置
  2. 点Aから少し角度をズラして、近くに点Bを配置
  3. canvasの中心部Cに向けて線を引き、細長い三角形を作成
  4. 1-3を沢山繰り返すことで、四方八方から中央に向かって線が伸びていく

canvas要素の外側に点Aを配置

線が見切れてしまうことを防ぎます。

集中線を中央以外に配置する場合は、外側の円周 csRadius をもっと大きくします。(書いている途中で気づいたので、スクリプトは調整していません。。。)

[イメージ]
sample1.png

点Aから少し角度をズラして、近くに点Bを配置

角度を変えて結ぶことで線の太さを表現します。角度の増減をランダムにすることで自然な集中線を表現します

[イメージ]
sample2.png

canvasの中心部Cに向けて線を引き、細長い三角形を作成

点Aと点B、終点Cを結びます。ただし完全な中心点だと中央の余白が作れないため、中に小さな円を想定し、その円周上を終点Cとします。実際には三角形ごとに中の小円半径をランダムに変え、ギザギザの余白ができるようにします。

[イメージ]
sample3.png

sample4.png

終わりに

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

[おまけ]
other1.gif
other2.gif

nekoneko-wanwan
Webデザイン、フロントエンド開発を主にやっています。真面目なものから、変なものまで。色々な記事を投稿していければと思います。
hotstartupinc
「ペライチ」を開発する会社です。
https://peraichi.com/
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