Vue3 で「動く」Slack絵文字ジェネレーターを作って公開した話
先日リリースした個人開発のアプリについて感想や技術スタックなど色々書きます。
どんなアプリを公開した?
以下ツイートで紹介している、 Slack の絵文字スタンプ用のアニ GIF を簡単につくれるアプリを公開しました。
タイミングが良かったのか数多く Retweet してもらえ、 Twitter を初めて初のバズの波動を感じました。アプリ自体はこちらです。とても簡単に試せるのでぜひ使ってもらえると嬉しいです。
技術スタック
- 言語: TypeScript
- フレームワーク: Vue.js 3
- ビルドツール: Vite
- インフラ: Vercel
Vue3 の SPA を Vite でビルドし、Vercel にデプロイしています。そして絵文字の描画・アニメーション部分は SVG を利用。サーバーは持たず、SVG から GIF の変換も全てフロントエンドで行っています。
あまり見せられたものでは無いですが、コードは全て GitHub に公開しています。
開発いろいろ
開発期間
Wakka Timeをエディタに導入していたので、開発期間のデータが取れました。
8 月 7 日の夜にプロジェクトをスタートして、11 日のリリース日までで開発期間は 4 日。合計約 39 時間コードを書いていたようです。
3 連休で外出出来ないことを良いことに 1 日 10 時間書いている日もあります。 3 連休最終日に妻に「PC やりすぎ💢」とめちゃ怒られたのも納得です。。
SVG 操作を初めて触るところから開発をスタートしていることを考慮すると、割と早く形になったのかなと思います。個人開発は勢いが大事だと実感しました。
運用費用
Vercel の無料プランでホストしているので今のところサーバー代は 0 円です。サーバー実装を持たず、SVG から GIF の変換もフロント側で全て完結したことが維持費用にも貢献しました。
ただ、一応 animated-emoji-gen.com のドメインを取得したので、その料金(年間 900 円程)が別途かかっています。
アクセス数の推移
初日に公開ツイートがバズったおかげで 1300 ユーザーとかなりのアクセスがありました。しかし、その後は劇的な低下具合・・。
まだオーガニック検索での流入は期待できないのでしょうがないかなとは思いますが、今後検索からも使ってもらえるようにしていきたいです。
また、後述する vue-i18n-next を使って多言語対応したおかげか、海外からのアクセスも数件あります。需要あるのかまだ分からないのですが、今後に期待。
技術的なポイント
実装するうえでの技術的なポイントを簡単に紹介します。
テキストのSVG Pathへの変換
絵文字の要素としてテキストを柔軟に扱うために、SVG の<text>
タグは利用せず、テキストを一度 SVG Path に変換しています。
変換には ttf ファイルや otf ファイルを Parse して SVG Path を出力できる opentype.jsを利用しています。
以下のように Font ファイルをロードして、任意の文字列を渡すことで SVG Path Data を取得できます。
import { load } from "opentype.js";
const textToPathData = async (text: string): string => {
const font = await load('使用したいFontファイルへのPath')
return font.getPath(text, 0, 0, 128).toPathData(2)
}
実際のコードでは、アプリ初期化時と言語変更時に、Noto フォントの各言語ファイルをロードして利用しています。
SVG PathのViewBoxへのフィット
128×128 の枠内を GIF として出力する都合上、どのような文字数・改行でも 128×128 の SVG の ViewBox の枠にフィットさせる必要があります。
このフィットは自動では出来ないので、SVG Path の transform scale のを使って、枠サイズに合わせて文字列の SVG Path を拡大縮小することで対応しています。
実装箇所はこちらです。
const textSize = computed(() => {
return rows.value.map((t) => {
if (!font.value) {
return {
height: 0,
width: 0,
};
}
const { x1, y1, x2, y2 } = font!.value
.getPath(t, 0, 0, viewSize)
.getBoundingBox();
return {
width: x2 - x1 + 20.8,
height: y2 - y1 + 20.8,
};
});
});
const transforms = computed(() => {
if (!font.value || !text.value.length) {
return ["scale(1,1)"];
}
return rows.value.map((t, i) => {
const xScale = viewSize / (textSize.value[i].width + 8);
const yScale = viewSize / textSize.value[i].height / rows.value.length;
return `scale(${xScale}, ${yScale})`;
});
});
opentype.js のgetBoundingBox
を使って描画前に文字のサイズを取得して、それをviewBox の大きさから割ることで、拡大率を出しています。そしてそれを SVG タグの transform にわたすことで、文字数・行に応じてリアクティブにサイズを変更しています。
SVG Filter
文字の動き・加工は、SVG Filter に SVG の<animate>
タグでアニメーションを設定することで実現しています。
Filter 自体のコーディングは、SVG Filtersという SVG Filter を GUI で生成できるサイトで作ったものの流用です。とても便利。
SVGのGIFへの変換
ブラウザ上での SVG から GIF への変換は、一番苦労したところです。当初、SVG を Canvas に変換し、そこから GIF にするなど色々試したのですが全く上手くいきませんでした。
結局@yuecoさんの神記事で紹介されていた方法で対応しました。詳細は記事をご欄ください。
一部 UI の都合上で処理を調整しているのですが、ほぼほぼ記事で掲載されているsvg2gif.ts
の実装をそのまま利用しています。感謝🙏
他言語対応
利用ユーザーを増やすため英語圏のユーザーにもアクセスしてもらいたいと思い、vue-i18n-nextを使い多言語対応を実装しました。vue-i18n-next はとても直感的な API で多言語化を実現できるのでとてもおすすめです。
他、今回 vue-i18n-next の開発者であるkazuponさんにもツイートを拾っていただき、性能改善 PR を送ってもらうという奇跡も起こりました。
GA 上でも海外からのアクセスが数件確認できているので、多言語対応の意味はあったのかなと思います。今後伸ばしていきたいです。
デザイン
デザインは CSS フレームワークのbulmaを利用しています。直感的な class 名でスタイルを調整できるのが好きです。
他、サイドバーに入力フォーム、中央にタイル状にフォントを描画するというレイアウトについては@coroconさんのSlack Emoji Generatorの神がかったレイアウトをまるっと参考にさせていただきました 🙏
自分自身、今までずっとこちらのアプリで絵文字を作っていました。シンプルな UI でとても使いやすいです。
その他 OG Image や全体のカラーテイストは愚直に Figma と CSS を色々いじりながら、調整しました。結果的に自分なりにはシンプルで見やすい感じになって良かったかなと思います。
おわりに
初めて皆に使ってもらえるアプリを個人開発で作ったのですが、様々な方に感想をもらえてとても嬉しかったです。
また、初日の一時ですが、個人開発アプリの GA を眺めながらニヤニヤするという自分には縁のないと思っていたら楽しみも体験できました。開発自体とても楽しかったので今後も animated-emoji-gen.com を継続して維持・改善していきたいです。
他、性能改善やFilter追加のプルリクエストはいつでも歓迎なので気軽に送ってください。
Discussion