Try the demo here!
- スマホのブラウザ環境で動作すること ✅
- カード下の左にスキップボタン、右にいいね!ボタンを表示する ✅
- スキップボタンをタップしたときはカードが左に流れるアニメーションが実行され、次のカードが表示される ✅
- いいね!ボタンをタップしたときはカードが右に流れるアニメーションが実行され、次のカードが表示される ✅
- すべてのカードを仕分けできたら empty 画面が表示される ✅
- テストを書く ✅
- スワイプでカードを仕分けできる ✅
- カードの下部をタップすると詳細画面が表示される ✅
- API サーバーと連携する ✅
② 前回作成したTinder-UIからの変更点
- 前回はブランチを変えずに全ての開発を行っていたので今回は機能毎にブランチを切り
イシュードリブンな開発
で進めていく。 - React の考え方でもある
単一責任
+Reusable
なコンポーネント作りを重視して開発を進めていく。 - テスト開発(テストを書く->テストを成功させるためのコードを書く->リファクター)を取り入れる。
- コンポーネントが正しくブラウザー上にレンダリングされているか(
Unit Test
)。 - ボタンを押した時にカードが正しくスワイプされているかどうか(
Integration Test
)。
- コンポーネントが正しくブラウザー上にレンダリングされているか(
master
ブランチにコードが push された際にGitHub Actionsを使用してテストからデプロイメントまでを自動化させる CICD の導入。ホスティングサービスにはFirebaseを採用。- 使用言語、ライブラリーの変更。詳しくは下記 ④ に記載。
- react
- typescript
- emotion
- emotion-reset
- react-spring
- react-use-gesture
- react-icons
- unsplash-js
- react-spinners
難しかったことは細かい点が4つほどありました。
- 速度をつけずにゆっくりスワイプすると、少々動くがまた元の位置に戻る。
- スクリーンをプッシュしている状態+素早くほんの少しだけスワイプするとスワイプされてしまう。
ブラウザーではディフォルトでタッチアクション、つまりズームしたり、タップしたりする動作の読み取りがONとなっているため、ブラウザーの解釈で自動的に画面上を更新してしまっているため。 この場合は、横にスライドしている動作をスクロールと認識してしまっているんだと思われる。
CSSプロパティーのtouch-action
をnone
にしてあげると、ブラウザーのタッチアクションの動作の読み取りをOFFにして、コード側にその裁量権が与えられるので、うまくいく。
② react-springとreact-use-gestureがどのように組み合わさり、スワイプ機能を実現しているのか?
参考にしたコードはreact-springのドキュメントにも記載してあるこちらの例です。
react-spring
のuseSprings()とreact-use-gesture
のuseGesture()という関数たちが何をしているのかということを理解しているとスッと入ってくると思いました。
useSprings()
は指定した数全てのオブジェクトにスプリングの機能(アニメーション)を与えます。この場合は一枚一枚のカードというオブジェクトに。
返り値は二つあり、ここでいうprops
という中身はオブジェクトArrayで一つ一つのオブジェクト内にはto()
で指定した、プロパティー(x,y,scaleなど)がspringValue
というスプリングアニメーション専用のタイプに変換され返ってきます。このプロパティーを変化させることでアニメーションを実現化せています。その値を変化させる事ができるのが二つ目の返り値であるset()
という関数になります。react
のuseState()
みたいなものです。
const to = i => ({ x: 0, y: i * -4, scale: 1, rot: -10 + Math.random() * 20, delay: i * 100 })
const [props, set] = useSprings(cards.length, i => ({ ...to(i), from: from(i) }))
useGesture()
という関数はユーザーが画面操作時に特定の動作(ここではドラッグされた時)が行われた瞬間、瞬間に呼び出され、コールバックの引数にはその瞬間時のスピードや位置などの情報が入ったオブジェクトが入ります。
const bind = useGesture(({ args: [index], down, delta: [xDelta], distance, direction: [xDir], velocity }) => {
..省略..
set(
return { x, rot, scale, ..省略.. } }
})
..省略..
})
useDrag()
とuseGesture()
の主な違いはuseDrag()
はドラッグ動作だけに反応するが、useGesture()
は複数の動作を一度に組み合わせて使う事ができる。useDrag()
+ usePinch()
をカードコンポーネントに与えるなど。
さてもう一度何が起こっていたかという話に戻ると、
- ユーザーがスワイプ動作をする。
- そのスワイプ動作の瞬間の値が
useGesture()
によって取得される。 - その値を使い、ユーザーが何をしているのか(クリックされている、左に動いているなど)という事を判断し、その状況によって
useSprings()
で作り出したプロパティーの値をset()
で変えていくことによって動き(カードがSwipeされる)を作り出している。
*現在ではto()
が推奨されている。
上記の例から抜粋させてもらいました。
style={{ transform: interpolate([x, y], (x, y) => `translate3d(${x}px,${y}px,0)`) }}
なぜこれでは動作しないのか?
style={{ transform: `translate3d(${x}px,${y}px,0)` }}
結論から先に言うとinterpolate()
はダイナミックなアニメーションをさせたい時に使う。
CSSアニメーションには大きく分けて二つのアニメーションが存在している。
-
Staticアニメーション
opacity
を0から1にしたり、translateY
で100px上に動かしたりするなど、ブラウザーが全て行う単純な動作。 -
Dynamicアニメーション
Dynamicアニメーションとは反対に複雑なアニメーションで主に
Javascript
で操作してアニメーションを行う。なのでユーザーの動作(スワイプ動作など)に合わせて複雑なアニメーションを作り出すことが可能となる。
以上の理由から、複雑なアニメーション(ダイナミックなアニメーション)を行いたい時はinterpolate()
を使う。
上記のinterpolate
の理解でいいと思うのですが、もう一歩深く調べたのでシェアします。
interpolate
という英語の意味は 補間(ほかん)する
という意味です。
内挿(ないそう、英: interpolation)や補間(ほかん)とは、ある既知の数値データ列を基にして、そのデータ列の各区間の範囲内を埋める数値を求めること、またはそのような関数を与えること。 またその手法を内挿法(英: interpolation method)や補間法という。
要は一般的には、あるデーターを基に不特定な部分を予測する事です。
アニメーション中での使われ方としては、ある瞬間とある瞬間の動きを定めてその間の動きは自動的に計算されるという事らしいです。
要は滑らかにしているという事です。例としてわかりやすいのは、CSSの@keyframes
です。
この記事では動画の補間ソフトフェアを使いフレームレートが低い動画を補間して滑らかにすることによって動画が自然になっている事がわかります。
以上のことを踏まえてからもう一度コードを見ると、理解が深まりませんか?
style={{ transform: interpolate([x, y], (x, y) => `translate3d(${x}px,${y}px,0)`) }}
dependenciesのインストールに大きな時間がかかっていた。
yarn --prefer-offline
を使いyarn cacheのディレクトリーにすでにダウンロードされているキャッシュをなるべく使い、もし既存のキャッシュに存在しない場合にだけサーバーからダウンロードするという形を取る事でワークフローのトータル時間が減少した。
以上が私が感じた難しかったこと学んだことになります。
どこか間違っている点があればご指摘ください 🙇♂️