3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React-Konvaで作成したcanvasをTwitterに画像投稿する

Posted at

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」を削除する。

ブラウザ側

browser.tsx
  async function getDataUrlAndTweet() {
    // axiosでミドルウェアを呼び出し
    await axios.post("/api/tweet-canvas", {
      dataUrl: stageRef.current
        .toDataURL()
        // 「data:image/png;base64」を削除
        .replace(/^data:image\/png;base64,/, ""),
    });
  }

バックエンド側

大体Twitのサンプルをコピー

pages/api/tweet-canvas.ts
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」を削除するのも忘れずに。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?