canvasでクリスマスカードを作ってみよう

OyamaEitaPosted by

この記事は GRIPHONE Advent Calendar 2019 10日目の記事です。

こんにちは、フロントエンドエンジニアの小山です。

先日ユーザの入力したテキストを画像と組み合わせて新しい画像を生成するジェネレータを作る機会がありました。
今回はクリスマスシーズンなので、それを応用してクリスマスカードを作ってみようと思います。

ベースにする画像はこちら。

html、canvasの準備

まずはhtmlにcanvasを設置します。
canvasのサイズはベースの画像のサイズに合わせておきましょう。
今回は800×540の画像を使います。
※ resultImageというidのついた空の画像タグがいますがこれは一番最後に説明します。

<html>
<head>
    <title>クリスマスカードを作ろう</title>
</head>
<body>
    <canvas id="canvas" width="800" height="540" />
    <img id="resultImage" width="800" height="540">
</body>
</html>

画像の読み込み

ここからはjavascriptです。
getContextメソッドで描画機能を有効にします。

const canvas = document.querySelecter('#canvas');
let ctx = canvas.getContext('2d');

次に下地になる画像を描画します。
drawImageメソッドでカンバスに画像をのっけます。
画像のロードが終わる前にdrawImageしちゃうと何も表示されないので画像のロードを待ってあげましょう。

var canvas = document.getElementById('canvas');
console.log(canvas)
var ctx = canvas.getContext('2d');

// 画像を読み込む
var imagePath = './img/card.jpg';
var image = new Image(); //画像を作成
image.addEventListener('load', function() {
    ctx.drawImage(image, 0, 0, image.width, image.height);
})
image.src = imagePath;

カードにタイトルを入れよう

画像がシンプルすぎるのでタイトルぐらいつけてあげましょう。
画像の上の方に「Merry Christmas」と入れてみます。
文字描画にはfillTextメソッドを使います。
第一引数に描画したい文字列、第2引数にx軸の文字の開始位置、第3引数にy軸の文字の開始位置、第4引数に文字を描画するエリアの横幅を指定します。

ここで注意なのが第3引数に指定するy軸の開始位置。
fillテキストのy軸指定は文字の左下を指定することになるので注意してください。

これも画像の描画の後にしないと画像の下のレイヤーに描画されて見えなくなっちゃうので画像のロードを待ってあげましょう。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// 画像を読み込む
var imagePath = './img/card.jpg';
var image = new Image(); //画像を作成
image.addEventListener('load', function() {
    ctx.drawImage(image, 0, 0, image.width, image.height);

    // タイトルを描画する
    var titleFontFamily = 'YuMincho' // フォントの指定(いい感じのやつ入れてください)
    var titleFontSize = 60 //フォントサイズ
    ctx.font = 'bold ' + titleFontSize + 'px ' + titleFontFamily; // せっかくタイトルなので太字にしてみました
    ctx.textAlign = 'center'; // せっかくタイトルなので中央揃えです
    ctx.fillText('Merry Christmas', 800/2, 150, 800)
})
image.src = imagePath;

ここまででできた画像がこちら。

ちょっとタイトルに飾りっ気がなさすぎますね。

タイトルをゴージャスにしてみる

グラデーションとドロップシャドウを入れてゴージャスにしてみましょう。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// 画像を読み込む
var imagePath = './img/card.jpg';
var image = new Image(); //画像を作成
image.addEventListener('load', function() {
    ctx.drawImage(image, 0, 0, image.width, image.height);
    
    // タイトルを描画する
    var titleFontFamily = 'YuMincho' // フォントの指定(いい感じのやつ入れてください)
    var titleFontSize = 60 //フォントサイズ
    ctx.font = 'bold ' + titleFontSize + 'px ' + titleFontFamily; // せっかくタイトルなので太字にしてみました
    ctx.textAlign = 'center'; // せっかくタイトルなので中央揃えです

    // 文字の横幅を計り、グラデーションさせる
    var titleWidth = ctx.measureText('Merry Christmas').width
    const gradient = ctx.createLinearGradient((800-titleWidth)/2, 0, titleWidth ,0);
    gradient.addColorStop(0.0, "#f4d49c");
    gradient.addColorStop(0.7, "#dcdc81");
    gradient.addColorStop(1.0, "#efb169");
    ctx.fillStyle = gradient

    // 文字のシャドウ
    ctx.shadowColor = '#734e00';
    ctx.shadowBlur = 5; //px単位

    ctx.fillText('Merry Christmas', 800/2, 150, 800)
})
image.src = imagePath;

仕上がりはこんな感じ。
一気にクリスマス感が出ましたね。

本文を追加しよう

タイトルも入れ終わったので本文を入れていきます。
例文はこちらのサイトから拝借させていただきました。

メリークリスマス!
 聖なる夜にお花の香りに包まれて、抱えきれないほどの沢山の幸せが二人の元に訪れますように。

なんと心温まる文章でしょうか。涙が出ます。

それでは早速本文を描画していきましょう。
本文を描画するにはタイトルと同じくfillTextメソッドを使いますが、その前にタイトル用に設定したフォント、フォントサイズ、グラデーション、シャドウ、文字揃えなどを上書きして本文用のスタイルにしなければなりません。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// 画像を読み込む
var imagePath = './img/card.jpg';
var image = new Image(); //画像を作成
image.addEventListener('load', function() {
    ctx.drawImage(image, 0, 0, image.width, image.height);

    // タイトルを描画する
    var titleFontFamily = 'YuMincho' // フォントの指定(いい感じのやつ入れてください)
    var titleFontSize = 60 //フォントサイズ
    ctx.font = 'bold ' + titleFontSize + 'px ' + titleFontFamily; // せっかくタイトルなので太字にしてみました
    ctx.textAlign = 'center'; // せっかくタイトルなので中央揃えです

    // 文字の横幅を計り、グラデーションさせる
    var titleWidth = ctx.measureText('Merry Christmas').width
    const gradient = ctx.createLinearGradient((800-titleWidth)/2, 0, titleWidth ,0);
    gradient.addColorStop(0.0, "#f4d49c");
    gradient.addColorStop(0.7, "#dcdc81");
    gradient.addColorStop(1.0, "#efb169");
    ctx.fillStyle = gradient

    // 文字のシャドウ
    ctx.shadowColor = '#734e00';
    ctx.shadowBlur = 5; //px単位

    ctx.fillText('Merry Christmas', 800/2, 150, 800)

    // 本文を描画するため、タイトル用に設定したフォント、グラデ、シャドウをリセットする
    var body = 'メリークリスマス!\n 聖なる夜にお花の香りに包まれて、抱えきれないほどの沢山の幸せが二人の元に訪れますように。'
    var bodyFontSize = 50; // 本文用のフォントサイズ
    var bodyFontFamily = 'YuMincho' // フォントの指定(いい感じのやつ入れてください)
    ctx.fillStyle = '#000000'; // グラデーションを打ち消す
    ctx.shadowColor = 'transparent'; // シャドウの色を透明にして見えないように
    ctx.textAlign = 'left'; // 文章を左揃えに
    ctx.font = bodyFontSize + 'px ' + bodyFontFamily; // 本文用のフォントに差し替え
    // 本文の描画 画像の両端から75pxずつ余白をとってます
    ctx.fillText(body, 75, 300, 650);

})
image.src = imagePath;

 出来上がった画像がこちら

ほっそい

無理やり1行に収められてバーコードみたいになってしまいました。
fillTextメソッドは指定された位置に画像を描画するだけなので自動で改行とかしてくれません。気が利かない感じです。多分モテないタイプです。

いい感じに改行を入れるためには本文の長さを1文字ずつ測って、行の長さから溢れたら描画するy座標をずらしてあげる処理が必要になります。

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

// 画像を読み込む
var imagePath = './img/card.jpg';
var image = new Image(); //画像を作成
image.addEventListener('load', function() {
    ctx.drawImage(image, 0, 0, image.width, image.height);

    // タイトルを描画する
    var titleFontFamily = 'YuMincho' // フォントの指定(いい感じのやつ入れてください)
    var titleFontSize = 60 //フォントサイズ
    ctx.font = 'bold ' + titleFontSize + 'px ' + titleFontFamily; // せっかくタイトルなので太字にしてみました
    ctx.textAlign = 'center'; // せっかくタイトルなので中央揃えです

    // 文字の横幅を計り、グラデーションさせる
    var titleWidth = ctx.measureText('Merry Christmas').width
    const gradient = ctx.createLinearGradient((800-titleWidth)/2, 0, titleWidth ,0);
    gradient.addColorStop(0.0, "#f4d49c");
    gradient.addColorStop(0.7, "#dcdc81");
    gradient.addColorStop(1.0, "#efb169");
    ctx.fillStyle = gradient

    // 文字のシャドウ
    ctx.shadowColor = '#734e00';
    ctx.shadowBlur = 5; //px単位

    ctx.fillText('Merry Christmas', 800/2, 150, 800)

    // 本文を描画するため、タイトル用に設定したフォント、グラデ、シャドウをリセットする
    var body = 'メリークリスマス!\n聖なる夜にお花の香りに包まれて、抱えきれないほどの沢山の幸せが二人の元に訪れますように。'
    var bodyFontSize = 30; // 本文用のフォントサイズ
    var bodyFontFamily = 'YuMincho' // フォントの指定(いい感じのやつ入れてください)
    ctx.fillStyle = '#000000'; // グラデーションを打ち消す
    ctx.shadowColor = 'transparent'; // シャドウの色を透明にして見えないように
    ctx.textAlign = 'left'; // 文章を左揃えに
    ctx.font = bodyFontSize + 'px ' + bodyFontFamily; // 本文用のフォントに差し替え

    // 本文の描画 画像の両端から75pxずつ余白をとってます
    // ctx.fillText(body, 75, 300, 650);

    // テキストを1文字毎に分割
    var textArray = body.split('')

    // テキストを行単位でまとめる
    var textLines = [''];
    var currentCol = 0; //横幅
    var currentRow = 0; //何行目に入れるか
    var textAreaWidth = 650 //横幅の最大
    var lineHeight = 55; // 1行の高さ
    textArray.forEach((char, index) => {
        // 改行文字が入っているか、横幅を溢れた場合は次の行に移行する
        if( currentCol + ctx.measureText(char).width >= textAreaWidth || char == "\n") {
            currentCol = 0
            currentRow++
            textLines[currentRow] = ''
        }
        textLines[currentRow] += char
        currentCol += ctx.measureText(char).width
    })

    // 行単位に分割したテキストを描画する
    textLines.forEach((line, index) => {
        ctx.fillText(line, 75, 240+lineHeight*(index), ctx.measureText(line).width)
    })

完成

これでようやく完成です!いい感じに改行も入りましたね!
もしこれを画像として保存したい場合は一番最後にtoDataURLメソッドを使い、canvasをbase64画像に変換してimgタグのsrc属性に入れてあげるといいでしょう。

document.getElementById('resultImage').src = canvas.toDataURL("image/png")

注意: サーバのないfile://環境ではtoDataURLメソッドはクロスオリジンの問題で動かない可能性があります。

最後に

これを応用すれば年賀状もバッチリですね!
(正直デザイナーに依頼するかワードアート使うかした方が早そうな気もしますが…)
それではみなさん良いクリスマスを!