遅延読み込み処理のモジュール

遅延読み込み処理というよりもモジュールの勉強ように作成。


```html:index.html
    <div class="img_box">
      <img src="" data-src="img/1.jpg" class="lazyload" width="800" height="533">
    </div>
    <div class="img_box">
      <img src="" data-src="img/2.jpg" class="lazyload" width="800" height="533">
    </div>
    <div class="img_box">
      <img src="" data-src="img/3.jpg" class="lazyload" width="800" height="533">
    </div>
    <div class="img_box">
      <img src="" data-src="img/4.jpg" class="lazyload" width="800" height="533">
    </div>
    <div class="img_box">
      <img src="" data-src="img/5.jpg" class="lazyload" width="800" height="533">
    </div>
    <div class="img_box">
      <img src="" data-src="img/6.jpg" class="lazyload" width="800" height="533">
    </div>
...
```

```javascript:lazyload.js

;(function(root, factory) {

    if (typeof define === 'function' && define.amd) {
      define(factory);
    } else if (typeof exports === 'object') {
      module.exports = factory();
    } else {
      root.LazyLoad = factory();
    }
  
})(this, function() {
    'use strict';

    var LazyLoad = function(el_name, options) {

        // new 演算子を使用していない場合
        if (!(this instanceof LazyLoad)) return new LazyLoad(el_name, options);

        
        this.load_list = [],
        this.el_name = el_name,
        this.options = Object.assign({
            loadTimingDown: 400,//(px)上から下にスクロールした時に実際の要素よりも早く読み込みたい場合
            loadTimingUp: 400,//(px)下から上にスクロールした時に実際の要素よりも早く読み込みたい場合
            animation: true,//デフォルトのフェードインアニメーション
            before_lazyload: function(){},//遅延読み込み前
            after_lazyload: function(){},//遅延読み込み後
        },options);

        // 遅延読み込みする要素を取得
        // 取得した要素はHTMLCollectionなので、配列に変換
        this.load_list = Array.prototype.slice.call(document.querySelectorAll(this.el_name));

        // １つも要素が見つからない場合はエラー
        if(this.load_list.length === 0) {
            throw new Error('not found elements');
        }

        return this;
    };

    /**
     *　遅延読み込み処理開始
     */
    LazyLoad.prototype.done = function() {

        // カスタムイベント作成
        add_custum_event.call(this);

        // 初回読み込み
        this.check_lazyload_collection();

        // スクロールイベント設置
        window.addEventListener('scroll', scroll_event.bind(this));

        return true;
    };

    /**
     * DOM要素の配列を回して、遅延読み込み処理に渡す。
     */
    LazyLoad.prototype.check_lazyload_collection = function() {
        var that = this;
        var el_list = this.load_list;

        if(el_list.length == 0) {
            return false;
        }

        // for(let key in el_list) {
        el_list.forEach(function(el, key) {
            // DOM要素でない場合は処理をしない
            if( !(el instanceof HTMLElement) ) return;

            if(that.check_lazyload(el)) {
                // 読み込んだら配列から削除
                el_list.splice(Number(key), 1);
            }
        });

        return true;
    };

    /**
     * 登録しているすべてのイベントを削除
     */
    LazyLoad.prototype.destroy = function() {
        var that = this;
        this.load_list.forEach(function(el, index) {
            el.removeEventListener('after_lazyload', that.options.after_lazyload);
            el.removeEventListener('before_lazyload', that.options.before_lazyload);
        });

        window.removeEventListener('scroll', scroll_event);
    }

    /**
     * 要素の高さから画像を遅延読み込みする
     * 
     * @param {*} el 
     */
    LazyLoad.prototype.check_lazyload = function(el) {

        // ブラウザ一番下の現在座標(縦のスクロール量＋ブラウザの高さ)
        var browser_bottom_position = window.pageYOffset + window.screen.height;
        
        // 指定した座標のコンテンツ最上部からの座標(スクロール量＋要素のブラウザの現在地点からの高さ)
        var target_el_top_position    = window.pageYOffset + el.getBoundingClientRect().top;
        var target_el_bottom_position = window.pageYOffset + el.getBoundingClientRect().bottom;

        // 指定した要素の位置。
        if( browser_bottom_position >= (target_el_top_position - this.options.loadTimingDown) && window.pageYOffset < (target_el_bottom_position + this.options.loadTimingUp)) {

            this.load_contents(el);

            return true;
        }

        return false;
    };

    /**
     * 読み込み処理部分。前後でイベントハンドラ実行
     * @param {*} el 
     */
    LazyLoad.prototype.load_contents = function(el) {
        // 遅延読み込み前イベント実行
        el.dispatchEvent(this.before_lazyload);

        // イベントは１回だけ、実行したら削除
        el.removeEventListener('before_lazyload', this.options.before_lazyload);

        // src属性を入れ替えてファイル読み込み
        el.setAttribute('src', el.getAttribute('data-src'));

        if(this.options.animation) {
            // クラス名追加
            el.classList.add('fadeIn');
        }

        // 遅延読み込み後イベント実行
        el.dispatchEvent(this.after_lazyload);

        // イベントは１回だけ、実行したら削除
        el.removeEventListener('after_lazyload', this.options.after_lazyload);
    };

    /**
     * スクロールイベントのコールバック関数
     * @param {*} that 
     */
    function scroll_event() {
        if(!this.check_lazyload_collection()) {
            // すべての要素の表示が完了した場合
            window.removeEventListener('scroll', scroll_event);
        }
    }

    /**
     * optionsから取得出来るメソッド登録処理
     * @param {*} that 
     */
    function add_custum_event() {
        var that = this;

        // 遅延読み込み前処理
        that.before_lazyload = document.createEvent('CustomEvent');
        that.before_lazyload.initCustomEvent('before_lazyload', true, true, that);

        // 遅延読み込み後処理
        that.after_lazyload = document.createEvent('CustomEvent');
        that.after_lazyload.initCustomEvent('after_lazyload', true, true, that);


        that.load_list.forEach(function(el, index) {
            el.addEventListener('after_lazyload', that.options.after_lazyload);
            el.addEventListener('before_lazyload', that.options.before_lazyload);
        });

        return true;
    }

    return LazyLoad;
});
```


```javascript:main.js
window.onload = function() {
    var lazyload = new LazyLoad('.lazyload', {
        animation: false,
        before_lazyload: function() {
            console.log('before');
        },
        after_lazyload: function(e) {
            console.log('after');
            e.target.className = 'fadeIn';
        }
    });

    lazyload.done();
}
```


bootstrap系のプラグインを参考にして作成しています。

vanillaJsで作成。ずっとjQuery使用していたのでかなり大変でした。

外部に提供するAPIはprototypeに入れる。プライベートスコープの関数はクロージャにようにしました。
当初モジュールパターン的に作ろうとしていましたが、メモリの無駄遣いな気がするのと、インスタンス毎に管理出来るようにした方がいい気がしたので、このような作りにしました。プライベート関数はインスタンスのthisを参照出来るようにcall、bindメソッドを使用しています。今回初めて使用しました。

遅延読み込み処理に関しては調べ不足でこれが良いやり方かどうかよく分かっていない。
before/after_lazyloadメソッドは要素読み込み前後に発火するイベントハンドラで、ここで読み込んだ要素に対してアニメーションを付けたりとかできます。
デフォルトでフェードインアニメーションを用意。オプションで外せます。

この程度の規模だとmvc的な設計にする必要がなく手を出しませんでした。


見直すと色々おかしい気がする。。。。
随時更新して行きたいです。

ご指摘お願いいたします。