ビルドFacebookのストーリークリエーターを使用してreactjsと


当時、私は自分自身の非常に大きなプロジェクトを構築する私の方法に乗っている、それは社会的なメディアのアプリだ.そして、私は私のアプリは、はい、それは人々が24時間後に自動的に隠されるものを共有することができます話機能だと思う非常に興味深い機能があります.私はそれの簡単なバージョンを構築することを決め、今日私はFacebookのストーリーの作成者の建物の経験を皆さんと共有したい.
生のデモ:https://trunghieu99tt.github.io/Facebook-story-mini/

スコープ
まず、スコープを定義しましょう.携帯電話上のFacebookアプリのストーリー機能は、それの小さな機能の多くは非常に巨大な機能ですが、Facebookのウェブサイト上のストーリー機能はありません.
ウェブサイトでは、我々は2つのオプションがあります.1はテキストストーリーで、2はテキストでイメージストーリーです.このブログでは、Facebookのウェブサイトのストーリー機能についてもっと簡単に考えます.
わかりました、もう少し少し行って、何をしなければならないかを見ましょう
  • テキストストーリー:中間の段落と変更可能な背景
  • イメージストーリー:ストーリーごとに1つのイメージと我々はまた、テキストブロック
  • を追加することができます
    それは簡単です、右?少なくともテキストストーリー機能.さて、次の部分に移りましょう

    2 .ツール、ライブラリ
    私はこの機能を開発するためにreactjsを使用して、テキストの話では十分ですが、イメージの話では、我々は追加/削除テキストブロック、変更方向、サイズ、…に対処するライブラリを見つける必要があります.そして、私は、24579152ファブリックがキャンバス要素の上にインタラクティブなオブジェクトモデルを提供するようになりました.私は、あなたが織物ウェブサイトに行って、読書を続ける前にそれについて読むほうがよいと思います.
    Fabric
    開始符号化
    あなたは、私には、私は作成反応アプリに固執する任意のボイラー板を使用することができます.私はあなたの人々が反応の基本的な知識を持っていると仮定し、どのように作成し、反応アプリを実行知っている.もう一つの小さなメモは、このプロジェクトでは、私はtypescriptを使用しますが、私は人々が入力スクリプトを知らないと思う、それは大したことではないので、それは小さなプロジェクトです.
    このプロジェクトでは、2つ以上のパッケージを追加する必要があります.
    次のコマンドを実行します.
    yarn add fabric fabricjs-react
    #or
    npm install fabric fabricjs-react
    
    OK、今、我々は行って良いです.
    次のステップに進む前に、フォルダの構造を定義しましょう.私たちは2つの主要なタイプのコンポーネントを持っていることを知っています.1はテキストまたはイメージストーリーを作成するストーリーフォームです.このようなフォルダ構造を作成します.

    定数フォルダーは、このアプリで使用するすべての定数値を保持します.

    3.1 .テキストストーリー
    テキストストーリーについては、それは簡単なものです、我々はちょうどdivとテキストを持っているその部門の中心に我々はまた、その部門の背景を変更することができます
    StoryFormで、そのフォルダにテキストと呼ばれるフォルダーを作成し、3つのファイルを作成します.TS(テキストファイル)、TextStory.モジュールです.CSSとTextStory.TSX

    テキストストーリーで.TSX :
    import { ChangeEvent, useState } from "react";
    
    import { BACKGROUND_LIST } from "../../../constants";
    
    import classes from "./textStory.module.css";
    
    const TextStory = () => {
        const [text, setText] = useState("");
        const [background, setBackground] = useState("#000");
    
        const onChangeText = (e: ChangeEvent<HTMLTextAreaElement>) => {
            const text = e.target.value;
            setText(text);
        };
    
        const saveToServer = () => {
            const data = {
                type: "text",
                background,
                text,
            };
            localStorage.setItem("data", JSON.stringify(data));
        };
    
        return (
            <div className={classes.root}>
                <aside className={classes.aside}>
                    <textarea
                        className={classes.textarea}
                        onChange={onChangeText}
                        rows={7}
                    />
                    <p>Change color</p>
                    <ul className={classes.backgroundList}>
                        {BACKGROUND_LIST.map((color) => {
                            return (
                                <li
                                    onClick={() => setBackground(color)}
                                    style={{
                                        background: color,
                                        cursor: "pointer",
                                        outline: `${
                                            color === background
                                                ? "2px solid blue"
                                                : ""
                                        } `,
                                    }}
                                ></li>
                            );
                        })}
                    </ul>
                    <button onClick={saveToServer}>Save</button>
                </aside>
                <div
                    className={classes.main}
                    style={{
                        background: background,
                    }}
                >
                    <p className={classes.text}>{text}</p>
                </div>
            </div>
        );
    };
    
    export default TextStory;
    
    上記のコンポーネントの完全なコードです.我々は、背景色を格納するために我々のテキストと状態を格納する状態があります.SaveToServer機能については、無視することができます、私たちは、このブログの後にそれに戻るでしょう.バックグラウンドのカラーリストでは、このプロジェクトでは、我々はそれをハードコード(しかし、あなたはそれを変更することができます色ピッカーまたは何をあなたがそれを改善したい)
    インデックスを作成します.定数フォルダ内のファイルをtsに設定します.
    export const BACKGROUND_LIST = [
        'linear-gradient(138deg, rgba(168,74,217,1) 0%, rgba(202,88,186,1) 55%, rgba(229,83,128,1) 100%)',
        'linear-gradient(138deg, rgba(55,31,68,1) 0%, rgba(115,88,202,1) 55%, rgba(97,0,30,1) 100%)',
        'linear-gradient(138deg, rgba(31,68,64,1) 0%, rgba(202,88,155,1) 55%, rgba(90,97,0,1) 100%)',
        'linear-gradient(138deg, rgba(14,33,240,1) 0%, rgba(88,202,197,1) 55%, rgba(11,97,38,1) 100%)',
        'radial-gradient(circle, rgba(238,174,202,1) 0%, rgba(148,187,233,1) 100%)',
        'linear-gradient(138deg, rgba(14,33,240,1) 0%, rgba(88,202,197,1) 55%, rgba(11,97,38,1) 100%)',
        'radial-gradient(circle, rgba(198,76,129,1) 12%, rgba(218,177,209,1) 27%, rgba(148,187,233,1) 100%',
        'linear-gradient(180deg, rgba(62,66,105,1) 0%, rgba(233,225,107,1) 55%, rgba(11,97,38,1) 100%)',
        'radial-gradient(circle, rgba(117,67,81,1) 2%, rgba(107,233,164,1) 37%, rgba(97,11,11,1) 100%)',
        '#2d88ff',
        '#ececec',
        '#6344ed',
        '#8bd9ff',
        'linear-gradient(315deg, rgba(255,184,0,1) 0%, rgba(237,68,77,0.7175245098039216) 61%, rgba(232,68,237,1) 78%)',
    ];
    
    スタイルファイルについては、私はそれをここに投稿しないように少し長いです.しかし、私はこのブログの最後にリンクをドロップするので、後でチェックすることができます.
    インデックスで.TSファイルは、1行だけ書く.
    export { default } from './TextStory';
    
    これがテキストストーリー形式の最終結果です

    テキストのデフォルトの色は白(私はCSSを使用して設定されますが、使用可能な色のリストを作成し、ユーザーが色を選択することができます).

    3.2 .イメージストーリー
    さて、これはこのブログの主な部分です、そして、それはより厳しいものです.
    これらのことをしなければならないからです.
  • 画像を表示します(このプロジェクトではURLから読みますが、マシンからアップロードするために変更できます)
  • テキストを追加:我々は、複数のテキストブロックを追加することができますし、各ブロックで、我々はそこにテキストを変更することができますドラッグ、回転、それを変更します.
  • 生地が遊びに来る時間です.
    ストーリー形式で、イメージと呼ばれるフォルダーを作成します.そのフォルダーで、imagestoryというファイルを作成します.TSX
    そこにコードを書きましょう
    import React, { ChangeEvent, useState } from "react";
    import { FabricJSCanvas, useFabricJSEditor } from "fabricjs-react";
    
    import classes from "./imageStory.module.css";
    
    const ImageStory = () => {
        const { editor, onReady } = useFabricJSEditor()
        return (
            <div className={classes.root}>
                <div className={classes.main}>
                    <FabricJSCanvas className={classes.canvas} onReady={onReady} />
                </div>
            </div>
        );
    };
    
    export default ImageStory;
    
    さて、フォームのイメージURLと送信関数を保持するフォームを追加します.
    import React, { ChangeEvent, useState } from "react";
    import { fabric } from "fabric";
    import { FabricJSCanvas, useFabricJSEditor } from "fabricjs-react";
    
    import classes from "./imageStory.module.css";
    
    const ImageStory = () => {
        const [image, setImage] = useState<string | null>(null);
        const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
    
        const { editor, onReady } = useFabricJSEditor();
    
        const submitImage = () => {
            if (image && image.startsWith("http")) {
                fabric.Image.fromURL(image, function (img) {
                    const canvasWidth = editor?.canvas.getWidth();
                    const canvasHeight = editor?.canvas.getHeight();
                    editor?.canvas.setWidth(500);
                    editor?.canvas.setHeight(500);
                    editor?.canvas.add(img);
                    const obj = editor?.canvas.getObjects();
                    obj?.forEach((o) => {
                        if (o.type === "image") {
                            o.scaleToHeight(canvasWidth || 100);
                            o.scaleToHeight(canvasHeight || 100);
                        }
                    });
    
                    editor?.canvas.centerObject(img);
                    setIsSubmitted(true);
                });
            }
        };
    
            const onChange = (e: ChangeEvent<HTMLInputElement>) => {
                const { value } = e.target;
                setImage(value);
            };
    
        return (
            <div className={classes.root}>
                <div className={classes.main}>
                    {!isSubmitted && (
                        <div className={classes.imageForm}>
                            <input type="text" onChange={onChange} />
                            <button onClick={submitImage}>Submit</button>
                        </div>
                    )}
                    <FabricJSCanvas className={classes.canvas} onReady={onReady} />
                </div>
            </div>
        );
    };
    
    export default ImageStory;
    
    我々は、我々のイメージURLを保存する州を持ちます
    私がイメージを提出しなかった時だけ、私がフォームを示したいので、私はそれに対処するためにissubmit状態を加えました.IsSubbMelete = falseの場合、イメージフォームのみを表示します.
    それではonsubmit関数を見てみましょう.
    const submitImage = () => {
            if (image && image.startsWith("http")) {
                fabric.Image.fromURL(image, function (img) {
                                    // Note that img now will be an fabric object
    
                                    // get width and height of canvas container
                    const canvasWidth = editor?.canvas.getWidth();
                    const canvasHeight = editor?.canvas.getHeight();
    
                                    // add image object 
                    editor?.canvas.add(img);
    
                                    // get all fabric objects in editor
                    const obj = editor?.canvas.getObjects();
    
                                    // This will not optimal way, but currently
                                    // we only have one image, so It should be fine
                    obj?.forEach((o) => {
                        if (o.type === "image") {
                                                    // resize image to fit with editor width and height
                            o.scaleToHeight(canvasWidth || 100);
                            o.scaleToHeight(canvasHeight || 100);
                        }
                    });
    
                    editor?.canvas.centerObject(img);
                    setIsSubmitted(true);
                });
            }
        };
    
    ファブリックはURLから読み込みイメージをサポートし、ファブリックオブジェクトを返します.コールバック関数では、現在のエディタにそのオブジェクトを追加します.イメージが我々のエディタ領域に合わないかもしれないように、イメージが現在その初期のサイズを保つということに留意する1つのもの、我々はエディタ領域に合うようにそれをリサイズする必要があります.私の現在の解決策は、エディタのすべてのオブジェクトを取得し、それがイメージの場合、それをサイズ変更することです.我々はストーリーごとに1つのイメージを持っているので、この解決策はうまくいきます.
    今、あなたのアプリケーションを実行し、フォームに有効なイメージのURLを貼り付けると送信を送信すると、我々はそれがエディタ領域でイメージを示して表示されます.そして、あなたはそのイメージ(ドラッグ、リサイズ、回転…)と対話することができます.グッドジョブ.😄
    我々は最初の目標を終えました.
    生地もテキストブロックをサポートしていますので、エディターにテキストを追加することは簡単です.
    Imagestoryコンポーネントを変更します
    import React, { ChangeEvent, useState } from "react";
    import { fabric } from "fabric";
    import { FabricJSCanvas, useFabricJSEditor } from "fabricjs-react";
    
    import classes from "./imageStory.module.css";
    
    const ImageStory = () => {
        const [image, setImage] = useState<string | null>(null);
        const [isSubmitted, setIsSubmitted] = useState<boolean>(false);
    
        const { editor, onReady } = useFabricJSEditor();
    
        const onAddText = () => {
            try {
                editor?.canvas.add(
                    new fabric.Textbox("Type something...", {
                        fill: "red",
                        fontSize: 20,
                        fontFamily: "Arial",
                        fontWeight: "bold",
                        textAlign: "center",
                        name: "my-text",
                    })
                );
                editor?.canvas.renderAll();
            } catch (error) {
                console.log(error);
            }
        };
    
        const onChange = (e: ChangeEvent<HTMLInputElement>) => {
            const { value } = e.target;
            setImage(value);
        };
    
        const submitImage = () => {
            if (image && image.startsWith("http")) {
                fabric.Image.fromURL(image, function (img) {
                    const canvasWidth = editor?.canvas.getWidth();
                    const canvasHeight = editor?.canvas.getHeight();
                    editor?.canvas.add(img);
                    const obj = editor?.canvas.getObjects();
                    obj?.forEach((o) => {
                        if (o.type === "image") {
                            o.scaleToHeight(canvasWidth || 100);
                            o.scaleToHeight(canvasHeight || 100);
                        }
                    });
    
                    editor?.canvas.centerObject(img);
                    setIsSubmitted(true);
                });
            }
        };
    
        return (
            <div className={classes.root}>
                {isSubmitted && (
                    <aside className={classes.aside}>
                        <button onClick={onAddText}>Add Text</button>
                        <button onClick={saveToServer}>Save</button>
                    </aside>
                )}
    
                <div className={classes.main}>
                    {!isSubmitted && (
                        <div className={classes.imageForm}>
                            <input type="text" onChange={onChange} />
                            <button onClick={submitImage}>Submit</button>
                        </div>
                    )}
                    <FabricJSCanvas className={classes.canvas} onReady={onReady} />
                </div>
            </div>
        );
    };
    
    export default ImageStory;
    
    OnAddText関数を見てみましょう.新しいファブリックを呼び出すことで新しいファブリックTextBoxオブジェクトを作成します.textbox ().
     editor?.canvas.add(
                    new fabric.Textbox("Type something...", {
                        fill: "red",
                        fontSize: 20,
                        fontFamily: "Arial",
                        fontWeight: "bold",
                        textAlign: "center",
                        name: "my-text",
                    })
                );
     editor?.canvas.renderAll();
    
    私たちが通過したparamsについて説明させてください:最初の引数は最初のテキストです、そして、2番目のものはそのテキストボックスのテキストのための構成を含むオブジェクトです.上記のコードでは、フォントサイズが20で、フォントファミリがarialである赤い太字のテキストを含むテキストを作成します.テキストは、テキストボックスに配置されます.テキストボックスを作成した後、エディタを使用してエディタに追加します.キャンバス.加えるそして最後に、最新の状態を得るためにエディタを再描画します.
    これが最終結果です.

    さて、今まで、我々はイメージとテキストを加えることで終わりました.何が削除についてですか?生地で、それはケーキのようなものです、ファブリックは我々がちょうど我々が削除したいオブジェクトを渡すために必要とする除去方法を持ちます、そして、ファブリックは我々のためにそれを取り扱うでしょう.しかし、どのようにオブジェクトを削除するメソッドを削除するか?
    我々は物事を削除する方法を覚えて、我々は最初に、右を選択しますか?それで、ファブリックは「GetActiveObject」と呼ばれているメソッドを持ちます、そのメソッドを使用することによって、我々はすべての選ばれたオブジェクトを得ることができます.問題を解決するには、すべてのアクティブなオブジェクトを取得する必要がありますし、ループを介してループを呼び出すメソッドを呼び出します.
    このように:
    const deleteSelected = () => {
            editor?.canvas.getActiveObjects().forEach((object) => {
                editor?.canvas.remove(object);
            });
        };
    
    さて、我々はすべての基本的な機能で行われます.さあ、次のステップに進みましょう.

    3.3 .データの保存と表示
    我々は追加することができます、これまでのところ移動するが、我々のアプリは、相互作用のものではない、我々は我々のデータベースに格納し、データベース右からデータを表示する必要がありますか?それで、どのように、我々はFuffjsでそれをすることができましたか?
    この小さなプロジェクトでは、ローカルストレージを使用してデータベースを簡単にします.データの形については、テキストは最良の方法だと思います.オブジェクトを作成するだけでJSONを使う必要があります.そのオブジェクトで文字列化します.
    テキストストーリー機能では、我々は多くのことをする必要はありません.我々が格納するために必要な情報は、テキストコンテンツや背景色です.
    const saveToServer = () => {
            const data = {
                background,
                text,
            };
            localStorage.setItem("data", JSON.stringify(data));
        };
    
    この関数をテキストストーリーフォームコンポーネントに追加し、OnClickイベントがサーバーに保存されているボタンを追加します.
    今では、イメージの話に、再び、ファブリックのおかげで、我々は我々のエディタのオブジェクトデータをJSONに変換するToJSON ()と呼ばれるメソッドを持っている今、我々はちょうどJSONを呼び出す必要があります.変換されたオブジェクトデータを使用して、ローカルストレージに保存します
    const saveToServer = () => {
            const objects = editor?.canvas.toJSON();
            if (objects) {
                localStorage.setItem("data", JSON.stringify(objects));
            }
        };
    
    データを表示するには、まずローカルストレージとJSONからデータを取得します.そのデータをパースします
    const showResultFromServer = () => {
            const json = localStorage.getItem("data");
            if (json) {
                const objects = JSON.parse(json);
                  // store it to component state. 
            }
        };
    
    テキストストーリーでは、データを解析した後、我々はテキストコンテンツと背景色を持っています.データを表示するには、右側を使用して簡単ですか?それがファブリックによって制御されたので、我々の唯一の懸念はイメージ・ストーリーを示す方法です.幸いにも、ファブリックは“LoadFromjson”と呼ばれるメソッドを持っています、我々はTOJSONメソッドから得られたJSONデータを渡す必要があります、そして、ファブリックは我々のために残りを取り扱います.
    例えば、次のようになります.
    editor.canvas.loadFromJSON(
                    data,
                    () = {}
                );
    
    LoadFromjsonには2つのparamsがあり、最初はJSONデータで、2番目はコールバック関数です.JSONが解析され、対応するオブジェクト(この場合、それらはイメージオブジェクトとテキストオブジェクトである)が初期化されるときにコールバック関数が呼び出されます.コールバック関数を必要としないので、今のところ、空の関数とします.
    OK、それで、我々はすべてそれでしています.
    完全なソースコードはここにあります.

    このチュートリアルでは、私はこのブログを同時に学習しています.このブログで言及したものを扱うより良い方法があるかもしれません.何か提案があれば、コメントをドロップすること自由に感じてください.どうもありがとう.