Railsでスワイプで画像切り替えする方法

#前提

- Ruby 2.6.3
- Rails 6.i.3
- Font Awesome導入済み


#Bootstrapの導入

```
$ yarn add jquery bootstrap popper.js
```
インストールが出来たら`environment.js`に以下の文を記述してください

```js:config/webpack/environment.js
const webpack = require('webpack')
environment.plugins.append(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery',
    Popper: ['popper.js', 'default']
  })
)
```

`application.js`にも追記してください。

```js:app/javascript/packs/application.js
import 'bootstrap';
import '../stylesheets/application';
```

Bootstrap導入出来ているはずです。
試しにボタンとか追加して確認してください。
[Bootstrap公式ページ](https://getbootstrap.jp/)



#application.scss作成
```
mkdir app/javascript/stylesheets
touch app/javascript/stylesheets/application.scss
```

#application.html.erb追加
```app/views/layouts/application.html.erb
<%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
```


#Hammer.js の導入
[参考ドキュメント](https://hammerjs.github.io/getting-started/)

```
$ yarn add hammerjs
```

```js:package.json

{
  "name": "match",
  "private": true,
  "dependencies": {
    "@fortawesome/fontawesome-free": "^5.15.3",
    "@rails/actioncable": "^6.0.0",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "5.2.1",
    "bootstrap": "^4.6.0",
    "hammerjs": "^2.0.8", //追記されてたらインストールできてます。
    "jquery": "^3.6.0",
    "popper.js": "^1.16.1",
    "turbolinks": "^5.2.0"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.11.2"
  }
}

```


#packs/application.jsに追加
```js:app/javascript/packs/application.js
import 'hammerjs';

//...(省略)


require("src/swipe")
```

#swipe.js作成
`touch app/javascript/src/swipe.js`を作成します。

```js:app/javascript/src/swipe.js

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;
      });
      if (newCards.length == 0) {
        $(".no-user").addClass("is-active");
      }
    }

    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)";
      });
      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();
        }
      });
    });
    function createButtonListener(reaction) {
      let cards = document.querySelectorAll(".swipe--card:not(.removed)");

      if (!cards.length) return false;

      let moveOutWidth = document.body.clientWidth * 2;

      let card = cards[0];
      card.classList.add("removed");

      if (reaction == "like") {
        card.style.transform =
          "translate(" + moveOutWidth + "px, -100px) rotate(-30deg)";
      } else {
        card.style.transform =
          "translate(-" + moveOutWidth + "px, -100px) rotate(30deg)";
      }

      initCards();
    }

    $("#like").on("click", function () {
      createButtonListener("like");
    });

    $("#dislike").on("click", function () {
      createButtonListener("dislike");
    });
  });
}

```

#index.html.erb

今回は`index.html.erb`に実装しています。

```html:users.index.html.erb
<div class="user-index-page">

  <div class="swipe">
    <div class="swipe--status">
      <i class="fa fa-times"></i>
      <i class="fa fa-heart"></i>
    </div>

    <div class="swipe--cards">
      <% @users.each do |user| %>
        <div class="swipe--card" id="<%= user.id %>">
          <% if user.profile_image.url.nil? %>
            <div class="profile-default-img"></div>
          <% else %>
            <%= image_tag user.profile_image.url, class: "profile-img" %>
          <% end %>
          <p class="profile-name">
            <%= user.name %>
          </p>
        </div>
      <% end %>
      <div class="no-user">近くにお相手がいません</div>
    </div>

    <div class="swipe--buttons">
      <button id="dislike"><i class="fas fa-times fa-2x"></i></button>
      <button id="like"><i class="fas fa-heart fa-2x"></i></button>
    </div>
  </div>

</div>
```

これで完成です。
`Rails`のバージョンが違うと動かなくなるのでその都度確認してから実装してください。
後は適当にスタイルつけたら完成です。