こんにちは、広野です。
最近の WEB サイトやスマホアプリを使っていて、ユーザになるべく手間をかけさせない簡易なフィードバックフォームがあるのを見かけませんか?
あれって、開発元がユーザがそのサイトやアプリをどう思っているのか知りたくて作っているんです。(当たり前か)
ユーザはあまり気にしないかもしれませんが、開発元は作ったサイトやアプリがユーザに受け入れられているのかめっちゃ気にしてまして、ユーザからのフィードバック以外にそれを知る術がありません。
しかし、フィードバックフォームの入力項目が多いと途端にユーザは書いてくれなくなるので、極力項目を削ぎ落したフォームにせざるを得ないのが実情です。もちろん、開発元はもっと根掘り葉掘り聞きたいんですけどね。
本記事では、そんな簡易フィードバックフォームを React と MUI で画面開発してみましたので、コードを紹介します。フィードバックデータを受け取るバックエンドは AWS で構築していますが、記事が長くなってしまうので省略します。
つくったもの
スクリーンショットを貼り付けます。
あるユーザ操作の終了後、画面①の右下に、「フィードバックを書く」というさりげないボタンがあります。(邪魔にならないように)
ボタンを押すと、簡易フィードバック用のダイアログが画面にオーバーレイする形で表示されます。
画面③、「クイズはいかがでしたか?」ダイアログの評価レーティングが星の数で表されるので、ユーザが評価した位置の星を押してもらいます。メッセージは強制ではないですが、何か書いてくれたら嬉しいので用意しています。
送信ボタンを押すと、画面④に遷移し、元々そこにいたアバターがフィードバックのお礼を言います。
ちなみにこの画面にはアバターを表示しないオプションがあり、その場合はアバターにお礼を言わせることができないため、Snackbar と言われる小さいメッセージ欄を画面下部に表示させます。これは 6 秒後に自動で消えるようにしています。
使用した MUI パーツ
この画面を実現するにあたり、多くの MUI のパーツを使っています。
MUI とは、マテリアルデザインに基づいた WEB 画面を開発するための部品を提供してくれる、React 用モジュールです。
以下に、使用した MUI パーツを列挙します。
画面パーツ
- Floating action button
「フィードバックを書く」ボタンです。
- Dialog
「クイズはいかがでしたか?」ダイアログ画面の枠や、その表示コントロールに使っています。
- Rating
星の数で表す、ユーザ評価入力欄です。
- TextField
メッセージ入力欄です。
- Button
「閉じる」や「送信」ボタンです。
- Icons
各ボタンには、その意味をイメージするためのアイコンを入れています。
- Snackbar
アバター非表示時のお礼メッセージ表示用です。
- Alert
アバター非表示時のお礼メッセージ表示用です。本記事では Snackbar と組み合わせて使用します。
レイアウト調整
- Box
画面に任意のサイズ、位置の表示枠をつくるためのレイアウト調整用パーツです。
「フィードバックを書く」ボタンを画面右下に表示させるために使用しています。
- Stack
画面内の1列、1行に表示させる画面パーツ(HTMLタグ単位)をどのように並べるか、コントロールするパーツです。超便利。
トランジション(アニメーション)
- Zoom
あるユーザ操作が終わってから 2 秒後に「フィードバックを書く」ボタンが「ズームイン」するアニメーションを入れています。後からアニメーション付きで表示させることで、ユーザにボタンがあることを気付かせたい意図があります。
本記事では省略しますが、他にも Fade や Slide などのアニメーションもところどころで使用しています。例えば、アバターはスライドインして表示させる、吹き出しによるメッセージは少し遅らせて表示する、などです。
React コード
実際のコードは量が多いため、関係するところだけ抜粋します。
import するモジュールは省略します。MUI の公式ドキュメント通りです。
すみません、MUI ではない通常 HTML タグのCSS やステートの定義も省略させて頂きます。setXxx という関数は xxx という名前のステートを更新するものとご理解ください。
「フィードバックを書く」ボタン
//Dialogオープン const handleOpenDialog = () => { setIsDialogOpen(true); }; //中略 {/* Floating Action Button to show the feedback dialog */} <Zoom in={isFeedbackFabOpen} mountOnEnter unmountOnExit style={{transitionDelay:"2000ms"}}> <Box role="presentation" sx={{position:"fixed",bottom:16,right:16}}> <Fab onClick={handleOpenDialog} variant="extended" size="small" color="info" sx={{fontFamily:"inherit",my:3}} aria-label="show-feedback"> <RateReviewIcon />フィードバックを書く </Fab> </Box> </Zoom>
- あるユーザ操作によりこの画面が表示完了になったタイミングでステート isFeedbackFabOpen が true になります。
そうすると 2 秒待ってから Zoom が発動し、Fab、つまり「フィードバックを書く」ボタンがズームインします。 - Box により、「フィードバックを書く」ボタンの表示位置を「下から 16 px、右から 16 px」に指定しています。Fab は、デフォルトで既存画面にオーバーレイして表示されます。
- 「フィードバックを書く」という文字列の左には、RateReviewIcon というアイコンを表示させています。
- Fab には onClick で handleOpenDialog という関数を呼び出すようにしています。ボタンを押すと、ダイアログを表示させる関数です。単純に、ダイアログの表示をコントロールするフラグにしているステート isDialogOpen を true にするだけです。
「クイズはいかがでしたか?」ダイアログ
//Dialog Styles const textfieldStyles = { fontFamily: "inherit", backgroundColor: "white" }; const textlabelStyles = { fontFamily: "inherit" }; //Dialogクローズ const handleCloseDialog = () => { setIsDialogOpen(false); }; //フィードバック送信関数 const putFeedbackQuiz = async (id) => { //ダイアログオープン setIsDialogOpen(false); //Fabクローズ setIsFeedbackFabOpen(false); //スナックバーで御礼 (if Avator disabled) setIsSnackbarOpen(true); //アバターからフィードバックを御礼 (if Avator enabled) setAvatorMessage(false); setAvatorFeedbackReply("フィードバックありがとね!サービス改善がんばるからねー!"); setAvatorEmotion("glad"); //アバターの表情を笑顔に変える //フィードバック記録 putFeedback(id, username, feedbackMessageQuiz, feedbackRatingQuiz, headerdata); }; //中略 {/* Feedback form */} <Dialog open={isDialogOpen} onClose={handleCloseDialog} hideBackdrop={true} keepMounted> <DialogTitle sx={{fontFamily:"inherit"}}>クイズはいかがでしたか?</DialogTitle> <DialogContent> <Stack direction="column" justifyContent="flex-start" alignItems="center" spacing={1.5}> <Stack direction="row" justifyContent="center" alignItems="center" spacing={1.5}> <span className="smalltxt50">Bad</span> <Rating name="rate-quiz" value={feedbackRatingQuiz} icon={<StarRoundedIcon />} emptyIcon={<StarOutlineRoundedIcon />} onChange={(event, newValue) => {setFeedbackRatingQuiz(newValue)}} precision={1} /> <span className="smalltxt50">Good</span> </Stack> <TextField name="message-quiz" value={feedbackMessageQuiz} onChange={(e) => {setFeedbackMessageQuiz(e.target.value)}} id="message-quiz" label="メッセージ" fullWidth multiline minRows={3} maxRows={3} variant="outlined" margin="dense" size="small" sx={{my:2}} InputProps={{style:textfieldStyles}} InputLabelProps={{style:textlabelStyles}} /> </Stack> </DialogContent> <DialogActions> <Button onClick={handleCloseDialog} variant="text" size="small" startIcon={<RemoveCircleRoundedIcon />} sx={{mr:3}}>閉じる</Button> <Button id={"quiz-" + quizgroup} onClick={(e) => {putFeedbackQuiz(e.target.id)}} disabled={(feedbackRatingQuiz) ? false : true} variant="contained" size="small" endIcon={<SendIcon />}>送信</Button> </DialogActions> </Dialog>
- 前述「フィードバックを書く」ボタンを押すことにより、ダイアログを表示するフラグ isDialogOpen が true になることでダイアログが表示されます。
- ダイアログの中は DialogTitle、DialogContent、DialogActions というパーツに分かれており、それぞれタイトル、コンテンツ、アクションを定義していきます。
- コンテンツには、入力欄を表示させています。Rating で星を、TextFieldでメッセージ入力欄を表示させます。
- 「閉じる」ボタンを押すと handleCloseDialog 関数を呼び出し、ダイアログを表示するフラグ isDialogOpen を false にします。
- 「送信」ボタンを押すと putFeedbackQuiz 関数を呼び出し、いくつかのフラグを変更します。(コード内参照)
putFeedback 関数を呼び出し、送りたい情報を渡します。putFeedback 関数は他の画面からも使用するため別コンポーネントでアプリ内共通関数として定義しており、単に axios でフィードバックデータを受け取るためのバックエンドにデータを送るだけのものとなっています。
「フィードバックありがとう!」メッセージ
アバターを非表示にするオプションを選択した場合、画面④’ のように画面下部に緑色の「フィードバックありがとう!」メッセージ (Snackbar) を表示しています。
import MuiAlert from '@mui/material/Alert'; //Alert Config const Alert = React.forwardRef(function Alert(props, ref) { return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />; }); //中略 {/* Thank you message for sending feedback if Avator is disabled */} <Snackbar open={isSnackbarOpen} autoHideDuration={6000} onClose={handleCloseSnackbar} anchorOrigin={{vertical:"bottom",horizontal:"center"}}> <Alert onClose={handleCloseSnackbar} severity="success" sx={{width:"100%"}}>フィードバックありがとう!</Alert> </Snackbar>
- 「送信」ボタンを押したときに Snackbar を表示するフラグ isSnackbarOpen を true にします。
- Snackbar で Alert を囲むことで、Alert のデザインを Snackbar で使用しています。
- Snackbar は 6 秒後に自動的に表示が消えますが、クリックしても消えるように Alert の onClose で Snackbar を表示するフラグ isSnackbarOpen を false に変更するようにしています。
まとめ
いかがでしたでしょうか?
アプリのロジック的には大したことないのですが、見た目の微調整に苦労しました。MUI パーツのオプションパラメータは、試行錯誤の上設定したものになっています。
本記事が皆様のお役に立てれば幸いです。