画像拡大用プラグインの実装とjQuery.extendの仕組み

画像のクリックで拡大表示を行う機能を、コミュニティコメント内の画像に適用する要件があり、共通利用可能なプラグインとして実装しました。この過程で、jQuery.extendの動作原理についても深く理解することができました。 以下は、実装時の主なコード構造です。
var ImageZoomPlugin = function(options) {
    this.config = $.extend({
        container: 'body',
        triggerSelector: '.image-wrapper img',
        onOpen: function() {},
        onClose: function() {}
    }, options);

    this.imageList = [];           // 現在のコメント内にあるすべての画像
    this.currentIndex = 0;         // 現在表示中の画像のインデックス
    this.$overlay = null;          // 拡大表示用のオーバーレイ要素
    this.$imageElement = new Image(); // ロード用の画像オブジェクト

    this.init();
};
ImageZoomPlugin のコンストラクタでは、初期設定をマージし、必要なプロパティを定義しています。ここで使用されている $.extend は、デフォルト値とユーザー指定のオプションを統合するための中心的なメソッドです。
ImageZoomPlugin.prototype = {
    init: function() {
        this.createOverlay();
        this.bindEvents();
    },

    createOverlay: function() {
        const markup = [
            '<div class="img-zoom-overlay">',
            '  <div class="zoom-content">',
            '  </div>',
            '</div>'
        ].join('');

        this.$overlay = $(markup);
        $(this.config.container).append(this.$overlay);
        this.overlay = this.$overlay.find('.zoom-content');
    },

    bindEvents: function() {
        const self = this;

        // 画像クリック時に拡大開始
        $(this.config.triggerSelector).on('click', function(e) {
            e.preventDefault();
            const $img = $(this);
            self.showImage($img);
        });

        // オーバーレイ全体をクリックで閉じる
        this.$overlay.on('click', function(e) {
            if (e.target === this) {
                self.close();
            }
        });

        // 前へボタン
        this.$overlay.on('click', '.nav-prev', function(e) {
            const index = parseInt($(this).data('index'));
            if (index > 0) {
                self.showImage($(self.imageList[index - 1]));
            }
            e.stopPropagation();
        });

        // 次へボタン
        this.$overlay.on('click', '.nav-next', function(e) {
            const index = parseInt($(this).data('index'));
            if (index < self.imageList.length - 1) {
                self.showImage($(self.imageList[index + 1]));
            }
            e.stopPropagation();
        });
    },

    showImage: function($targetImg) {
        const self = this;
        this.currentIndex = 0;
        this.imageList = [];

        // 同一コンテナ内の全画像を取得
        const siblings = $targetImg.closest('[data-comments]').find('img');
        siblings.each(function(i) {
            if ($(this).attr('src') === $targetImg.attr('src')) {
                self.currentIndex = i;
            }
            self.imageList.push(this);
        });

        // 画像数が2以上ならナビゲーション表示
        const hasNavigation = this.imageList.length > 1;

        const src = $targetImg.attr('src').split('?')[0]; // クエリ文字列除去
        this.resizeAndDisplay(src, hasNavigation);
    },

    resizeAndDisplay: function(src, showNav) {
        const self = this;
        this.$imageElement.src = src;

        this.$imageElement.onload = function() {
            const naturalWidth = this.width;
            const naturalHeight = this.height;

            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;

            let displayWidth = naturalWidth;
            let displayHeight = naturalHeight;

            // 画面サイズに合わせて縮小
            if (naturalWidth > viewportWidth - 80) {
                displayWidth = viewportWidth - 80;
                displayHeight = (naturalHeight / naturalWidth) * displayWidth;
            }

            if (displayHeight > viewportHeight - 80) {
                displayHeight = viewportHeight - 80;
                displayWidth = (naturalWidth / naturalHeight) * displayHeight;
            }

            self.displayLargeImage(src, displayWidth, displayHeight, showNav);
        };
    },

    displayLargeImage: function(src, width, height, showNav) {
        const self = this;
        const content = this.overlay.empty();

        // クローズボタン
        content.append('<div class="close-btn">×</div>');

        // 画像本体
        content.append(`<div class="image-container"><img src="${src}" alt="拡大画像"></div>`);

        // アニメーション付き表示
        content.animate({
            width: width + 60,
            marginLeft: -(width / 2),
            marginTop: -(height / 2)
        }, 0, function() {
            if (showNav) {
                const navHtml = [
                    `<div class="nav-btn nav-prev" data-index="${self.currentIndex}">‹</div>`,
                    `<div class="nav-btn nav-next" data-index="${self.currentIndex}">›</div>`
                ].join('');
                content.append(navHtml);
            }

            self.$overlay.css({ display: 'block' });
            $('body').css('overflow', 'hidden');
        });
    },

    close: function() {
        this.$overlay.hide();
        $('body').css('overflow', 'auto');
    }
};
上記の実装では、複数画像のスライド表示やレスポンシブ対応を意識しており、画像のリサイズはブラウザのウィンドウサイズに応じて自動調整されています。

jQuery.extend の理解ポイント

  1. 基本構文
    $.extend(destination, source1, source2, ...)
    複数のオブジェクトを destination にマージします。元のオブジェクトが変更される点に注意が必要です。
  2. コピー用の使い方
    変更を避けるために空オブジェクトを第一引数に指定:
    const merged = $.extend({}, obj1, obj2);
    これにより、obj1 と obj2 を結合した新しいオブジェクトが生成されます。
  3. 静的メソッドの拡張
    $.extend({ method: fn }) で jQuery グローバルにメソッドを追加可能。
    例:$.formatDate() といったユーティリティ関数を定義できます。
  4. インスタンスメソッドの拡張
    $.fn.extend({ method: fn }) で、$(...).myMethod() のようにチェーン可能なメソッドを追加。
    例:チェックボックスの選択状態操作など。
  5. 深さコピーの有効化
    第1引数に true を指定すると、ネストされたオブジェクトも再帰的にマージされます。
    $.extend(true, {}, { a: { b: 1 } }, { a: { c: 2 } })
    結果:{ a: { b: 1, c: 2 } }(深いマージ)
jQuery.extend は単なる設定マージではなく、プラグイン開発における設計の基盤となる重要な機能です。本実装では、オプションの柔軟なカスタマイズと、拡張性のある構造を実現しています。

タグ: jQuery javascript plugin extend image zoom

10月13日 20:12 投稿