您好,欢迎来到华拓科技网。
搜索
您的当前位置:首页javascript自适应宽度的瀑布流实现思路_javascript技巧

javascript自适应宽度的瀑布流实现思路_javascript技巧

来源:华拓科技网


这样的布局并不陌生,从2011年Pinterest创立以来,中国互联网就迅速掀起了一股模仿Pinterest的热潮,国内有众多网站采用瀑布流的布局方式,例如花瓣网、美丽说等等。而事实上在中国互联网,模仿一些在国外被人看好的模式(当然,你也可以说是山寨或抄袭,呵呵!!)向来都是一个不错的idea。

OK,现在进入正题。这里主要介绍瀑布流的一种实现方法:绝对定位(css)+javascript+ajax+json。简单一点如果不做滚动加载的话就是绝对定位(css)+javascript了,ajax和json是滚动加载更多内容的时候用到的。

下面是实现思路:

1、计算页面的宽度,计算出页面可放数据块的列数(如上图所示就有6列)。

2、将各个数据块的高度尺寸记入数组中(需要等所有图片加载完成,否则无法知道图片的高度)。

3、用绝对定位先将页面第一行填满,因为第一行的top位置都是一样的,然后用数组记录每一列的总高度。

4、继续用绝对定位将其他数据块定位在最短的一列的位置之后然后更新该列的高度。

5、当浏览器窗口大小改变时,重新执行一次上面1-4步以重新排放(列数随页面宽度而改变,因而需要重新排放)。

6、滚动条滚动到底部时加载新的数据进来后也是定位在最短的一列的位置之后然后更新该列的高度。

思路有了,然后就是如何用代码实现。当然,如果看完以上的6个步骤你已经知道如何实现,那么下面的内容大可不必细看。

首先在页面上写好基本的HTML和CSS(为方便起见,CSS就不外联了),代码如下:
代码如下:




瀑布流布局




  • 图片标题1

  • 图片标题2

  • 图片标题3

  • 图片标题4

  • 图片标题5

  • 图片标题6

  • 图片标题7

  • 图片标题8

  • 图片标题9

  • 图片标题10

  • 图片标题11

  • 图片标题12

  • 图片标题13

  • 图片标题14

  • 图片标题15

  • 图片标题16

  • 图片标题17

  • 图片标题18

  • 图片标题19

  • 图片标题20






  • 以上代码非常简单,可以看出页面最初将会先加载20个数据块。值得一提的是在CSS里面定义了opacity为0,目的是在数据块未排放好之前先隐藏起来,排放好后再将opacity设为1显示出来,另外这里用了css3的transition做一点体验上的升级;还有一点就是可以看到页面底部有一个id为“loading”的DIV,用来表示数据正在加载中。下面开始用JS实现以上思路(6个步骤)。

    1、计算页面的宽度,计算出页面可放数据块的列数
    代码如下:

    function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
    var w = document.documentElement.offsetWidth;//计算页面宽度
    var ul = document.getElementById("flow-box");
    var li = ul.getElementsByTagName("li");
    var iw = li[0].offsetWidth + mh;//计算数据块的宽度
    var c = Math.floor(w / iw);//计算列数
    ul.style.width = iw * c - mh + "px";//设置ul的宽度至适合便可以利用css定义的margin把所有内容居中
    }


    注释写得非常明白,这一步不说应该都很容易懂。

    2、将各个数据块的高度尺寸记入数组中
    代码如下:

    function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
    //... 省略上一步的部份代码 ...
    ul.style.width = iw * c - mh + "px";//设置ul的宽度至适合便可以利用css定义的margin把所有内容居中

    var liLen = li.length;
    var lenArr = [];
    for (var i = 0; i < liLen; i++) {//遍历每一个数据块将高度记入数组
    lenArr.push(li[i].offsetHeight);
    }
    }


    由于数据块里面含有图片,也没有给定图片的尺寸,所以需要等待图片加载完成后方可获取其高度;那么可以在window.onload的时候调用flow方法。代码变成:
    代码如下:

    function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
    //... 省略上一步的部份代码 ...
    ul.style.width = iw * c - mh + "px";//设置ul的宽度至适合便可以利用css定义的margin把所有内容居中

    var liLen = li.length;
    var lenArr = [];
    for (var i = 0; i < liLen; i++) {//遍历每一个数据块将高度记入数组
    lenArr.push(li[i].offsetHeight);
    }
    }
    //图片加载完成后执行
    window.onload = function() {flow(10, 10)};


    3、用绝对定位先将页面第一行填满,因为第一行的top位置都是一样的,然后用数组记录每一列的总高度。
    代码如下:

    function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
               //... 省略上一步的部份代码 ...
    for (var i = 0; i < liLen; i++) {//遍历每一个数据块将高度记入数组
    lenArr.push(li[i].offsetHeight);
    }

    var oArr = [];
    for (var i = 0; i < c; i++) {//把第一行排放好,并将每一列的高度记入数据oArr
    li[i].style.top = "0";
    li[i].style.left = iw * i + "px";
    li[i].style.opacity = "1";
    li[i].style["-moz-opacity"] = "1";
    li[i].style["filter"] = "alpha(opacity=100)";
    oArr.push(lenArr[i]);
    }
    document.getElementById("loadimg").style.top = _getMaxValue(oArr) + 50 + "px";//将loading移到下面
    }
    //图片加载完成后执行
    window.onload = function() {flow(10, 10)};
    //获取数字数组的最大值
    function _getMaxValue(arr) {
    var a = arr[0];
    for (var k in arr) {
    if (arr[k] > a) {
    a = arr[k];
    }
    }
    return a;
    }


    截至目前为止,可以到浏览器里面预览一下效果:

    OK,接下来开始放置其他的数据块了,也就是到思路的第4步了。

    4、继续用绝对定位将其他数据块定位在最短的一列的位置之后然后更新该列的高度。
    代码如下:

    function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
    //... 省略上一步的部份代码 ...
    for (var i = 0; i < c; i++) {//把第一行排放好,并将每一列的高度记入数据oArr
    li[i].style.top = "0";
    li[i].style.left = iw * i + "px";
    li[i].style.opacity = "1";
    li[i].style["-moz-opacity"] = "1";
    li[i].style["filter"] = "alpha(opacity=100)";
    oArr.push(lenArr[i]);
    }

    for (var i = c; i < liLen; i++) {//将其他数据块定位到最短的一列后面,然后再更新该列的高度
    var x = _getMinKey(oArr);//获取最短的一列的索引值
    li[i].style.top = oArr[x] + mv + "px";
    li[i].style.left = iw * x + "px";
    li[i].style.opacity = "1";
    li[i].style["-moz-opacity"] = "1";
    li[i].style["filter"] = "alpha(opacity=100)";
    oArr[x] = lenArr[i] + oArr[x] + mv;//更新该列的高度
    }
    document.getElementById("loadimg").style.top = _getMaxValue(oArr) + 50 + "px";//将loading移到下面
    }
    //图片加载完成后执行
    window.onload = function() {flow(10, 10)};
    //获取数字数组的最大值
    function _getMaxValue(arr) {
    //... 省略部份代码 ...
    }
    //获取数字数组最小值的索引
    function _getMinKey(arr) {
    var a = arr[0];
    var b = 0;
    for (var k in arr) {
    if (arr[k] < a) {
    a = arr[k];
    b = k;
    }
    }
    return b;
    }


    到这一步可以到浏览器里面再看一次效果,可以说整个瀑布流的雏形都出来了:

    5、当浏览器窗口大小改变时,重新执行一次上面1-4步以重新排放

    这一步操作起来也相当便捷,在改变窗口大小时,再执行一次flow方法即可
    代码如下:

    function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
    //... 省略部份代码 ...
    //图片加载完成后执行
    window.onload = function() {flow(10, 10)};
    //改变窗口大小时重新布局
    var re;
    window.onresize = function() {
    clearTimeout(re);
    re = setTimeout(function() {flow(10, 10);}, 200);
    }
    //获取数字数组的最大值
    function _getMaxValue(arr) {
    //... 省略部份代码 ...
    }
    //获取数字数组最小值的索引
    function _getMinKey(arr) {
    //... 省略部分代码 ...
    }


    这里值得注意的便是setTimeout,由于onresize的触发频率非常高,用setTimout设定一个间隔时间可以减低flow方法的执行频率,降低性能损耗。

    6、滚动条滚动到底部时加载新的数据进来后也是定位在最短的一列的位置之后然后更新该列的高度。
    代码如下:

    function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
    //... 省略部份代码 ...
    document.getElementById("loadimg").style.top = _getMaxValue(oArr) + 50 + "px";//将loading移到下面

    function scroll() {//滚动加载数据
    var st = oArr[_getMinKey(oArr)];
    var scrollTop = document.documentElement.scrollTop > document.body.scrollTop? document.documentElement.scrollTop : document.body.scrollTop;
    if (scrollTop >= st - document.documentElement.clientHeight) {
    window.onscroll = null;//为防止重复执行,先清除事件
    _request(null, "GetList.php", function(data) {//当滚动到达最短的一列的距离时便发送ajax请求新的数据,然后执行回调函数
    _addItem(data.d, function() {//追加数据
    var liLenNew = li.length;
    for(var i = liLen; i < liLenNew; i++) {
    lenArr.push(li[i].offsetHeight);
    }
    for(var i = liLen; i < liLenNew; i++) {
    var x = _getMinKey(oArr);
    li[i].style.top = oArr[x] + 10 + "px";
    li[i].style.left = iw * x + "px";
    li[i].style.opacity = "1";
    li[i].style["-moz-opacity"] = "1";
    li[i].style["filter"] = "alpha(opacity=100)";
    oArr[x] = lenArr[i] + oArr[x] + 10;
    }
    document.getElementById("loadimg").style.top = _getMaxValue(oArr) + 50 + "px";//loading向下移位
    liLen = liLenNew;
    window.onscroll = scroll;//执行完成,恢愎onscroll事件
    });
    })
    }
    }
    window.onscroll =scroll;
    }
    //图片加载完成后执行
    window.onload = function() {flow(10, 10)};
    //... 省略部份代码 ...
    //追加项
    function _addItem(arr, callback) {
    var _html = "";
    var a = 0;
    var l = arr.length;
    (function loadimg() {
    var img = new Image();
    img.onload = function() {
    a += 1;
    if (a == l) {
    for (var k in arr) {
    var img = new Image();
    img.src = arr[k].img;
    _html += '

  • ' + arr[k].title + '
  • ';
    }
    _appendhtml(document.getElementById("flow-box"), _html);
    callback();
    }
    else {
    loadimg();
    }
    }
    img.src = arr[a].img;
    })()
    }
    //ajax请求
    function _request(reqdata, url, callback) {
    var xmlhttp;
    if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
    }
    else {
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange = function () {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    var data = eval("(" + xmlhttp.responseText + ")");
    callback(data);
    }
    }
    xmlhttp.open("POST", url);
    xmlhttp.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    xmlhttp.send(reqdata);
    }
    //追加html
    function _appendhtml(parent, child) {
    if (typeof (child) == "string") {
    var div = document.createElement("div");
    div.innerHTML = child;
    var frag = document.createDocumentFragment();
    (function() {
    if (div.firstChild) {
    frag.appendChild(div.firstChild);
    arguments.callee();
    }
    else {
    parent.appendChild(frag);
    }
    })();
    }
    else {
    parent.appendChild(child);
    }
    }
    //获取数字数组的最大值
    function _getMaxValue(arr) {
    //... 省略部份代码 ...
    }
    //获取数字数组最小值的索引
    function _getMinKey(arr) {
    //... 省略部份代码 ...
    }


    这一步涉及的代码比较多,简单概括其实就是多了几个方法:scroll()、_addItem()、_request()、_appendhtml()。

    主要是看scroll()。在这里_addItem()和_requeat()是供scroll()调用的,而_appendhtml()是供_addItem()调用的。

    这一步的整个过程是:当页面滚动到最短的一列数据的底部时就发出ajax请求加载新的数据,然后待数据中的图片全部load完后就追加到页面上,然后将这些数据项的高度写入到数组lenArr中,并对新加入的这些数据项进行定位,按照每一项都放在最短列的后面的规则而排放在适当的位置上,最后再将loading图片向下移到最底部的位置。

    总结以上的整个思路,有4个地方值得一说:

    1、缩放浏览器窗口时,onresize的触发很频繁,为降低性能损耗,需要待缩放结束后再执行重排,以上思路是使用setTimeout来处理。

    2、页面滚动到最下面加载新数据的时候,只需对新数据排列。

    3、以上思路中加载新数据要等图片都加载完成后才知道其高度,但实际项目中最好是服务器能给定高度值。

    4、滚动触发加载新数据时,要避免事件多次触发,以上思路是将onscroll事件置为空,加载完成后再将事件恢复。

    最后附上完整的代码:
    flow.html
    代码如下:




    瀑布流布局




  • 图片标题1

  • 图片标题2

  • 图片标题3

  • 图片标题4

  • 图片标题5

  • 图片标题6

  • 图片标题7

  • 图片标题8

  • 图片标题9

  • 图片标题10

  • 图片标题11

  • 图片标题12

  • 图片标题13

  • 图片标题14

  • 图片标题15

  • 图片标题16

  • 图片标题17

  • 图片标题18

  • 图片标题19

  • 图片标题20




  • function flow(mh, mv) {//参数mh和mv是定义数据块之间的间距,mh是水平距离,mv是垂直距离
    var w = document.documentElement.offsetWidth;//计算页面宽度
    var ul = document.getElementById("flow-box");
    var li = ul.getElementsByTagName("li");
    var iw = li[0].offsetWidth + mh;//计算数据块的宽度
    var c = Math.floor(w / iw);//计算列数
    ul.style.width = iw * c - mh + "px";//设置ul的宽度至适合便可以利用css定义的margin把所有内容居中

    var liLen = li.length;
    var lenArr = [];
    for (var i = 0; i < liLen; i++) {//遍历每一个数据块将高度记入数组
    lenArr.push(li[i].offsetHeight);
    }

    var oArr = [];
    for (var i = 0; i < c; i++) {//把第一行排放好,并将每一列的高度记入数据oArr
    li[i].style.top = "0";
    li[i].style.left = iw * i + "px";
    li[i].style.opacity = "1";
    li[i].style["-moz-opacity"] = "1";
    li[i].style["filter"] = "alpha(opacity=100)";
    oArr.push(lenArr[i]);
    }

    for (var i = c; i < liLen; i++) {//将其他数据块定位到最短的一列后面,然后再更新该列的高度
    var x = _getMinKey(oArr);//获取最短的一列的索引值
    li[i].style.top = oArr[x] + mv + "px";
    li[i].style.left = iw * x + "px";
    li[i].style.opacity = "1";
    li[i].style["-moz-opacity"] = "1";
    li[i].style["filter"] = "alpha(opacity=100)";
    oArr[x] = lenArr[i] + oArr[x] + mv;//更新该列的高度
    }
    document.getElementById("loadimg").style.top = _getMaxValue(oArr) + 50 + "px";//将loading移到下面

    function scroll() {//滚动加载数据
    var st = oArr[_getMinKey(oArr)];
    var scrollTop = document.documentElement.scrollTop > document.body.scrollTop? document.documentElement.scrollTop : document.body.scrollTop;
    if (scrollTop >= st - document.documentElement.clientHeight) {
    window.onscroll = null;//为防止重复执行,先清除事件
    _request(null, "GetList.php", function(data) {//当滚动到达最短的一列的距离时便发送ajax请求新的数据,然后执行回调函数
    _addItem(data.d, function() {//追加数据
    var liLenNew = li.length;
    for(var i = liLen; i < liLenNew; i++) {
    lenArr.push(li[i].offsetHeight);
    }
    for(var i = liLen; i < liLenNew; i++) {
    var x = _getMinKey(oArr);
    li[i].style.top = oArr[x] + 10 + "px";
    li[i].style.left = iw * x + "px";
    li[i].style.opacity = "1";
    li[i].style["-moz-opacity"] = "1";
    li[i].style["filter"] = "alpha(opacity=100)";
    oArr[x] = lenArr[i] + oArr[x] + 10;
    }
    document.getElementById("loadimg").style.top = _getMaxValue(oArr) + 50 + "px";//loading向下移位
    liLen = liLenNew;
    window.onscroll = scroll;//执行完成,恢愎onscroll事件
    });
    })
    }
    }
    window.onscroll =scroll;
    }
    //图片加载完成后执行
    window.onload = function() {flow(10, 10)};
    //改变窗口大小时重新布局
    var re;
    window.onresize = function() {
    clearTimeout(re);
    re = setTimeout(function() {flow(10, 10);}, 200);
    }
    //追加项
    function _addItem(arr, callback) {
    var _html = "";
    var a = 0;
    var l = arr.length;
    (function loadimg() {
    var img = new Image();
    img.onload = function() {
    a += 1;
    if (a == l) {
    for (var k in arr) {
    var img = new Image();
    img.src = arr[k].img;
    _html += '

  • ' + arr[k].title + '
  • ';
    }
    _appendhtml(document.getElementById("flow-box"), _html);
    callback();
    }
    else {
    loadimg();
    }
    }
    img.src = arr[a].img;
    })()
    }
    //ajax请求
    function _request(reqdata, url, callback) {
    var xmlhttp;
    if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
    }
    else {
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xmlhttp.onreadystatechange = function () {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    var data = eval("(" + xmlhttp.responseText + ")");
    callback(data);
    }
    }
    xmlhttp.open("POST", url);
    xmlhttp.setRequestHeader("Content-Type", "application/json; charset=utf-8");
    xmlhttp.send(reqdata);
    }
    //追加html
    function _appendhtml(parent, child) {
    if (typeof (child) == "string") {
    var div = document.createElement("div");
    div.innerHTML = child;
    var frag = document.createDocumentFragment();
    (function() {
    if (div.firstChild) {
    frag.appendChild(div.firstChild);
    arguments.callee();
    }
    else {
    parent.appendChild(frag);
    }
    })();
    }
    else {
    parent.appendChild(child);
    }
    }
    //获取数字数组的最大值
    function _getMaxValue(arr) {
    var a = arr[0];
    for (var k in arr) {
    if (arr[k] > a) {
    a = arr[k];
    }
    }
    return a;
    }
    //获取数字数组最小值的索引
    function _getMinKey(arr) {
    var a = arr[0];
    var b = 0;
    for (var k in arr) {
    if (arr[k] < a) {
    a = arr[k];
    b = k;
    }
    }
    return b;
    }




    GetList.php
    代码如下:
    header("Content-Type:application/json;charset=utf-8");
    echo('{"d": [
    {"img": "http://www.mitxiong.com/NewsImages/2012121821504156.jpg", "title": "图片1"},
    {"img": "http://www.mitxiong.com/NewsImages/2012112718241731.jpg", "title": "图片2"},
    {"img": "http://www.mitxiong.com/NewsImages/2012111806582944.jpg", "title": "图片3"},
    {"img": "http://www.mitxiong.com/NewsImages/2012110907231232.jpg", "title": "图片4"},
    {"img": "http://www.mitxiong.com/NewsImages/2012110406319529.jpg", "title": "图片5"},
    {"img": "http://www.mitxiong.com/NewsImages/2012101808066955.jpg", "title": "图片6"},
    {"img": "http://www.mitxiong.com/NewsImages/2012101307276582.jpg", "title": "图片7"},
    {"img": "http://www.mitxiong.com/NewsImages/2012082223432719.jpg", "title": "图片8"},
    {"img": "http://www.mitxiong.com/NewsImages/2012082121509065.jpg", "title": "图片9"},
    {"img": "http://www.mitxiong.com/NewsImages/2012081922387254.jpg", "title": "图片10"},
    {"img": "http://www.mitxiong.com/NewsImages/2012081700252403.jpg", "title": "图片11"},
    {"img": "http://www.mitxiong.com/NewsImages/2012081407597304.jpg", "title": "图片12"},
    {"img": "http://www.mitxiong.com/NewsImages/2012081218248259.jpg", "title": "图片13"},
    {"img": "http://www.mitxiong.com/NewsImages/2012080621278799.jpg", "title": "图片14"},
    {"img": "http://www.mitxiong.com/NewsImages/2012072907484455.jpg", "title": "图片15"},
    {"img": "http://www.mitxiong.com/NewsImages/20120725215314.jpg", "title": "图片16"},
    {"img": "http://www.mitxiong.com/NewsImages/2012072507238259.jpg", "title": "图片17"},
    {"img": "http://www.mitxiong.com/NewsImages/2012072409035684.jpg", "title": "图片18"},
    {"img": "http://www.mitxiong.com/NewsImages/2012072219405236.jpg", "title": "图片19"},
    {"img": "http://www.mitxiong.com/NewsImages/2012071218416980.jpg", "title": "图片20"}
    ]}');
    ?>

    Copyright © 2019- huatuo6.cn 版权所有 赣ICP备2024042791号-9

    违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

    本站由北京市万商天勤律师事务所王兴未律师提供法律服务