14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

un-T factory! XA Advent Calendar 2023

Day 21

jQuery無しの環境で「matchHeight」が使いたい

Posted at

はじめに

jquery.matchHeight.js
使用したことはありますでしょうか?
自分はこれまで何度もお世話になってきました。

しかし昨今、私は下記が気になっていました。

  • jqueryに依存しない環境がメジャーになり、使いにくい場面が増えている
  • jqueryのresizeイベントを利用していることで、パフォーマンス的にも良くない点がある

目標

そこで、この記事では 「jquery.matchHeight.js」 を参考にしつつ、以下の点を達成した、気軽に使用できるコードを提示したいと思います。

  • jquery非依存
  • matchHeightとして必要な、横並びの要素の高さを揃え、リサイズされた時には高さを再設定する処理
  • パフォーマンスを改善
  • 機能を絞ってシンプルに

完成コード

matchHeight.js
export default class MatchHeight {
  constructor(targetClass, observeClass) {
    this.targetClass = targetClass;
    this.targetEls = Array.from(document.querySelectorAll(targetClass));
    this.observeEl = document.querySelector(observeClass);
    this.init();
  }

  _row(elements) {
    const tolerance = 1;
    let lastTop = null;
    let rows = [];

    elements.forEach(el => {
      const top = el.getBoundingClientRect().top;
      const lastRow = rows.length > 0 ? rows[rows.length - 1] : null;

      if (!lastRow || Math.floor(Math.abs(lastTop - top)) > tolerance) {
        rows.push([el]);
      } else {
        lastRow.push(el);
      }

      lastTop = top;
    });

    return rows;
  }

  _reset(els) {
    els.forEach(el => {
      el.style.height = 'auto';
    });
  }

  _match(els) {
    this._reset(els);
    const max_h = Math.max(...els.map(el => el.clientHeight));
    els.forEach(el => {
      el.style.height = max_h + 'px';
    });
  }

  _bind(els) {
    const rows = this._row(els);
    rows.forEach(row => {
      this._match(row);
    });
  }

  _setObserver(targetEls, observeEl) {
    this.observer = new ResizeObserver(() => {
      this._bind(targetEls);
    });
    this.observer.observe(observeEl);
  }

  init() {
    this._setObserver(this.targetEls, this.observeEl);
  }

  update() {
    this.targetEls = Array.from(document.querySelectorAll(this.targetClass));
    this.observer.disconnect();
    this._setObserver(this.targetEls, this.observeEl);
  }
}
index.js
import MatchHeight from '/matchHeight'; // 任意のディレクトリ

// 使用例
// items: 高さを揃えたい要素
// wrapper: リサイズを検知したい要素(この要素がリサイズすると、高さを揃える処理が走ります)
new MatchHeight('.items', '.wrapper');

簡単に解説

MatchHeightクラスの中で定義している関数について、以下でそれぞれ簡単に役割を説明します。

_row()

横並びの要素を配列にまとめる

_reset()

それぞれの要素の高さを取得するために、一時的にheightプロパティをautoにする

_match()

_row()でまとめた横並びの要素の中で一番高い要素の高さを _reset()を利用して取得し、横並びの要素それぞれのheightプロパティに指定する

_bind()

上の3つの関数を利用することで、横並びの要素の高さを揃える

_setObserver()

ResizeObserverを使用することで、必要なタイミングでのリサイズのみを検知し、_bind()を発火させることで高さを再設定する

引数には以下をを設定
第一引数:高さを揃えたい要素のリスト
第二引数:リサイズを検知したい要素

_init()

_setObserver()の引数にそれそれ要素を設定して発火させる

_update()

以下の処理を行って更新します

  • 高さを揃えたい要素を再取得
  • ResizeObserverでのリサイズ監視を解除
  • _setObserver()を呼び出し直す

(高さを揃えたい要素が増えた時などに使用ください)


上記の中で
・_row
・_resize
・_match
3つの関数は「jquery.matchHeight.js」を流用、参考にしております。


説明の途中で「ResizeObserver」というものが出てきましたね。
簡単に説明すると以下のようなWebAPIです。

ResizeObserver

ResizeObserver は要素の矩形領域のサイズの変化を監視して、そのサイズに変化があるとコールバック関数を呼び出すオブジェクトです。
window.resize イベントではウィンドウサイズ(ビューポート)の変更を検知しますが、ResizeObserver を使用すると特定の要素のサイズ変更を簡単に検出できます。

要は、
resizeイベントがウィンドウのリサイズを全て検知するのに対して、
ResizeObserverは 指定した要素がリサイズした時のみ検知 してくれる
というものになります。

上記のコードでは、パフォーマンスの改善を目的にこのResizeObserverを利用しています。

注意点

高さを揃える要素の中にcssなどでサイズが可変するプロパティなどがある場合、ResizeObserverでbody要素を指定したり、指定する要素のmax-widthを可変が発生し始めるサイズにするなどの調整が必要な場合があります。

これはResizeObserverは指定した要素にリサイズが発生しない場合は処理が呼び出されないためです。

まとめ

「完成コードそのままで十分だよ〜」だとありがたいですが、、

完成コードには必要最低限の機能しかありません。
また、TypeScriptでもありません。
上記の注意点もあります。
なので、ご利用される状況に応じて調整・追記など入れてもらえればと思います。

以上、何かのお役に立てれば〜〜〜〜〜

14
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?