カテゴリー
サインイン 新規登録

間違いや改善の指摘

内容の技術的な誤り・誤字脱字やミスのご報告・解説やトピックの追記/改善のご要望は教材をさらに良くしていく上でとても貴重なご意見になります。

少しでも気になった点があれば、ご遠慮なく投稿いただけると幸いです🙏

実際には誤りではなく勘違いであっても、ご報告いただけることで教材のブラッシュアップにつながります。

質問ポリシー①

教材受講者みなさんのスムーズな問題解決のために、心がけていただきたいことがあります。

教材の内容に関する質問を投稿しましょう

教材の内容に関係のない質問や教材とは異なる環境・バージョンで進めている場合のエラーなど、教材に関係しない質問は推奨していないため回答できない場合がございます。

その場合、teratailなどの外部サイトを利用して質問することをおすすめします。教材の誤字脱字や追記・改善の要望は「文章の間違いや改善点の指摘」からお願いします。

8-9

スワイプ機能の実装

今回のパートではHammer.jsを使ってスワイプ機能を実装していきます。

本パートのゴール

本パートでは、下記の動画のようにスワイプ機能を実装します。

ゴールまでの流れ

  1. スワイプ機能のJSファイルを作成
  2. swipe.js の読み込み
  3. スワイプページにアクセスしたときにswipe.jsの処理を実行
  4. $(funtion(){...}) を追加
  5. カードの配置の実装
  6. カードを移動したときの処理を実装
  7. スワイプし終わった後の処理を実装

本パートでは、JavaScriptのコードが多く出てきます。参考のリンクを掲載しているので、リンクを参照しながら学習を進めましょう。

1. スワイプ機能のJSファイルを作成

まず、スワイプ機能のJSファイルを作成します。

以下のコマンドを実行してください。

console
Copied!
touch app/javascript/src/swipe.js

2. swipe.js の読み込み

次にswipe.jsファイルを読み込むコードを追加します。app/javascript/packs/application.jsに以下のコードを追加してください。

app/javascript/packs/application.js
12345678910111213141516171819202122232425
Copied!
import 'bootstrap'; import 'hammerjs'; import '../stylesheets/application'; import '@fortawesome/fontawesome-free/js/all'; // This file is automatically compiled by Webpack, along with any other files // present in this directory. You're encouraged to place your actual application logic in // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") require("src/profile_image_upload"); // ここに追加 require("src/swipe") // Uncomment to copy all static images under ../images to the output folder and reference // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) // or the `imagePath` JavaScript helper below. // // const images = require.context('../images', true) // const imagePath = (name) => images(name, true)

3. スワイプページにアクセスしたときにswipe.jsの処理を実行

それでは、いよいよJavaScriptのコードを書いていきます。まずスワイプ機能はスワイプページでしか処理を実行しないので、スワイプページにいるときだけ処理を実行できるようにします。

app/javascript/src/swipe.js に以下のコードを追加してください。

app/javascript/src/swipe.js
123
Copied!
if(location.pathname == "/users") { }

location.pathnameで現在のページURLのパスを参照します。http://localhost:3000/users にアクセスしたら、/usersを参照します。

そのため、if(location.pathname == "/users")とすることで、現在のページURLが/usersであればif式以下の処理を実行するという条件式になります。

4. $(funtion(){...}) を追加

次にjQuery のコードを追加していきます。

最初に、以下のコードを追加します。

js
1
Copied!
$(function(){ ... })

このコードはページの読み込みが完了し、DOMの構築が完了した時点でfunction()内のコードを実行します。もしこのコードがなければ、ページの読み込み途中にswipe.jsのコードが実行されてしまうため、このコードを書きます。

それでは、swipe.jsに以下のコードを追加してください。

app/javascript/src/swipe.js
123456789
Copied!
if(location.pathname == "/users") { // ここから追加 $(function () { }); // ここまで }

5. カードの配置の実装

次にカードの配置の実装をしていきます。

実装のイメージとしては、以下の画像のようにカードを重ねて配置していきます。

それでは、swipe.jsに以下のコードを追加してください。

app/javascript/src/swipe.js
12345678910111213141516171819
Copied!
if(location.pathname == "/users") { $(function () { // ==========ここから追加する========== let allCards = document.querySelectorAll('.swipe--card'); function initCards() { allCards.forEach(function (card, index) { card.style.zIndex = allCards.length - index; card.style.transform = 'scale(' + (20 - index) / 20 + ') translateY(-' + 30 * index + 'px)'; card.style.opacity = (10 - index) / 10; }); } initCards(); // ==========ここまで追加する========== }); }
js
1
Copied!
let allCards = document.querySelectorAll('.swipe--card');

まずこのコードで、.swipe--card要素のNodeListを返します。つまりスワイプするユーザーのカード情報を全て取得します。

参考:MDN web docs - Document.querySelectorAll()

js
1234567
Copied!
function initCards() { allCards.forEach(function (card, index) { card.style.zIndex = allCards.length - index; card.style.transform = 'scale(' + (20 - index) / 20 + ') translateY(-' + 30 * index + 'px)'; card.style.opacity = (10 - index) / 10; }); }

initCards関数では、まず先ほど定義したallCardsforEach()を使って、配列の各要素に対して1度ずつ実行します。

参考:MDN web docs - Array.prototype.forEach()

forEach()以下では3つのstyleを設定しています。

  • z-index
  • transform
  • opacity

z-indexは重なりの順序を指定します。

参考: z-index

transformでカードを縮小や移動をさせています。

参考: MDN web docs - transform

opacityは要素の透明度を指定します。

参考: opacity

それでは、現状のスワイプページを確認しましょう。

http://localhost:3000/users にアクセスしてください。

アクセスすると、上記のようなページが表示されます。まだスワイプはできません。

6. カードを移動したときの処理を実装

次にカードを移動したときの処理を実装していきます。

実装する際は、panというイベントを利用します。

参考: Hammer.js - Hammer.Pan(options)

panはHammer.jsで使えるイベントの名前で、画面を押したまま指を動かす動作のことです。

それでは、swipe.jsに以下のコードを追加してください。

app/javascript/src/swipe.js
1234567891011121314151617181920212223242526272829303132333435363738394041
Copied!
if(location.pathname == "/users") { $(function () { let allCards = document.querySelectorAll('.swipe--card'); // この行を追加 let swipeContainer = document.querySelector('.swipe'); function initCards() { allCards.forEach(function (card, index) { card.style.zIndex = allCards.length - index; card.style.transform = 'scale(' + (20 - index) / 20 + ') translateY(-' + 30 * index + 'px)'; card.style.opacity = (10 - index) / 10; }); } initCards(); // ==========ここから追加する========== allCards.forEach(function (el) { let hammertime = new Hammer(el); hammertime.on('pan', function (event) { if (event.deltaX === 0) return; if (event.center.x === 0 && event.center.y === 0) return; el.classList.add('moving'); swipeContainer.classList.toggle('swipe_like', event.deltaX > 0); swipeContainer.classList.toggle('swipe_dislike', event.deltaX < 0); let xMulti = event.deltaX * 0.03; let yMulti = event.deltaY / 80; let rotate = xMulti * yMulti; event.target.style.transform = 'translate(' + event.deltaX + 'px, ' + event.deltaY + 'px) rotate(' + rotate + 'deg)'; }); }); // ==========ここまで追加する========== }); }
js
123
Copied!
let hammertime = new Hammer(el); hammertime.on('pan', function (event) {

上記のコードで、インスタンスを生成してpanというイベントを登録しています。

参考: Hammer.js - Getting Started

js
12
Copied!
if (event.deltaX === 0) return; if (event.center.x === 0 && event.center.y === 0) return;

deltaXはHammer.jsのイベントオブジェクトで、X軸の動きのことです。

centerもHammer.jsのイベントオブジェクトで、ポインタの位置を表します。

参考: Hammer.js - General API

※ Hammer.jsのイベントオブジェクトは上記の公式ドキュメントを参照してください

つまり上記のコードでは、カードを動かしていない場合は、処理を終了するようにreturnを返しています。

js
12
Copied!
swipeContainer.classList.toggle('swipe_like', event.deltaX > 0); swipeContainer.classList.toggle('swipe_dislike', event.deltaX < 0);

上記のコードでswipeContainerに対象とするクラス名があれば除去し、クラス名がなければ追加します。

今回の場合、右に動かした場合はswipe_likeクラスの追加または除去、左に動かした場合は、swipe_dislikeクラスの追加または除去します。

つまり、上記の2行でスワイプしたときのハートや×のアイコンを表示させます。

参考: MDN web docs - Element.classList

js
12345
Copied!
let xMulti = event.deltaX * 0.03; let yMulti = event.deltaY / 80; let rotate = xMulti * yMulti; event.target.style.transform = 'translate(' + event.deltaX + 'px, ' + event.deltaY + 'px) rotate(' + rotate + 'deg)';

transformは、要素を拡大縮小、移動させるメソッドです。

参考: MDN web docs - transform

このtransformを使って移動を実現しています。

それでは、現状のスワイプページを確認しましょう。

http://localhost:3000/users にアクセスして軽くスワイプしてみてください。

上記の動画のようにカードをスワイプできたらうまく動作しています。

ただ、まだ動きがぎこちないので実装を進めます。

7. スワイプし終わった後の処理を実装

最後にスワイプし終わった後の処理を実装します。

実装する際は、panendというイベントを利用します。

参考: Hammer.js - Hammer.Pan(options)

panendはHammer.jsで使えるイベントの名前で、画面を押したまま指を動かす動作を終えたときに発生するイベントです。

それでは、swipe.jsに以下のように編集してください。

app/javascript/src/swipe.js
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
Copied!
if(location.pathname == "/users") { $(function () { let allCards = document.querySelectorAll('.swipe--card'); let swipeContainer = document.querySelector('.swipe'); function initCards() { // この行を追加する let newCards = document.querySelectorAll('.swipe--card:not(.removed)'); // この行を編集する newCards.forEach(function (card, index) { card.style.zIndex = allCards.length - index; card.style.transform = 'scale(' + (20 - index) / 20 + ') translateY(-' + 30 * index + 'px)'; card.style.opacity = (10 - index) / 10; }); } initCards(); allCards.forEach(function (el) { let hammertime = new Hammer(el); hammertime.on('pan', function (event) { // 中略 }); // ==========ここから追加する========== hammertime.on('panend', function (event) { el.classList.remove('moving'); swipeContainer.classList.remove('swipe_like'); swipeContainer.classList.remove('swipe_dislike'); let moveOutWidth = document.body.clientWidth; let keep = Math.abs(event.deltaX) < 200 event.target.classList.toggle('removed', !keep); if (keep) { event.target.style.transform = ''; } else { let endX = Math.max(Math.abs(event.velocityX) * moveOutWidth, moveOutWidth) + 100; let toX = event.deltaX > 0 ? endX : -endX; let endY = Math.abs(event.velocityY) * moveOutWidth; let toY = event.deltaY > 0 ? endY : -endY; let xMulti = event.deltaX * 0.03; let yMulti = event.deltaY / 80; let rotate = xMulti * yMulti; event.target.style.transform = 'translate(' + toX + 'px, ' + (toY + event.deltaY) + 'px) rotate(' + rotate + 'deg)'; initCards(); } }); // ==========ここまで追加する========== }); }); }
js
12
Copied!
swipeContainer.classList.remove('swipe_like'); swipeContainer.classList.remove('swipe_dislike');

上記のコードで、ハートや×のアイコンを表示するクラスを削除しています。

参考: MDN web docs - Element.classList

js
1
Copied!
let moveOutWidth = document.body.clientWidth;

document.body.clientWidthでブラウザ内の表示域を取得します。

js
12345678
Copied!
let keep = Math.abs(event.deltaX) < 200 event.target.classList.toggle('removed', !keep); if (keep) { event.target.style.transform = ''; } else { . .

Math.abs()で数値の絶対値を返します。

参考: MDN web docs - Math.abs()

Math.abs(event.deltaX) < 200で動かしたx軸が200以下であればtruekeepという変数に代入します。

つまり上記のコードでは、keepという変数でスワイプをするか、transformを空白にするかという条件式を書いています。

js
1234
Copied!
function initCards() { let newCards = document.querySelectorAll('.swipe--card:not(.removed)'); newCards.forEach(function (card, index) {

スワイプした場合、スワイプしたカードは表示する必要はないので、initCards関数でremovedクラスがあるカードは除外しています。

それでは、現状のスワイプページを確認しましょう。

http://localhost:3000/users にアクセスしてスワイプしてみてください。

上記の動画のようにカードをスムーズにスワイプできたらうまく動作しています。

以上で本パートは終了です。

お疲れさまでした。