はじめに
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
をクリックすると開閉する。
やりたいこと
- このようなカードが並んでいて、開閉可能なものには矢印がついている。
- カードのヘッダー部分をクリックすることで開閉。
- カードが開いた時に矢印も回転(アニメーション)する。
- カードが閉じる時に矢印が回転(アニメーション)し元の状態に戻す
実装に必要そうなこと
-
card-header
をJavaScriptで指定し、クリックされた時にクラス名を追加する。 - 追加したクラスに、アイコンの表示を変えるCSSを書く。
- もう1度クリックされた時には上で追加されたクラスを削除する。
実際にやったこと
1. viewファイルに直書きではなく、JavaScriptを読み込んで使う
まず、assets/javascripts/logs/index.js
を読み込む設定をする。
rails 任意のviewのみで、特定のjsを読み込む方法 - Qiita
logs/index.js
はviews/logs/index.slim.html
に対してだけ読み込ませたいので、上記を参考にviewファイルの下部に以下を記述。
<%= javascript_include_tag 'logs/index.js' %>
2. jQueryでクリックイベントを追加する
card-header
部分をクリックすることで開閉可能で、カードが開いた時に矢印も回転するのであれば、card-header
をクリックした時に、矢印が回転するような処理 を書かないといけない。
card-header
はココ
card-header
をクリックした、とJavaScript側に分かってもらえてるかどうかをまずチェック。
$(function(){
$('.card-header').click(function(){
alert("click event")
});
});
card-header
をクリックしてアラートが出ればOK
どうやらうまくいってるみたい。
3. カードの開閉状態で条件分岐させる
次に、クリックしたカードが開くかどうかをチェック。
現在はcontentカラム(詳細文)が空でなければ開くし、card-header
の文字の右側に矢印が表示される。
つまり矢印の有無でカードが開くかどうかをチェックできる、と考えた。
クリックしたcard-header
にfa-chevron-down
というクラスが存在すれば開く、しなければ開かない。
jQueryにfind
という子孫要素を取得できるメソッドがあったので使ってみる。
【jQuery入門】find()で子要素を取得する手法まとめ! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト
find(expr) - jQuery 日本語リファレンス
以下のようなコードを書いた。まずは条件分岐が成功するかどうかをチェックする。
$(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
のオプションやプロパティについてはここを参考にした。
まず、開閉に伴ってクラスを付けたり外したりする処理をjQueryで書く。
$(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
クラスがついてなければ追加、ついてたら削除する。
これも検証ツールで確認する。
<i class="fas fa-chevron-down>"
の横にspined-icon
が追加されているのが確認できた。
CSSでspined-icon
に矢印を回転させる処理を書く。
.spined-icon {
transform: rotate(90deg);
transition-duration: 0.5s;
}
transform: rotate(90deg);
で要素を90度回転、それが始まってから終わるまでの時間をtransition-duration: 0.5s
で設定している。
「0.5秒かけて要素を90度回転させてください」ということ。実際の動きを見てみる。
閉じるときのアニメーションがない。それはともかく……
クリックしてないカードの矢印も動いちゃってる……
何がいけなかったのか?
コードをもう一度見てみる。
$(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-header
のi
にのみクラスを追加/削除する処理を行うコードを考える。
また、クリックしたcard-header
のi
に処理を行うため、再びfind
を使った。
jQueryで子要素を取得するいくつかの方法〜children,find,contents
完成形
$(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-icon
がtoggleClass
によって削除されても、fa-cheveron-down
にtransition-duration
を設定することでアニメーションがうまくいった。
// 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
の中の情報はどのように使うのが正解だったのか?
ここらへんに対してしっかり調べて解決することが出来なかったこと。
学習が進み、分かり次第追記予定。