javascript实现瀑布流加载图片原理


Posted in Javascript onFebruary 02, 2016

讲一下大概的原理吧,还是先上图:

 javascript实现瀑布流加载图片原理

 功能描述:

  • 根据不同菜单的属性值分别加载不同的数据
  • 下拉滚动条到一定位置预加载图片,滚动条拉到最底下的时候渲染html;
  • 鼠标移到菜单,切换各个图片列表;
  • 鼠标移到图片列表上,显示详细信息; 

技术实现方案:

先梳理一下从加载到显示的流程:

1. 加载数据

2. 拼接HTML写入到页面

3. 检查刚刚写入的HTML中的img是否全部加载完成,如果是,进入5、否则进入4

4. 等待图片加载完成

5. 计算每个元素的位置

一开始的时候最头疼的是如何定位的问题,后来经过朋友指导终于解决:计算总共有多少列图片并且把每一列的高度都放到一个数组里面。每当一张图片加载完成的时候就查找这个数组里面最小的值,并且定位当前图片的top设置为这个值,完成后把这个图片的高度加上数组里面的最小值并且返回到数组里面,依次类推。 
 PS:因为这个功能代码太多,只能作基本的简单分解代码了: 

// 创建用于记录每列高度的数组
_getLowestCol: function() {
  t._cols = new Array(5),min = 0;
  // 初始化为0
  for (var i = 0; i < t._cols.length; i++) {
    if (cols[i] < cols[min]) {
      min = i;
    }
    return min;
  }
},
_reposition: function() {
  t._grids.each(function(i, grid) {
    //先显示出来
    grid = $(grid).show();
    
    var height = grid.outerHeight(), min = t._getLowestCol();

    // 定位
    grid.animate({
      left: (t._colWidth + t._colSpacing) * min,
      top: t._cols[min],
      opacity: 1
    },1000);
    // 记录高度
    t._cols[min] += height;
  });

}

 其次开发过程中遇到的难题是:因为如上图所示,鼠标移动到菜单栏需要切换图片列表,并且分别需要用瀑布流加载不同类型的数据。所以要处理在切换页面的时候如何才能做到每个页面只执行一次代码请求接口,而不需要每一次切换都重新请求数据接口,仅仅执行切换显示图片列表的操作就可以了。

考虑到每一个菜单都有一个自定义属性,所以这个问题轻易地解决了:建立一个对象来记录当前菜单是否已经执行过代码,如果没有就执行请求数据 。 

var isLoad = {};//是否载入过
labelType.mouseover(function() {
  var i = $(this).index();
  var api = _this.attr('api');//接口标识
  
  if(! isLoad[ api ]){
    isLoad[ api ] = i;
    loadData(wrapper, api);
  }
  
});

以下为全部代码:
html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<style type="text/css">
*{margin:0;padding:0;}
ul,li{  list-style-type:none;}
li img{width:100%;list-style:none;}
</style>
</head>
<body>
  <div class="photo_box">
    <ul id="container" style="border:1px solid #000;width:80%;height:600px;overflow:hidden;margin:0 auto;position: relative;">
    </ul>
    <div id="loading" class="loading" style="text-align: center;margin-top: 20px;font-size: 1.2em;">加载中...</div>
    <div id="more" class="more"style="text-align: center;margin-top: 20px;font-size: 1.2em;"><input type="button" value="更 多" id="clear" /></div>
  </div>
<script type="text/javascript" src="../../lib/seajs/sea.js"></script>
<script type="text/javascript" src="../../lib/base/1.0.x/base.js"></script>
<script type="text/javascript">
seajs.use(['lib/jquery/1.11.x/index.js', '_example/waterFall_1.1/waterfall.js'], function($, waterFall) {
  waterFall.init({
    container: $('#container'),
    dataURL: 'http://www.woxiu.com/index.php?action=Index/Main&do=ApiZhuboGrade',
    dataType: 'jsonp',
    template: '<% for (var i = 0; i < data.length; i++) { %>' +
            '<li style="display: none;">' +
              '<img src=" <%-data[i].room_img%> ">' +
            '</li>' + 
          '<% } %>',
    colWidth: 200,
    colSpacing: 10,
    rowSpacing: 15,
    page: 1,
    pageEnd: 8,
  });

  // 限制同时展示的页数
  var loadCounter = 1;
  function pageNum(){
    if (loadCounter >= 3) {
      $('#more').show();
      $('#loading').hide();
      return true;
    } else {
      loadCounter++;
      $('#more').hide();
      $('#loading').show();
    }
    return false;
  }
  $('#clear').click(function() {
    loadCounter = 1;
    waterFall._loadNext();
  });
});

</script>
</body>

js:

/**
 * 瀑布流布局组件类
 * @param {Object} options 组件设置
 *    @param {NodeList} options.container 瀑布流容器
 *    @param {String} options.dataURL 数据地址
 *    @param {String} [options.dataType='jsonp'] 数据类型,json或jsonp
 *    @param {String}  options.template 模板编辑
 *    @param {Number} [options.colWidth] 图片大小。
 *    @param {Number} [options.colSpacing] 列间隔。
 *    @param {Number} [options.rowSpacing] 行间隔。
 *    @param {Number} [options.page=1] 数据开始页码
 *    @param {Number} [options.pageEnd] 数据末尾页码

 * @pageNum() 函数,如果不需要现在加载也是,需要把函数里面的判断去掉。


 从加载到显示的流程

1. 加载数据
2. 拼接HTML写入到页面
3. 检查刚刚写入的HTML中的img是否全部加载完成,如果是,进入5、否则进入4
4. 等待图片加载完成
5. 计算每个元素的位置

 */
define(function(require, exports, module) {
  'use strict';
  
  var Tmpl = require('lib/tmpl/2.1.x/index.js'),
    $ = require('lib/jquery/1.11.x/index.js');

  var waterFall = {
    init: function(options) {
      var t = this;
      t._container = options.container;
      t._template = options.template;
      t._colWidth = options.colWidth;
      t._colSpacing = options.colSpacing;
      t._rowSpacing = options.rowSpacing;
      t.dataURL = options.dataURL;
      t.dataType = options.dataType;
      t.page = options.page;
      t.pageEnd = options.pageEnd;
      t._switch = false;

      //计算有几列 总宽度 / (列宽 + 列间隔)
      t._totalCols = parseInt(t._container.width() / (t._colWidth + t._colSpacing));

      // 创建用于记录每列高度的数组
      t._cols = new Array(t._totalCols);
      // 初始化为0
      for (var i = 0; i < t._cols.length; i++) {
        t._cols[i] = 0;
      }

      t._loadingPage = options.page || 0;
      t._loadNext(options);

      //下拉滚动条加载
      var lastTime = new Date().getTime();

      $(window).scroll(function() {

        if ( !t._switch ) {
          //判断是否滚动过快,在ie下
          var thisTime = new Date().getTime();

          if (thisTime - lastTime < 50) {
            console.log(thisTime - lastTime);
            lastTime = thisTime;
            return;
          }

          if ($(window).scrollTop() + $(window).height() >= document.documentElement.scrollHeight) {
            lastTime = thisTime;
            t._loadNext();
          }
        }
      });
    },
    //加载器
    _loadNext: function(t) {
      var t = this;

      t._switch = true;
      //请求数据
      if (!t.trigger) {
        $.ajax({
          url: t.dataURL,
          data: { page: ++t._loadingPage },
          dataType:t.dataType,
          success: function(response){
            
            t.trigger = t._completeLoading(response);

          },
          error:function(){console.log('Error! 请求有误');}
        });  
      }
      return false;
    },
    //加载完数据调用此函数
    _completeLoading: function(result) {
      var t = this;
      if (t._loadingPage >= t.pageEnd) {
        $('#more').hide();
        $('#loading').html('<p>已是最后一页了喔 ^_^ ^_^</p>');
        return true;
      }
      else {
        //if (!pageNum()) {
          t._add(result);
        //};
      }
      
      return false;
    },
    //添加格子
    _add: function(result) {
      var t = this, grids = '';
      //调用模板
      var content = Tmpl.render(t._template, {data:result.data});
      //原始定位
        t._grids = $(content).css({
          position: 'absolute',
          left: t._container.width(),
          top: t._container.height(), 
          width: t._colWidth,
          opacity: 0
        });

      //把Html添加到容器
      t._container.append(t._grids);

      // 执行一次_reposition,如果所有图片都加载完成,该方法返回true,否则返回false
      if ( !t._reposition() ) {
        // 有图片未加载完,监听onload和onerror
        t._grids.find('img').bind('load error', function() {
          this.loaded = true;
          // 有图片加载完成,再次执行_reposition
          if (t._grids) {
            t._reposition();
          }
        });
      }
    },
    // 此方法用于获取高度最低的列
    _getLowestCol: function() {
      var cols = this._cols, min = 0;
      for (var i = 1; i < cols.length; i++) {
        if (cols[i] < cols[min]) {
          min = i;
        }
      }
      return min;
    },
    //定位
    _reposition: function() {

      var t = this, allImgsLoaded = true;

      // 检测图片是否全部加载完成
      t._grids.find('img').each(function(i, img) {
        if (!img.loaded && !img.complete) {

          allImgsLoaded = false;
        }
        return allImgsLoaded;
      });

      if (allImgsLoaded) {
        t._grids.each(function(i, grid) {
          //先显示出来
          grid = $(grid).show();
          
          var height = grid.outerHeight(), min = t._getLowestCol();

          // 非第一行的时候,要加上行间隔
          if (t._cols[min]) { t._cols[min] += t._rowSpacing; }
          // 定位
          grid.animate({
            left: (t._colWidth + t._colSpacing) * min,
            top: t._cols[min],
            opacity: 1
          },1000);
          // 记录高度
          t._cols[min] += height;
        });
        // 重设外层容器高度为最高列高度
        t._container.css( 'height', Math.max.apply(Math, t._cols) );
        t._switch = false;
        delete t._grids;
      }

      return allImgsLoaded;
    },
  }

  return waterFall;
});

以上就是本文的全部内容,希望对大家学习javascript程序设计有所帮助。

Javascript 相关文章推荐
iframe 自适应高度[在IE6 IE7 FF下测试通过]
Apr 13 Javascript
JS 字符串连接[性能比较]
May 10 Javascript
javascript &amp;&amp;和||运算法的另类使用技巧
Nov 28 Javascript
jquery插件之easing 动态菜单
Aug 21 Javascript
JS实现仿百度输入框自动匹配功能的示例代码
Feb 19 Javascript
快速学习AngularJs HTTP响应拦截器
Dec 31 Javascript
JS中对象与字符串的互相转换详解
May 20 Javascript
js实现界面向原生界面发消息并跳转功能
Nov 22 Javascript
Bootstrap表单控件使用方法详解
Jan 11 Javascript
jQuery中内容过滤器简单用法示例
Mar 31 jQuery
Vue手把手教你撸一个 beforeEnter 钩子函数
Apr 24 Javascript
layer关闭当前窗口页面以及确认取消按钮的方法
Sep 09 Javascript
基于JQuery实现图片轮播效果(焦点图)
Feb 02 #Javascript
Hammer.js+轮播原理实现简洁的滑屏功能
Feb 02 #Javascript
JavaScript实现的MD5算法完整实例
Feb 02 #Javascript
javascript禁止超链接跳转的方法
Feb 02 #Javascript
JS组件Bootstrap Table使用方法详解
Feb 02 #Javascript
Node.js实现JS文件合并小工具
Feb 02 #Javascript
体验jQuery和AngularJS的不同点及AngularJS的迷人之处
Feb 02 #Javascript
You might like
用PHP实现 上一篇、下一篇的代码
2012/09/29 PHP
解析curl提交GET,POST,Cookie的简单方法
2013/06/29 PHP
33道php常见面试题及答案
2015/07/06 PHP
JS 自动完成 AutoComplete(Ajax 查询)
2009/07/07 Javascript
js获取GridView中行数据的两种方法 分享
2013/07/13 Javascript
easyui datagrid 键盘上下控制选中行示例
2014/03/31 Javascript
使用JavaScript获取电池状态的方法
2014/05/03 Javascript
改变状态栏文字的js代码
2014/06/13 Javascript
浅析javascript的间隔调用和延时调用
2014/11/12 Javascript
jQuery+ajax中getJSON() 用法实例
2014/12/22 Javascript
javascript 动态创建表格
2015/01/08 Javascript
JS闭包可被利用的常见场景小结
2017/04/09 Javascript
简单实现js轮播图效果
2017/07/14 Javascript
vue中页面跳转拦截器的实现方法
2017/08/23 Javascript
C#实现将一个字符转换为整数
2017/12/12 Javascript
Vue.js 中取得后台原生HTML字符串 原样显示问题的解决方法
2018/06/10 Javascript
jQuery实现ajax的嵌套请求案例分析
2019/02/16 jQuery
Node.js 多进程处理CPU密集任务的实现
2019/05/26 Javascript
JS中的const命令你真懂它吗
2020/03/08 Javascript
JavaScript 几种循环方式以及模块化的总结
2020/09/03 Javascript
[07:47]DOTA2国际邀请赛采访专栏:探访Valve总部
2013/08/08 DOTA
[02:07]DOTA2超级联赛专访BBC:难忘网吧超神经历
2013/06/09 DOTA
[02:26]DOTA2英雄米拉娜基础教程
2013/11/25 DOTA
[40:29]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第一场
2018/04/10 DOTA
[43:57]LGD vs Mineski 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Python中的descriptor描述器简明使用指南
2016/06/02 Python
浅析Python中else语句块的使用技巧
2016/06/16 Python
Python实现修改文件内容的方法分析
2018/03/25 Python
Python实现的拟合二元一次函数功能示例【基于scipy模块】
2018/05/15 Python
用ldap作为django后端用户登录验证的实现
2020/12/07 Python
Python 利用argparse模块实现脚本命令行参数解析
2020/12/28 Python
Expedia加拿大官方网站:加拿大最大的在线旅游提供商
2017/12/31 全球购物
学习型党组织建设经验材料
2014/05/26 职场文书
房屋授权委托书范本
2014/10/07 职场文书
正规欠条模板
2015/07/03 职场文书
Vue和Flask通信的实现
2021/05/19 Vue.js