Reactで画像を作成してTwitterAPIを使って画像投稿する。
ReactでCanvasを使う
探したところReact-Kanvaが便利そうだった。使い方は公式や他の方の記事を参照。
公式:https://konvajs.org/docs/react/
使い方:https://qiita.com/YSRKEN/items/bb8b34510d70ec90eb50
画像をDataURLとして取得
作成したCanvasの画像データを取得する。
useRefのHookを使ってCanvasの要素を取得し、.toDataURL()を呼び出せばOK
(https://qiita.com/YSRKEN/items/bb8b34510d70ec90eb50 から引用)
import React, { useRef } from "react";
import { Stage as StageType } from 'konva/types/Stage';
const stageRef = useRef<StageType>(null);
const process = () => {
const temp = stageRef.current;
// stageRefの中身(temp )がnullな可能性を考慮してチェック
if (temp !== null) {
// dataUrlに、画像データがdata URL(MIME Type + base64文字列)形式で書き込まれる。
// toDataURLの引数を変更すれば、PNG以外の画像形式への変換も可能
const dataUrl = temp.toDataURL();
// これ以降、dataUrlを使った操作(画像保存、画像加工等)を行う
}
};
return <Stage ref={stageRef} width={500} height={500}>
<Layer>
<Rect stroke='black' strokeWidth={4} x={5} y={5} width={490} height={490} />
<Rect fill='red' x={100} y={100} width={300} height={200} />
</Layer>
</Stage>;
余談だが、画像をダウンロードしたい場合は<a>タグにDataURLを入れてダウンロードする。
const downloadCanvas = () => {
// dataUrlを取得
const url = stageRef.current.toDataURL();
// <a>タグを作成してdataUrlを入れる
const a = document.createElement("a");
a.href = url;
a.download = "image.png";
// <a>タグをクリックする処理
a.click();
}
ブラウザの幅に合わせてCanvasの拡大率を変える
ブラウザのサイズに合わせてCanvasの拡大率を変更したい場合は、<div>要素などでブラウザのフレームサイズを取得して、拡大率を算出する。
// Stageの要素
const stageRef = useRef(null);
// 画像を表示するフレームの要素
const frameRef = useRef(null);
// Canvasで生成する画像のサイズ
const sceneWidth = 960;
const sceneHight = 1280;
// ブラウザ表示時の拡大率
const [scale, setScale] = useState(1);
useEffect(() => {
// ブラウザ表示時の拡大率を設定
// ここでは横幅(offsetWidth)に合わせる
setScale(frameRef.current.offsetWidth / sceneWidth);
}, []);
return (
<div ref={frameRef}>
<Stage
width={sceneWidth * scale}
height={sceneHight * scale}
scale={{ x: scale, y: scale }}
ref={stageRef}
>
...
</Stage>
</div>
)
ただ、これを行うと初期表示の一瞬だけブラウザに拡大率1の画像が表示されてしまう。ひとまずCSSのpositionを使って画面外に表示することで対応した。
display:hiddenにしてもいいのだが、画像取得の際にhiddenだと取れなくなってしまう。
{/* 拡大率が1のときは画面外へ飛ばす */}
<style jsx>
{`
.canvas {
${scale == 1 ? " position: absolute; right: -10000px;" : ""}
}
`}
</style>
画像取得時に拡大率を戻す
ブラウザのサイズに合わせて拡大率を変更したままDataURLを取得すると、そのサイズの画像が取得されてしまう。
画像取得前に拡大率を1に変更し、取得後にブラウザの拡大率に戻す。
ボタン押下時にツイートフラグ(isTweetCanvas)をtrueにして、useEffectで拡大率(scale)を変更。scaleが1になった次のレンダリングで画像を取得する。
// ブラウザ表示時の拡大率
const [scale, setScale] = useState(1);
// ボタン押下のフラグ
const [isTweetCanvas, setIsTweetCanvas] = useState(false);
useEffect(() => {
if (isTweetCanvas) {
// 画像を取得する前に画像の拡大率を1にする
if (scale != 1) {
setScale(1);
} else {
// 画像取得、ツイート処理(後述)
getDataUrlAndTweet();
// 拡大率、ツイートフラグを元に戻す
setScale(frameRef.current.offsetWidth / sceneWidth);
setisTweetCanvas(false);
}
}
}, [isTweetCanvas, scale]);
return (<>
<div ref={frameRef}>
<Stage
width={sceneWidth * scale}
height={sceneHight * scale}
scale={{ x: scale, y: scale }}
ref={stageRef}
>
...
</Stage>
</div>
<button onClick={setIsTweetCanvas(true)}>画像をツイート</button>
</>)
TwitterAPIの呼び出し
TwitterAPIのライブラリは見た感じスターが一番多かったTwitを使用。
※現時点(2021/12/19)でTwitter公式がオススメしているnode-twitter-api-v2は、DataURLを使った画像アップロードはできないので注意。
ブラウザで呼び出すとCORSエラーやfsが開けませんといったエラーになるので、BFFなどnode.jsが動く環境で呼び出す。Next.jsならmiddlewareで良さそう。
DataURLをそのまま入れるとエラーになるので冒頭の「data:image/png;base64」を削除する。
ブラウザ側
async function getDataUrlAndTweet() {
// axiosでミドルウェアを呼び出し
await axios.post("/api/tweet-canvas", {
dataUrl: stageRef.current
.toDataURL()
// 「data:image/png;base64」を削除
.replace(/^data:image\/png;base64,/, ""),
});
}
バックエンド側
大体Twitのサンプルをコピー
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// ブラウザ側から取得したデータ
const body = req.body;
// トークンを設定
// トークンの取得方法は「TwitterAPI トークン取得方法」などでググってください
var T = new Twit({
consumer_key: process.env.TWITTER_APP_KEY,
consumer_secret: process.env.TWITTER_APP_SECRET,
access_token: process.env.TWITTER_TOKEN,
access_token_secret: process.env.TWITTER_TOKEN_SECRET,
timeout_ms: 60 * 1000,
strictSSL: true,
});
// 画像をツイッターにアップロード
T.post(
"media/upload",
// ここにDataURLを入れる。
// 「media」ではなく「media_data」を使用する。
{ media_data: body.dataUrl },
function (err, data, response) {
var mediaIdStr = data.media_id_string;
var altText =
var meta_params = { media_id: mediaIdStr, alt_text: { text: altText } };
T.post(
"media/metadata/create",
meta_params,
function (err, data, response) {
if (!err) {
var params = {
// ツイート文
status: "loving life #nofilter",
media_ids: [mediaIdStr],
};
T.post("statuses/update", params, function (err, data, response) {
console.log(data);
});
}
}
);
}
);
おわり
これで画像付きツイートがツイートされているはず。
最初にnode-twitter-api-v2を使ったせいで上手くいかず色々悩んでしまった。
DataURLから「data:image/png;base64」を削除するのも忘れずに。