石橋を叩いて壊すページ

脱jQueryしてみた感想文

このブログも開設から約10年経過して、そろそろ古臭い部分やアクセスの遅さも気になって来たので、
見た目のレイアウトは維持しつつ内部コードをごっそり変更してみました。
気になるところがあればコメント欄で教えていただけると助かります。

今までデータベースを中心にした「いかにもブログ然としたブログ」だったんですが、
どうも時々DBにコネクションを張りに行くのに時間がかかってレスポンスが遅れるような動きが見られたので、
今回は趣向を変えて「1990年代の古き良きホームページ然なファイル管理を2020年的な中身で」をテーマにリニューアルしてみました。
おかげでほぼ全部のコンテンツが静的なhtmlファイルです。無駄にこだわってテーマごとにフォルダも分けました。
これでアクセス速度が改善したらいいな。
以前はどういうわけかSQLインジェクションできる穴を探る妙なアクセスも度々あって気になってたけど、今回は動的な部分を減らしたのでだいぶ気が楽になりました。

で、その過程でJavaScriptコードを勉強がてら脱jQueryしてみました。
最初に結論ですが個人サイトレベルで脱jQueryは実用ではないですね。うんざりしました。はい。
以下、あくまで私個人の感想ですのでご承知おきください。あと私の理解が正確でなかった場合はごめんなさい。

脱jQueryとはどういうことか

脱jQuery。jQueryを使わなくする、と言うことです。
jQueryは、昔はブラウザごとの挙動の違いを吸収するのに便利でした。
今はJavaScriptの標準化も進んできて挙動の違いそのものがなくなってしまったので、じゃあjQuery要らないよね、というのが脱jQueryのスタート地点かと思います。

まずそこが違う。
jQueryは挙動の違いの吸収以外にも機能があるのです。そしてその部分がまだまだ便利なのです。
ということに遅ればせながら脱jQueryを進めていて再認識しました。
その部分とは例えば以下の通り。

.offset
jQueryのoffsetdocumentを基準として要素の位置を取得するメソッドです。
有体に言えばページの一番左上を基準として要素のx座標とy座標を取ってきます。
標準化されたJavaScriptにはこれがない。HTMLElement.offsetTopというメソッドはあるけれど、基準がその要素を含む直近の位置指定された要素ないしtd, th, tableないしbodyって、なにそれ候補多くない?
座標は取れるけど、何を基準に取った座標か一貫しないので使いづらい。
対処方法はググれば見つかりますが、対処した結果がjQueryのoffsetじゃない?
.fadeIn/Out
言わずと知れたフェード処理。ぼんやり出てくるアニメーションです。
CSSのtransition(状態遷移)とopacity(透明度指定)を駆使すれば簡単に実装できます。透明度を弄るだけなら
これにCSSのdisplay:none(要素の非表示)が絡んでくると話がややこしくなります。

要素をフェードインさせる場合、非表示状態(display:none)の要素にいきなりdisplay:blockとopacity:1を設定してもアニメーションしません。
transitionは要素が非表示だとアニメーションを生成しないのです。
これを回避するには先ずdisplay:blockして、数ミリ秒後にopacity:1を設定してやる必要があります。

フェードアウト時は当然ながら、要素にいきなりdisplay:noneとopacity:0を設定しても仕様がありません。
要素は即座にdisplay:noneされるのでアニメーションなど起きるべくもない。
これを回避するには先ずopacity:0を指定して、アニメーションの完了後に改めてdisplay:noneする必要があります。
でもアニメーションが完了するのはいつでしょう?transitionのアニメーション時間が静的なCSSファイル上で宣言されていたら、それを取ってこないといけません。
あるいはCSS上とJavaScript上に同じ値を二重持ちするか、transitionendイベントを捕まえるか。

更に問題なのが、ユーザーの操作が早すぎる場合などでフェードイン・アウトが競合した場合。
例えば、チェックボックスがオンにされたらフェードインし、オフにされたらフェードアウトすることを考えます。
もし、オンにされた1ミリ秒後にオフにされたら?
まずフェードインでdisplay:block、直後にフェードアウトでopacity:0、続いてフェードインの数ミリ秒遅れ処理でopacity:1設定、最後にフェードアウト完了時のdisplay:none設定。
そんな具合で複数のアニメーションが競合すると意味不明な状態が爆誕してしまいます。

これを避けるにはpromiseチェーンなどを使って、アニメーションが競合せず意図した順に進むよう「アニメーション要求を積み上げる」処理が必要です。
でもただ詰むだけでは駄目です。先の例でチェックボックスを高速連打されたら、要素は詰まれたアニメーションを全てこなすまでずっと明滅し続けるでしょう。
従って要求を積むときは直前に積まれたアニメーションを即座に完了させるように…
おや?どこかで聞いた覚えが。そう、.finish(アニメーションの即時完了)。なんだ、jQueryを使えば済む問題か。

ちなみにdisplay:noneの代わりにheight(要素の高さ)をゼロにして要素を不可視化する手もありますが、下記の通りautoは使えなかったり、そもそもフェードアウト時のアニメーション完了待ちは不要にならないのであまり解決にはなりません。
また、特定の要素にアニメーションをさせるためには要素の保持用とpromiseチェーンの保持用の2つの変数が必要になるけれど、jQueryはそれをセットで1変数に保持できるのが便利。
.slideDown/Up
上記でも触れましたが、height(要素の高さ):0とheight:autoの間ではtransitionはアニメーションを生成しません。
高さをautoではなく数値で指定したりと、いずれにせよ考えることは山積みです。
もっと単純なところ
例えばIDで要素を取ってくるとき。
jQueryなら$('#id');。
JavaScriptだとdocument.getElementById('id')。長い!

要素を順に処理したいとき。
$('p').each(function(){/*処理*/});
let elmenets=document.getElementByTagName('p'); for(let i=0;i<elements.length;i++){/*処理*/}。
getElementByTagNameの戻り値は配列とは似て非なるものなので、for inで回すには配列に変換するコードを挟む必要があります。

イベントをキャッチしたいとき。
$('#id').click(function(){/*処理*/});。
document.getElementById('id').addEventListener('click',function(){/*処理*/});。
イベントが2つならこれが2行になってさらに長い。jQueryなら一度に複数のイベントを登録できます。

上記のいずれについても、別に「できない」と言っているのではありません。やれば出来るのです。
長いのが嫌なら短くできるコード書けばいいのです。動きが煩雑なら共通化するメソッド書けばいいのです。
課題にぶち当たったら自分でググって解決してコード書いてテストして今後もメンテすればいいのです。
ただ、そうやって苦労して調べて書いた処理が突き詰めるとjQueryのそれと似たような形に落ち着いてくるのを何度か繰り返すと、
やっていてだんだん空しくなってくるというのが私の脱jQueryの最大の印象でした。

まあ気づきはあったし勉強にもなったから、うんざりはしたけど後悔はしていませんとも。(負け惜しみ)
中身も知らずにjQueryを使えば済むなんて脳死じゃない?と言われれば別に否定はしませんが、プログラマが全員機械語を熟知でもしてない限り世の中の大抵のプログラムは脳死かと思うのでそれはまあ気にしない方向で行きましょう。

勉強にはなるのですべて無駄とは言いませんが、上記した通り「Xをした数ミリ秒後にYをする」のようなバッドノウハウ的な解決方法が多いこともあって、そこまで本腰入れて学ぶことか?という気持ちも少しあるのも本音です。

うんざりしつつも乗り掛かった舟の勢いで結局全部やり切りましたので、このページがエラー無く見れているならちゃんと動いているはずです。

脱jQueryしない場合

今回一つ気になったのは、Googleのウェブパフォーマンス
自前のコードでjQueryを使うためには先にjQueryがロードされていなければならないので、スクリプトのロード順が重要になるわけですが、
悪いことにJavaScriptはロード中のHTMLに干渉する能力(document.write)があるから、HTMLのロード中にscriptタグにぶち当たるとそこでスクリプトをロードして実行完了するまで後続のHTMLのロードが停止してしまいます。
これがロード待ちとして画面の描画遅れにつながるため、パフォーマンス上よろしくないようです。

解決法としては、例えばhtml上にjQueryと自前のスクリプトをそれぞれロードするscriptタグが計2つ並んでいる場合、
それらのscriptタグにdefer属性をつけると非同期ロードかつ実行順序が保証されるようです。
手元のChromeでごく簡単に試した限りですが、jQueryの読み込みが遅れた場合もそれが完了するまで自前のスクリプトは実行待機する様子が確認できました。
上記のパフォーマンス上も特に引っかかりはしないようです。
自前のスクリプトが、いわゆるonLoadイベント後に動く処理しか持っていない場合は、この属性を活用すると少し幸せになるかもしれません。

もののついでに

勉強ついでに、自分好みの動きをするLightboxもどきも自作しました。
自分好みのポイントは以下。

  • 1つの段落内にリンクされた画像が複数ある場合、それらの画像をひとつのグループとしてスライドショー状態にすること。
    および、その段落内のテキストを画像に対する説明として表示すること。
    これはLightbox導入以前からこのブログがそういうデザインになっていたことに合わせたもの。
  • 画像をクリックしてLightboxが表示されるとき、初期状態で画像を目いっぱい拡大表示すること。
    だって画像をクリックしてくれた人はその画像をもっとよく見たいからクリックしてくれたと思うので、その画像が小さく表示されたら残念に違いないと思ったので。
    最も、PCならこれでいいけどスマホだとほぼサイズ変わらん…画像を横倒しに表示でもしない限りは。
  • なんとなく、画像不使用。
    絵文字便利ですね。スマホChromeだと虫眼鏡のレンズ部分が半透明だったりちょっと面白い。
  • もちろん脱jQuery。


こんな感じで、画像をクリックすると拡大します。左右キー、ホイール、スワイプ、または◀▶クリックで左右送り。

この記事を評価

この記事にコメント

  1. ...

【この記事にコメント】
お名前:
コメント:

Menu