教材の内容に関係のない質問や教材とは異なる環境・バージョンで進めている場合のエラーなど、教材に関係しない質問は推奨していないため回答できない場合がございます。
その場合、teratailなどの外部サイトを利用して質問することをおすすめします。教材の誤字脱字や追記・改善の要望は「文章の間違いや改善点の指摘」からお願いします。
今回のパートではHammer.jsを使ってスワイプ機能を実装していきます。
本パートでは、下記の動画のようにスワイプ機能を実装します。
本パートでは、JavaScriptのコードが多く出てきます。参考のリンクを掲載しているので、リンクを参照しながら学習を進めましょう。
まず、スワイプ機能のJSファイルを作成します。
以下のコマンドを実行してください。
console Copied!touch app/javascript/src/swipe.js
次にswipe.js
ファイルを読み込むコードを追加します。app/javascript/packs/application.js
に以下のコードを追加してください。
app/javascript/packs/application.js12345678910111213141516171819202122232425 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)
それでは、いよいよJavaScriptのコードを書いていきます。まずスワイプ機能はスワイプページでしか処理を実行しないので、スワイプページにいるときだけ処理を実行できるようにします。
app/javascript/src/swipe.js
に以下のコードを追加してください。
app/javascript/src/swipe.js123 Copied!if(location.pathname == "/users") {
}
location.pathname
で現在のページURLのパスを参照します。http://localhost:3000/users にアクセスしたら、/users
を参照します。
そのため、if(location.pathname == "/users")
とすることで、現在のページURLが/users
であればif式以下の処理を実行するという条件式になります。
次にjQuery のコードを追加していきます。
最初に、以下のコードを追加します。
js1 Copied!$(function(){ ... })
このコードはページの読み込みが完了し、DOMの構築が完了した時点でfunction()
内のコードを実行します。もしこのコードがなければ、ページの読み込み途中にswipe.js
のコードが実行されてしまうため、このコードを書きます。
それでは、swipe.js
に以下のコードを追加してください。
app/javascript/src/swipe.js123456789 Copied!if(location.pathname == "/users") {
// ここから追加
$(function () {
});
// ここまで
}
次にカードの配置の実装をしていきます。
実装のイメージとしては、以下の画像のようにカードを重ねて配置していきます。
それでは、swipe.js
に以下のコードを追加してください。
app/javascript/src/swipe.js12345678910111213141516171819 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();
// ==========ここまで追加する==========
});
}
js1 Copied!let allCards = document.querySelectorAll('.swipe--card');
まずこのコードで、.swipe--card
要素のNodeListを返します。つまりスワイプするユーザーのカード情報を全て取得します。
参考:MDN web docs - Document.querySelectorAll()
js1234567 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
関数では、まず先ほど定義したallCards
をforEach()
を使って、配列の各要素に対して1度ずつ実行します。
参考:MDN web docs - Array.prototype.forEach()
forEach()
以下では3つのstyleを設定しています。
z-index
は重なりの順序を指定します。
参考: z-index
transform
でカードを縮小や移動をさせています。
opacity
は要素の透明度を指定します。
参考: opacity
それでは、現状のスワイプページを確認しましょう。
http://localhost:3000/users にアクセスしてください。
アクセスすると、上記のようなページが表示されます。まだスワイプはできません。
次にカードを移動したときの処理を実装していきます。
実装する際は、pan
というイベントを利用します。
参考: Hammer.js - Hammer.Pan(options)
pan
はHammer.jsで使えるイベントの名前で、画面を押したまま指を動かす動作のことです。
それでは、swipe.js
に以下のコードを追加してください。
app/javascript/src/swipe.js1234567891011121314151617181920212223242526272829303132333435363738394041 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)';
});
});
// ==========ここまで追加する==========
});
}
js123 Copied!let hammertime = new Hammer(el);
hammertime.on('pan', function (event) {
上記のコードで、インスタンスを生成してpan
というイベントを登録しています。
参考: Hammer.js - Getting Started
js12 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のイベントオブジェクトは上記の公式ドキュメントを参照してください
つまり上記のコードでは、カードを動かしていない場合は、処理を終了するようにreturn
を返しています。
js12 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
js12345 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
は、要素を拡大縮小、移動させるメソッドです。
このtransform
を使って移動を実現しています。
それでは、現状のスワイプページを確認しましょう。
http://localhost:3000/users にアクセスして軽くスワイプしてみてください。
上記の動画のようにカードをスワイプできたらうまく動作しています。
ただ、まだ動きがぎこちないので実装を進めます。
最後にスワイプし終わった後の処理を実装します。
実装する際は、panend
というイベントを利用します。
参考: Hammer.js - Hammer.Pan(options)
panend
はHammer.jsで使えるイベントの名前で、画面を押したまま指を動かす動作を終えたときに発生するイベントです。
それでは、swipe.js
に以下のように編集してください。
app/javascript/src/swipe.js1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 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();
}
});
// ==========ここまで追加する==========
});
});
}
js12 Copied!swipeContainer.classList.remove('swipe_like');
swipeContainer.classList.remove('swipe_dislike');
上記のコードで、ハートや×のアイコンを表示するクラスを削除しています。
参考: MDN web docs - Element.classList
js1 Copied!let moveOutWidth = document.body.clientWidth;
document.body.clientWidth
でブラウザ内の表示域を取得します。
js12345678 Copied!let keep = Math.abs(event.deltaX) < 200
event.target.classList.toggle('removed', !keep);
if (keep) {
event.target.style.transform = '';
} else {
.
.
Math.abs()
で数値の絶対値を返します。
Math.abs(event.deltaX) < 200
で動かしたx軸が200以下であればtrue
をkeep
という変数に代入します。
つまり上記のコードでは、keep
という変数でスワイプをするか、transform
を空白にするかという条件式を書いています。
js1234 Copied!function initCards() {
let newCards = document.querySelectorAll('.swipe--card:not(.removed)');
newCards.forEach(function (card, index) {
スワイプした場合、スワイプしたカードは表示する必要はないので、initCards
関数でremoved
クラスがあるカードは除外しています。
それでは、現状のスワイプページを確認しましょう。
http://localhost:3000/users にアクセスしてスワイプしてみてください。
上記の動画のようにカードをスムーズにスワイプできたらうまく動作しています。
以上で本パートは終了です。
お疲れさまでした。