MasonryLayouts

title: 原生JS实现瀑布流效果 tags:

  • JS

  • 瀑布流

    categories:

  • Web

masonry-layouts.js

/**
 * 瀑布流布局
 */

var Masonry = function masonry () {};
Masonry.prototype.loading = false;
Masonry.prototype.noMoreData = false;

/**
 * 初始化
 * @method init
 * @for Masonry
 * @param {string} id   需要实现无限加载的 DOM Element id 
 * @param {int} columns 瀑布流列数
 * @param {int} gap     可选, 列间隔; 如果不赋值, 根据列数计算
 * @param {function} loadingDataCBF 加载更多数据的回调函数
 * @param {function}  parseCBF      格式化数据列表每一项的函数
 * @param {int}       nextPage      下一页页码
 */
Masonry.prototype.init = function ({id, columns, gap, loadingDataCBF, parseCBF, nextPage}) {
  var box = document.getElementById(id);
  var items = box.children;
  nextPage = nextPage || 1;

  window.onload = function() {
      waterFall();

      // 页面尺寸改变时实时触发
      window.onresize = function() {
          waterFall();
          loadingOrNot();
      };

      window.onscroll = function() {
        waterFall();
        loadingOrNot();
      };
  };

  function waterFall() {
    var boxWidth = box.offsetWidth;
    if (!items || items.length === 0) { // 如果没有数据,尝试加载
      loadingData();
      return;
    }
    var itemWidth = items[0].offsetWidth;
    if (gap && !columns) {
        columns = parseInt(boxWidth / (itemWidth + gap));
    } else {
      if (columns > 1) {
        gap = parseInt(boxWidth - itemWidth * columns) / (columns - 1);
      } else {
        gap = 0;
      }
    }
    var arr = [];
    for (var i = 0; i < items.length; i++) {
        if (i < columns) {
            items[i].style.top = 0;
            items[i].style.left = (itemWidth + gap) * i + 'px';
            arr.push(items[i].offsetHeight);
        } else {
            var minHeight = arr[0];
            var index = 0;
            for (var j = 0; j < arr.length; j++) {
                if (minHeight > arr[j]) {
                    minHeight = arr[j];
                    index = j;
                }
            }
            items[i].style.top = arr[index] + 'px';
            items[i].style.left = items[index].offsetLeft + 'px';
            arr[index] = arr[index] + items[i].offsetHeight;
        }
    }
    var maxHeight = Math.max.apply(null, arr);
    box.style.height = maxHeight + 'px'; //因为是绝对定位absolute,所以父级没有高度,需要取最高的一列的高度给父级
  }

  /**
   * 加载更多数据
   * @method loadingData
   */
  function loadingData () {
    let that = this;
    this.loading = true;
    loadingDataCBF(nextPage).then(res => {
      if (res && res.length > 0) {
        res.forEach(item => {
          var html = parseCBF(item);
          box.insertAdjacentHTML('beforeend', html);
        });

        // 等待所有 图片加载完毕
        var imgTimer = setInterval(function () {
          let notLoadedFlag = false;

          for (var i = 0; i < document.images.length; ++i) {
            var item = document.images[i];
            if (box.contains(item)) {
              if (item.complete && item.naturalHeight !== 0) {
                continue;
              } else {
                notLoadedFlag = true;
              }
            }
          }

          if (!notLoadedFlag) { // 如果所有图片加载完毕, 清除定时器, 并计算瀑布流布局
            clearInterval(imgTimer);
            waterFall();
          }
        }, 50);

        that.loading = false;
        nextPage += 1;
      } else {
        that.noMoreData = true;
      }
    });
    loadingOrNot();
  }

  /**
   * 判断是否加载更多数据
   * @method loadingOrNot
   */
  function loadingOrNot() {
    if (this.noMoreData || this.loading) {  // 如果没有更多数据or正在加载中,则不加载
      return;
    }
    if (items.length > 0) {
        var scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
        var viewHeight = document.documentElement.clientHeight;
        var lastElement = items[items.length-1];
        var lastTop = lastElement.offsetTop; // 滚动到最后一个元素加载; 如果需要加载到最后一个元素的一半:  + Math.floor(lastElement.offsetHeight/2)
        if (lastTop < scrollTop + viewHeight) {   // 如果底部滚动到最后一个元素,返回true
          loadingData();
        }
    } else {
      loadingData();
    }
  }
}

usage

注意

  • 在添加执行waterfall()之前,需要确定所有box的子元素加载完毕,如果图片没有加载完毕,获取的宽高可能是不对的,如果有必要可能需要判断动态添加的cssjs文件是否加载完毕,可以参考 这里

参考

最后更新于

这有帮助吗?