Help us understand the problem. What is going on with this article?

JavaScript/jQuery初心者が、アコーディオンメニューの開閉に伴って回転する矢印アイコンをなんとか実装したときの記録

はじめに

JavaScript/jQuery初心者が、コンテンツの開閉状態と連動する矢印アニメーションを、jQueryで実装するまでにやったことの記録です。

3行で

  • thisでクリックした要素にだけ処理を行う
  • アニメーション用のCSSを書く
  • toggleClassでアニメーション用クラスの付け替えをする

環境 + 前提

Rails 5.2.4.3
Ruby 2.6.5
Bootstrap 4.5.0
jquery-rails 4.4.0
sass-rails 5.1.0

このポートフォリオに実装
YWT Quest

Bootstrapのカードを使用。
カードコンポーネントによるアコーディオンを実装していて、card-headerをクリックすると開閉する。

やりたいこと

  1. このようなカードが並んでいて、開閉可能なものには矢印がついている。 スクリーンショット 2020-06-30 0.12.14.png
  2. カードのヘッダー部分をクリックすることで開閉。
  3. カードが開いた時に矢印も回転(アニメーション)する。
  4. カードが閉じる時に矢印が回転(アニメーション)し元の状態に戻す

実装に必要そうなこと

  • card-headerをJavaScriptで指定し、クリックされた時にクラス名を追加する。
  • 追加したクラスに、アイコンの表示を変えるCSSを書く。
  • もう1度クリックされた時には上で追加されたクラスを削除する。

実際にやったこと

1. viewファイルに直書きではなく、JavaScriptを読み込んで使う

まず、assets/javascripts/logs/index.jsを読み込む設定をする。

rails 任意のviewのみで、特定のjsを読み込む方法 - Qiita
logs/index.jsviews/logs/index.slim.htmlに対してだけ読み込ませたいので、上記を参考にviewファイルの下部に以下を記述。

views/logs/index.slim.html
<%= javascript_include_tag 'logs/index.js' %>

2. jQueryでクリックイベントを追加する

card-header部分をクリックすることで開閉可能で、カードが開いた時に矢印も回転するのであれば、card-headerをクリックした時に、矢印が回転するような処理 を書かないといけない。

card-headerはココ

スクリーンショット 2020-06-29 22.32.08.png

card-headerをクリックした、とJavaScript側に分かってもらえてるかどうかをまずチェック。

logs/index.js
$(function(){
  $('.card-header').click(function(){
    alert("click event")
  });
});

card-headerをクリックしてアラートが出ればOK

click-event.gif

どうやらうまくいってるみたい。

3. カードの開閉状態で条件分岐させる

次に、クリックしたカードが開くかどうかをチェック。
現在はcontentカラム(詳細文)が空でなければ開くし、card-headerの文字の右側に矢印が表示される。

つまり矢印の有無でカードが開くかどうかをチェックできる、と考えた。
クリックしたcard-headerfa-chevron-downというクラスが存在すれば開く、しなければ開かない。

jQueryにfindという子孫要素を取得できるメソッドがあったので使ってみる。

【jQuery入門】find()で子要素を取得する手法まとめ! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト
find(expr) - jQuery 日本語リファレンス

以下のようなコードを書いた。まずは条件分岐が成功するかどうかをチェックする。

logs/index.js
$(function(){
  $('.card-header').click(function(){
    var icon = $(this).find('.fa-chevron-down');
    // 本文がなく、アイコンがないcard-headerをクリックした時はundefinedが返されることを利用した
    if(icon[0] !== undefined) {
      console.log("true");
    } else {
      console.log("false");
    }
  });
});

最初はicon[0] == "<i class=\"fas fa-chevron-down\"></i>"と、icon[0]に入っている情報そのものと照らし合わせたり
icon[0].toString();として文字列に変換してから比較したり
indexOfを使って前方一致(<i classだけ等)で検索をかけてみたりしたが、全て失敗。

どうしよう?とconsole.logを見ていると、開閉できないカードを押したときundefinedが返ってきているのを発見。

「じゃあ、undefinedが返ってきていないときにアニメーション用の処理をすればいいのでは……?」

と考えて、ifの中身をif(icon[0] !== undefined)と書き換えることで、開くカードのcard-headerをクリックしたときにはtrueが、開かないカードのcard-headerをクリックしたときにはfalseが返ってくるようになった。

4. 矢印をアニメーションさせる処理を書く

rotate() - CSS: カスケーディングスタイルシート | MDN

矢印を回転させるにはrotate()プロパティでアイコンを回転させれば良いと考えた。
しかし、肝心のアニメーションの実装は……?

jQueryでCSSのtransform: rotate()を使った回転アニメーションする際のメモ ‹ jQuery ‹ JavaScript ‹ emwaiblog

この記事では、回転用のクラス名を追加することで、回転を実装している。
また、transitionを使うことでtransformをアニメーション化している。

transitionのオプションやプロパティについてはここを参考にした。

【CSS3】Transition(変化)関連のまとめ

まず、開閉に伴ってクラスを付けたり外したりする処理をjQueryで書く。

assets/javascripts/logs/index.js
$(function(){
  $('.card-header').click(function(){
    var icon = $(this).find('.fa-chevron-down');
    // 本文がなく、アイコンがないcard-headerをクリックした時はundefinedが返されることを利用した
    if(icon[0] !== undefined) {
      $('.fa-chevron-down').toggleClass("spined-icon")
    }
    // else以下を削除
  });
});

toggleClassで、fa-chevron-downクラスを持つ要素にspined-iconクラスがついてなければ追加、ついてたら削除する。

これも検証ツールで確認する。

class-toggle.gif

<i class="fas fa-chevron-down>"の横にspined-iconが追加されているのが確認できた。

CSSでspined-iconに矢印を回転させる処理を書く。

assets/stylesheets/logs/index.scss
.spined-icon {
  transform: rotate(90deg);
  transition-duration: 0.5s;
}

transform: rotate(90deg);で要素を90度回転、それが始まってから終わるまでの時間をtransition-duration: 0.5sで設定している。

「0.5秒かけて要素を90度回転させてください」ということ。実際の動きを見てみる。

ohno.gif

閉じるときのアニメーションがない。それはともかく……

クリックしてないカードの矢印も動いちゃってる……

何がいけなかったのか?

コードをもう一度見てみる。

assets/javascripts/logs/index.js
$(function(){
  // 'card-header'クラスを持つ要素をクリックしたとき、
  $('.card-header').click(function(){
    // 'card-header'クラスの子孫要素の中から'fa-chevron-down'クラスを持つ要素を探し、変数iconに格納する
    var icon = $(this).find('.fa-chevron-down');
    if(icon[0] !== undefined) {
      // 'fa-chevron-down'クラスを持つ要素に'.spined-icon'クラスを追加(既にある場合は削除)
      $('.fa-chevron-down').toggleClass("spined-icon")
    }
  });
});

このコード$('.fa-chevron-down').toggleClass("spined-icon")では、fa-chevron-downクラスを持つ全てのi要素にspined-iconクラスを付与することになってしまい、全ての矢印が回転してしまう。

「クリックした要素のみ」「クリックした要素 jQuery」等で検索していると、この記事を見つけた。
jQueryの$(this)の使い方(どこを指してるのか?)

ここに、thisを使ってクリックした要素にのみ処理を行っている例と、thisではなく要素(記事中ではp)を指定してしまった時の例があり、後者では自分の失敗例と同じで全てのp要素に処理が行われてしまっている。

これを参考に、クリックしたcard-headeriにのみクラスを追加/削除する処理を行うコードを考える。
また、クリックしたcard-headeriに処理を行うため、再びfindを使った。

jQueryで子要素を取得するいくつかの方法〜children,find,contents

完成形

icon-animation-kansei.gif

logs/index.js
$(function(){
  $('.card-header').click(function(){
    var icon = $(this).find('.fa-chevron-down');
    // 詳細文がなく、アイコンがないcard-headerをクリックした時はundefinedが返されることを利用した
    if(icon[0] !== undefined) { 
      // thisをつけてクリックした要素にだけ効かせる。そうしないと全部の矢印にtoggleClassが効いてアニメーションしてしまう
      // findで.fa-chevron-downのついた要素を見つけ、そこにクラスを追加している
      // spined-iconというクラスでアニメーションをつけている
      $(this).find('.fa-chevron-down').toggleClass("spined-icon")
    }
  });
});

また、spined-icontoggleClassによって削除されても、fa-cheveron-downtransition-durationを設定することでアニメーションがうまくいった。

logs/index.scss
// ywtのカードヘッダーに、開閉可能な場合つける矢印
@mixin ywt-header-angle($anglecolor: $second-color) {
  i.fa-chevron-down {
    color: darken($anglecolor, 30%);
    // アイコンのクラスに、transitionをつけることで、閉じる際にもアニメーションを有効化している
    transition-duration: 0.5s;
  }
}

// 矢印のアニメーション用
.spined-icon {
  transform: rotate(90deg);
  transition-duration: 0.5s;
}

おわりに

JavaScriptやjQueryへの苦手意識はまだまだ消えないが、console.logで処理や戻り値を追いながら、1つずつ進めていくことで、なんとか実装。
心残りとしては、カードの開閉が可能かどうかをチェックするためのif(icon[0] !== undefined)について。
if(icon[0] == "<i class=\"fas fa-chevron-down\"></i>")じゃなぜダメだったのか?
indexOfが使えなかったのはなぜか?
iconの中の情報はどのように使うのが正解だったのか?

ここらへんに対してしっかり調べて解決することが出来なかったこと。
学習が進み、分かり次第追記予定。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした