1
/
5

【開発日誌 #11】React.jsを使って滑らかなアニメーションページを構築

要件

  • Webサイト内で画像を作成し、SNSでシェアをする
  • デザインにアニメーションを加え、フラワーアーティストが作る世界観をWebサイト上に再現する

公開URL

https://pola-ba-creativityinyou.jp/

背景と概要

ブランドの認知拡大、及びロイヤリティの向上を目的に、Webサイト上で作った花束に感謝の言葉を乗せてシェアするWebサイトを制作。

構成

  • LP:キャンペーン内容の表示
  • 画像作成画面:画像を作成し、SNSでシェアするページ
  • 受け取り画面:シェアされた画像をアニメーションと共に表示するページ

主な機能

コラージュ機能

好みの花の画像を選択し、複数の花の画像を重ね合わせて1枚の画像を生成
(画像の選択、削除、拡大、縮小、回転)

■花の選択/追加のコード
/js/creation/pages/Make.tsx

const addFlower = (flowerId) => {
        if (!edited) {
            setModalIsOpen(true);
            dispatch(
                makeEdited({})
            )
        }
        setImmediate(() => {
            canvasRef.current.addFlower(flowerId);
        })

    };

   const listItems = flowers.map((flower) => (
        <ListItem
            flower={flower}
            addFlower={addFlower}
            key={flower.id}
        />
    ));

/js/creation/components/Canvas.tsx

useImperativeHandle(ref, () => ({
        addFlower(flowerId){
            const flowerObj = addFlowerDataItem(flowerId)
            addFlowerToCanvas(flowerObj)
        },
    }));

    const addFlowerToCanvas = (flowerData) => {
        var imageObj = new Image();
        imageObj.onload = function () {
            var image = new Konva.Image({
                id: flowerData.id,
                x: flowerData.x,
                y: flowerData.y,
                scaleX: flowerData.scaleX,
                scaleY: flowerData.scaleY,
                rotation: flowerData.rotation,
                image: imageObj,
                draggable: true,
                globalCompositeOperation: "screen",

            });
            layer.add(image);
            image.on("transformend", function () {
                updateFlowerDataItem(image.attrs)
            });
            image.on("dragend", () => {
                updateFlowerDataItem(image.attrs)
            });

            selectCanvasItem(image)
        };
        imageObj.src = '/assets/images/flowers/small/'+flowerData.flowerId.toString().padStart(3,'0')+'.png';
    }
    
    const addFlowerDataItem = (flowerId)  => {
        const flowerObj = {
            id : uuid(),
            x: Math.floor(Math.random() * canvasWidth / 4),
            y: Math.floor(Math.random() * canvasHeight / 4),
            scaleX: 1,
            scaleY: 1,
            rotation: 0,
            flowerId : flowerId
        }
        dispatch(
            addCanvasDataItem({
                data : flowerObj
            })
        )
        return flowerObj
    }

■花の画像の削除のコード
/js/creation/components/Canvas.tsx

const deleteFlowerDataItem = (id) => {
        dispatch(
            deleteCanvasDataItem({
                id: id
            })
        )
    }

/js/features/creation/creationSlice.ts

deleteCanvasDataItem: (state, { payload }) => {
            state.canvasData = state.canvasData.filter(flowerData => {
                return flowerData.id !== payload.id
            })
        },

■transformerを設定してる部分のコード(拡大、縮小、回転konvaの機能を使ってるため拡大縮小回転自体の処理のコードはありません)
/js/creation/components/Canvas.tsx

transformer = new Konva.Transformer({
            enabledAnchors: [
                'top-left',
                'top-right',
                'bottom-left',
                'bottom-right',
              ],
        });
        layer.add(transformer);


プレビュー機能

作成された画像をプレビュー

■花束作成画面で作成後にプレビュー用の一枚画像を生成する箇所のコード
/js/creation/pages/Make.tsx

<div onClick={confirm} className={"m-buttonNext" + (!canvasData.length ? " hidden" : "")}>
                    NEXT
                </div>
const confirm = () => {
        if(!canvasData.length){
            alert('花が配置されていません');
            return;
        }
        dispatch(updateImageBase64({ image: canvasRef.current.getImage() }));
        history.push("/confirm");
    };

/js/creation/components/Canvas.tsx

getImage(){
            logo.opacity(1)
            transformer.nodes([]);
            stage.width(canvasWidth);
            stage.height(canvasHeight);
            stage.scale({ x: 1, y: 1 });
            const dataUrl = stage.toDataURL();
            fitStageIntoContainer()
            logo.opacity(0)
            return dataUrl
        }



SNSシェア機能

作成した画像をLINE、Facebook、Twitterでシェア

どのように構築したか

フロントエンド/バックエンド

実際に花を選んでいるようなスムーズな動きを再現するために、React.jsを使用してSPA(シングルページアニメーション)でサイトを構築しました。

SPAにすることによって画像のプレビューやページ遷移の読み込み時のタイムラグを感じさせない構造になっています。

画像読み込み時の処理は作成ページへの遷移段階で全ての読み込みを完了させておく処理にしました。ページ遷移時に画像が瞬時に表示されないなどのストレスが解消され、より滑らかなアニメーションが再現可能となりました。

■画像の先読みのコード

        const outroFlowerNums = [
            "002",
            "003",
            "005",
            "007",
            "008",
            "019",
            "022",
            "027",
            "012",
            "026",
            "011",
            "036",
            "010",
            "015"
        ];

        // 後の画面の画像プリロード
        let imgDir = "/assets/images/flowers/large/"

        // SP表示なら中サイズ画像
        if (window.matchMedia('(max-width: 767px)').matches) {
            imgDir = "/assets/images/flowers/medium/"
        }
        outroFlowerNums.forEach((num) => {
            const img = new Image()
            img.src = imgDir + num + ".png";
        });
    }

    useEffect(() => {
        preloadOutroImages()
    },[])


インフラ

SNSシェアや広告掲載を予定していたため、多数のアクセスによるサーバー費用の増大も無視できません。

今回使用したReact.jsは画像作成から完了前のプレビューまではブラウザ上で画像を保存することで、極力サーバーとの通信を減らし、使用料の低減を実現することができました。

最後に

細部に渡る微調整を繰り返すことによって、サイト自体のクオリティが格段に上がり、大変ご好評いただくことができました。一般ユーザーが気付かない部分までこだわり抜く姿勢を大変学ばせていただきました。

Invitation from 株式会社コムデ
If this story triggered your interest, have a chat with the team?
株式会社コムデ's job postings
11 Likes
11 Likes

Weekly ranking

Show other rankings
Like 岩佐駿's Story
Let 岩佐駿's company know you're interested in their content