みんなの「作ってみた」

「Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう」を実際に作ってみる

2019/02/17

taishikato
taishikato
Vancouverなう

はじめに

この記事は、Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう - Qiitaを参考に実際に手を動かしてOGP生成アプリを作ってみて、記事中にコードミスがあったり、別のやり方でもできそうと思ったりと色々ありました。
別にディスりたいわけではなく、おそらく元記事は、こうやるとOGP生成アプリができるよねっていうシステム概要を載せてくれてるんだと思います。
なのでこの記事は、実際に動くコードをもとに書いていこうと思います。

プロジェクト作成、準備

$ vue create ogp-vue-test
$ cd ogp-vue-test
$ yarn add firebase # Or npm i firebase -S

画像を生成、Firebase Cloud Storageにアップロードする

まずは、画像を作ってアップロードする画面を作りましょう。

src/App.js
<template>
  <div class="hello">
    <svg ref="svgCard" width="860px" height="200px" viewBox="0 0  860 520" preserveAspectRatio="xMidYMid meet" >
      <rect id="svgEditorBackground" x="0" y="0" width="860" height="200" style="fill: none; stroke: none;"/>
      <text style="fill:black;font-family:Arial;font-size:50px;" x="56" y="46" id="e1_texte">{{ msg }}</text>
    </svg>
    <input v-model="msg" type="text">
    <button @click="create">create</button>
  </div>
</template>

<script>
import firebase from 'firebase'

// firebaseのconfig情報をペースト
const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: ""
};
firebase.initializeApp(config)
const db = firebase.firestore()

// svgをpngに変換
const svg2imageData = (svgElement, successCallback, errorCallback) => {
  const canvas = document.createElement('canvas')
  canvas.width = 1200
  canvas.height = 630
  const ctx = canvas.getContext('2d')
  const image = new Image()
  image.onload = () => {
    ctx.drawImage(image, 0, 0, 1200, 630)
    successCallback(canvas.toDataURL())
  }
  image.onerror = (e) => {
    errorCallback(e)
  }
  const svgData = new XMLSerializer().serializeToString(svgElement)
  image.src = 'data:image/svg+xml;charset=utf-8;base64,' + btoa(unescape(encodeURIComponent(svgData)))
}

export default {
  name: 'hello',
  data () {
    return {
      msg: 'Hello World',
      uuid: '1', // 適当に採番する
      description: 'Vue.jsとFirebaseでOGP生成アプリをつくってみます'
    }
  },
  methods: {
    async create() {
      // refでsvgCardをsvgに設定しているのでthis.$refs.svgCardで要素を取れます
      svg2imageData(this.$refs.svgCard, async (data) => {
        const sRef = firebase.storage().ref()
        const fileRef = sRef.child(`${this.uuid}.png`)

        // Firebase Cloud Storageにアップロード
        await fileRef.putString(data, 'data_url');
        const url = await fileRef.getDownloadURL()
        console.log(url)

        // Firestoreに保存しておく
        const card = db.collection('cards').doc(this.uuid)
        await card.set({
          url,
          message: this.description
        });
      })
    }
  }
}
</script>

yarn serveで実際にブラウザ上で確認してみましょう。

$ yarn serve
すると、http://localhost:8080/で以下のような画面が現れるはずです。

(ちなみに今回使用しているしょぼいSVG(赤文字でHello World)はDrawing SVGというツールで作りました)

今回のコードで肝になっているのは、やはり、フォームに入力したテキストとSVG内の内容が一致しているかつ可変であるというところかな、と思います。
msgという変数をバインドしています。

つまり、元の記事でも言われている通り、ユーザー入力の値を画像にするのにSVGはとても適していると思います(絵文字も使える!)。

さて、createボタンを押してみましょう。ボタンが押されたタイミングで、画像がFirebase Storageに格納されます:clap:

OGP表示側コード

functions/index.js
const functions = require('firebase-functions');
const express = require('express');
const app = express();
const admin = require('firebase-admin');

admin.initializeApp(functions.config().firebase);

const db = admin.firestore();

const url = 'https://qiita.com/';
const site_name = 'Qiita';
const title = 'Qiita';
const meta_description = 'プログラミング情報共有サイトです。';
const meta_keywords = ['プログラミング'];
const og_description = 'プログラミング情報共有サイトです。';
const og_image_width = 1200;
const og_image_height = 630;
const fb_appid = '';
const tw_description = 'プログラミング情報共有サイトです。';
const tw_site = '';
const tw_creator = '';

const genHtml = image_url => `
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>${title}</title>
    <meta name="description" content="${meta_description}">
    <meta name="keywords" content="${meta_keywords.join(',')}">
    <meta property="og:locale" content="ja_JP">
    <meta property="og:type" content="website">
    <meta property="og:url" content="${url}">
    <meta property="og:title" content="${title}">
    <meta property="og:site_name" content="${site_name}">
    <meta property="og:description" content="${og_description}">
    <meta property="og:image" content="${image_url}">
    <meta property="og:image:width" content="${og_image_width}">
    <meta property="og:image:height" content="${og_image_height}">
    <meta property="fb:app_id" content="${fb_appid}">
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="${title}">
    <meta name="twitter:description" content="${tw_description}">
    <meta name="twitter:image" content="${url}">
    <meta name="twitter:site" content="${tw_site}">
    <meta name="twitter:creator" content="${tw_creator}">
  </head>
  <body>
    <script>
      // クローラーにはメタタグを解釈させて、人間は任意のページに飛ばす
      // location.href = '/share';
    </script>
  </body>
</html>
`;

app.get('/s/:id', (req, res) => {
  db.collection('cards')
    .doc(req.params.id)
    .get()
    .then(result => {
      if (!result.exists) {
        res.status(404).send('404 Not Exist');
      } else {
        const data = result.data();
        const html = genHtml(data.url);
        res.set('cache-control', 'public, max-age=3600');
        res.send(html);
      }
    });
});
exports.s = functions.https.onRequest(app);

Firebase Cloud Functionsの機能を使うため、動作確認をする際には、yarnではなくfirebaseコマンドを使う必要があります。

$ firebase serve
http://localhost:5000/s/1 にブラウザでアクセスすると…真っ白な画面が!
ソースコードを表示してみると…画像のURLがog:imageプロパティの値にセットされたHTMLが現れます!

まとめ

Vue.jsとFirebaseでOGP画像生成系のサービスを爆速で作ろう - Qiitaをもとに、実際に手を動かして実装してみました。
元記事の通り、Firebase x Vueで爆速でOGP画像生成系サービスは作れると思うし、本当に素晴らしいと思いました!

元記事と、当記事での実装の違いは、
元記事ではOGP表示側コード側で画像URLをFirebase Storageから取得していたのですが、
当記事では画像URLをFirebase firestoreに保存しておいて、OGP表示側コード側ではそこから取得しています。
自分としてはこっちの方がシンプルだと思ったのですが、もしデメリット等あれば教えていただけると幸いです。

今回作ったものをGithubに置きました。参考にしていただけたらと思います。
taishikato/ogp-vue-test:thumbsup: