ブログ内の文章をTシャツにする機能を作った
SUZURIのエンジニアのyukyuです。SUZURIでは、画像やテキストからTシャツなどのグッズを作成できるSUZURI APIが公開されています。そこで、ブログ内の文章をTシャツにする機能を作りました。
サイトとソースコード
今回作成したブログとそのソースコードです。ブログの記事はChatGPTで生成したものです。
作成動機
ブログの記事という無形物を有体物にできたら、着用することで道行く人にシェアできて面白そうという動機でつくりました。
大まかな仕組み
仕組みとしてはシンプルです。
- ブログ上で文章が選択されたら、Tシャツを作るボタンを表示する
- ボタンを押したら、SUZURI APIへ選択された文章をPOSTし、Tシャツを作成する
- APIのレスポンスでTシャツの販売ページURLを取得し、遷移させる
ブログはNetlifyへデプロイしています。
ボタンが押されたらNetlify Functions(以下、Functions)を介して、SUZURI APIへPOSTしています。
フロントエンドからSUZURI APIを利用するとCORSエラーが起きるため、Functionsを利用しています。
実装
ブログにはGatsby.jsのstarter-blogを利用しました。
ブログ上で文章が選択された状態の判定
Tシャツを作るボタンは文章が選択されている時のみ表示するので、文章が選択されているかどうか判定する必要があるため、window.getSelection()
を利用します。
window.getSelection().toString()
で選択された文字列が取得できるので、この文字列の長さが0より大きい時は文章が選択されていると判断できます。
handleSelectText
で文章が選択されているか判定し、選択されていたら
-
setShowButton(true)
でシェアボタンを表示する -
setShareText(selectedText)
でshareText
の値を変更する
ということを行いました。
const handleSelectText = () => {
const selectedText = window.getSelection().toString()
const isSelected = selectedText.length > 0
if (isSelected) {
setShowButton(true)
setShareText(selectedText)
} else {
setShowButton(false)
}
}
...
<section
dangerouslySetInnerHTML={{ __html: post.html }}
itemProp="articleBody"
onMouseUp={() => handleSelectText()}
onMouseOut={() => handleSelectText()}
/>
...
文字選択を行う動作は以下の2パターンが考えられます。
-
section要素の領域
でドラッグして、section要素の領域
でボタンを離す -
section要素の領域
でドラッグして、section要素の領域外
でボタンを離す
section要素に限定した意図
section要素に限定しないと、プロフィールの文字やフッターの文字などの記事以外をTシャツにできてしまうためです。
なので、MouseUp、MouseOut時にhandleSelectText
を実行しています。後述しますが、これだけでは不十分でした。
SUZURI APIでTシャツを作成する
幸いにもSUZURI APIには文章からTシャツを作ることができるエンドポイントがあるので、そちらを利用します。
Material(素材) Text
テキストからMaterialとスタンダードTシャツを作ります。レートリミットが設けてあります。
POST /api/v1/materials/text
shareText
をPOSTすることでTシャツが作成されます。
APIへPOSTするコードを書く
フロントエンドからFunctionsへPOSTするコードは以下のようになります。
...
const handleClickSuzuri = async () => {
const apiUrl = "https://hoge.netlify.app/.netlify/functions/suzuri"
const config = {
headers: {
"Content-Type": "application/json"
},
}
const data = JSON.stringify({ "text": shareText })
const response = await axios.post(apiUrl, data, config)
.then((res) => {
window.location.href = res.data.products[0].sampleUrl
})
}
...
FunctionsからSUZURIへPOSTするコードは以下のようになります。
const axios = require('axios')
exports.handler = async (event, context) => {
const apiUrl = "https://suzuri.jp/api/v1/materials/text"
const config = {
headers: {
"Authorization": `Bearer ${process.env.SUZURI_API_KEY}`,
"Content-Type": "application/json"
},
}
const data = { "text": JSON.parse(event.body).text }
const response = await axios.post(apiUrl, data, config)
.then((res) => {
return {
statusCode: res.status,
body: JSON.stringify(res.data),
}
})
.catch((e) => {
return {
statusCode: e.response.status,
body: JSON.stringify(e.response.data),
}
})
return response
}
Functionsの導入や設定にはこちらを参考にしました
Tシャツの販売ページへ遷移
作成したTシャツ詳細画面へのURLをレスポンスから取得します。Tシャツだけを生成しているのでレスポンスのうち、products[0]
にあるsampleUrl
がTシャツ詳細画面へのURLなります。そこで、window.location.href = res.data.products[0].sampleUrl
で遷移を行うようにしました。
実際につくられたTシャツはこちらで確認できます
おわりに
実装後に気づいたのですが、section要素の領域外
でドラッグされ続けていて、さらに文章の選択が行われている場合も加味する必要がありそうです。
今回の実装では、スマートフォンではうまく動作しませんでした。
文字選択されている時のみ、ボタンやバルーンなどを表示する実装のベストプラクティスがあったら是非教えていただきたいです。
Discussion