使用Bootstrap与jQuery实现瀑布流

使用Bootstrap与jQuery实现瀑布流

前端实现瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest。瀑布流的关键在于布局中的每一个元素等宽不等高。

实现多栏的布局,如果使用html5+css3的原生分栏方式的话,每当有新元素加入时,并不是均匀的加载每一栏的最底部,而是加在最后一栏当中,这就导致用户向下滚动时,在页面最下方的不是新加载的内容。结合bootstrap的网格化布局将页面分为三栏,使用另外的js脚本实现添加元素的逻辑。

<div class="container">
    <div class="row waterfalls">
        <div class="col-lg-4 waterfall"></div>
        <div class="col-lg-4 waterfall"></div>
        <div class="col-lg-4 waterfall"></div>
    </div>
</div>

如何实现每个元素均匀的添加到其中?算法比较简单,获取元素后,遍历每一栏,计算其高度,将元素添加到高度最小的一栏中。

$.getJSON('/loadmore', {t: requestTime++}, function (data) {
        for (let i = 0; i < data.length; i++) {    // data为后端响应来的json数据
            let waterfall = findShortest();
            let el = document.createElement('div');
            // write data into el, do some other things with el, 
            waterfall.append(el);
        }
    });

获取每一栏高度的时候,不能直接获取每一栏的offsetHeight属性,因为每一栏是个div元素,都处在Bootstrap中的同一行,其高度都是最高的一个,即行高。需要遍历其子元素来计算高度。为了实现在手机等窄屏设备下的良好展现,pc上三栏的瀑布流在手机上并为一行,那么在脚本中,检测到是窄屏时,新加入的元素就自动添加到最后一栏的末尾。

function getWaterfallLen(waterfall) {
    let lengthSum = 0;
    waterfall = waterfall.children;
    for (let i = 0; i < waterfall.length; i++) {
        lengthSum += waterfall[i].offsetHeight;
    }
    return lengthSum;
}

function findShortest() {
    let waterlalls = $('.waterfall');
    if ($(window).width() < 1200) {     // 适配手机
        return waterlalls[2]; 
    }
    let shortestOne = 0;
    let shortestLen = getWaterfallLen(waterlalls[0]);
    for (let i = 0; i < 3; i++) {
        let len = getWaterfallLen(waterlalls[i]);
        if (shortestLen > len) {
            shortestLen = len;
            shortestOne = i;
        }
    }
    return waterlalls[shortestOne];
}

如何实现瀑布流数据请求

问题进一步具体描述:如何实现根据用户请求每次返回固定数量的元素,当没有东西可返回或者用户请求次数达到上限时阻止用户继续请求并告知用户。

现有的条件和环境:前端页面实现了瀑布流,并可以通过用户点击,触发ajax,并接受一个数组,放置数组中的元素到页面中正确的地方,数组以json文件传输。后端有足够的元素,可以通过分页的方式将查询数据分成固定数量的若干数组。数组中元素的数据结构为带html格式的字符串。字符串中出现双引号会有BUG,导致后端无法正常将数据编码为json格式,前端无法正常解析html,插入到网页中导致显示错误。

问题:如何解决引号的bug?如何限制用户的请求次数?后端如何知晓用户请求到哪里了?如何将数据正确转换为json,可否优化数据结构?如何提示用户不可据徐请求?

现有的条件和环境:json编码是使用jinja模板通过字符串拼接的方式进行的,这就定位了引号bug的来源。前端解析也是直接通过原生js 的innerHTML方法插入的,前端的粗暴做法也带来了隐患。

解决:重构数据结构,使用python的json相关的库实现(p60-64),前端也使用createNode和setAttribute方式创立dom对象再插入。这样可以基本上不改变现有的通讯方式和数组的形式。后端代码如下

articles = list(map(format_article,articles)) 
# articles是查询到的数据 format_article是将一个文章数据格式化成字典的函数
return json.dumps(articles)	# 转化为json字符串发送给前端

对于获取用户请求的位置和限制次数,我想的是在前端发送的请求中带一个请求次数的数据。具体实现,在前端的js环境中创造一个记录次数的变量,正常请求时,请求成功就次数+1。超过预定的次数,在前端就阻止其继续发送请求(表现为按钮上的字提示为“请前往文章分类获取更多”,按钮变灰)。如果非正常的通过url发送请求,缺失参数或者参数不正确则掷出404错误。需要注意的是在使用paginate分页后获得的是pagination对象,需要获取其items属性才能获取到列表继续操作。

使用jQuery可以进一步简化以上复制一个新节点的方式。在使用选择器选择到jQ的节点后,使用clone方法可以直接复制一个,复制到一个新的变量上。设置具体操作class也可简化为addClassremoveClassattr('class')。在js中判断字符串是否含有某一个字串,不能直接使用python的in关键字,但有一个search方法可以调用,这个方法不仅可以进行普通的子串匹配,还可以进行正则表达式的匹配,功能强大。只不过需要注意的是,匹配成功会返回匹配成功的第一个字串的索引,故返回0并不是没有找到,没要找到的结果是-1。如果是判断匹配成功,表达式应该为:<string>.search(<substring>) !=== -1

批量实现超链接

虽然在页面视觉上实现了瀑布流,但点击了卡片并不会触发向相应内容的跳转。我一开始的想法是在每个卡片的基础上添加一个a超链接标签,但是在我添加过后,卡片的样式受到了较大的改动,下划线、颜色、字间距等等收到了较大的影响,我懒得改css,这个方案否决了。于是我决定通过使用js来实现,使用js实现第一个难题就是如何得知每个卡片内容对应的url。我决定在每个卡片的标签上添加一个id属性,存储其id并结合总体的url规则可以拼接出一个目标url。接下来需要解决的就是如何给每一个卡片(标签组)绑定上点击事件,最终实现跳转。

昨天的情况比较好实现,在生成dom节点时就使用jQ绑定click事件,绑定后的元素再插入到总的文档树中,万事大吉。但在一个不是使用ajax异步生成节点的情况下又应该怎么做呢?绑定事件的前提是选择上dom元素,我一开始的方案是在父节点上规定一个id,在选取到它过后使用children方法遍历其下面的目标元素。但是jQ的children并不是默认返回子元素的数组,而是一个选择器函数返回的子匹配数组。这就相当于,需要多在目标子元素上设置一些属性,来标记需要被选择的元素。但这不是多此一举吗?我可以直接通过这些标识来实现选择呀!恰好jQ 的class选择器选出来是一个符合要求的数组,可以通过这个数组来操作。

如果直接使用循环在其中绑定click回调函数,并不能实现上面的目标。因为目标元素是写死在dom树中的,需要等待文档加载完才能操作,也就是说,一切操作是要写在ready的回调函数中,根据es6标准,在ready中的函数在执行完后其中的数据是会被销毁的,具体地说,就是在这个里面获取的文档数据不是全局变量,会被视作本地变量在执行完后销毁,for循环中做出的这些操作无效。为了解决这个问题,需要定义一个全局变量,存储这些改变。我的方案是,在全局建立一个数组,在做出改变后,将数据’注册‘到全局中,保存改变。需要注意的是,不能使用click回调函数,需要使用onclick绑定函数,否则点击后会没有反应。具体代码参考为:

let arr = [];
$(document).ready(function () {
    let articles = $('.article');
    for (let i = 0; i < articles.length; i++) {
        let article = articles[i];
        article.onclick = function () {
            window.location.href = '/blog/articles/' + article.id;
        };
        arr.push(article);
    }
});

绑定事件

上文所述的绑定事件的方法其实是js的原生的绑定的方法,在jQuery中,绑定应该是直接调用对象的on方法(v1.7后)。on() 和 click() 的区别:二者在绑定静态控件时没有区别,但是如果面对动态产生的控件,只有 on() 能成功的绑定到动态控件中[pharma]。那jQuery中on、bind、live这三种事件绑定的方法有什么区别呢?在[枫上善若水]的博客[浅谈Jquery中的bind(),live(),delegate(),on()绑定事件方式]中有很清楚的说明:

相同点:

1.都支持单元素多事件的绑定;空格相隔方式或者大括号替代方式;

2.均是通过事件冒泡方式,将事件传递到document进行事件的响应;

比较和联系:

1.bind()函数只能针对已经存在的元素进行事件的设置;但是live(),on(),delegate()均支持未来新添加元素的事件设置;

2.bind()函数在jquery1.7版本以前比较受推崇,1.7版本出来之后,官方已经不推荐用bind(),替代函数为on(),这也是1.7版本新添加的函数,同样,可以用来代替live()函数,live()函数在1.9版本已经删除;

3.live()函数和delegate()函数两者类似,但是live()函数在执行速度,灵活性和CSS选择器支持方面较delegate()差些,想了解具体情况,请戳这[jQuery的.bind()、.live()和.delegate()之间区别]

4.bind()支持jQuery所有版本;live()支持jquery1.8-;delegate()支持jquery1.4.2+;on()支持jquery1.7+;

进一步,上述绑定打开页面的代码可以进一步修改为以下形式,取消了数组对内存的额外占用:

$(document).ready(function () {
    let articles = $('.article');
    for (let i = 0; i < articles.length; i++) {
        articles[i].on('click',function(){
            window.location.href = '/blog/articles/' + article.id;
        });
    }
});

事件捕获与事件冒泡

在研究绑定事件中,经常可以看到事件冒泡的说法。我们每在浏览器中做出一个操作,每触发一个事件,JavaScript响应他其实是有一个过程的。比如点击一个div标签中的a标签时,浏览器会从body->div->a->div->body对DOM从外到内、从内到外进行一次遍历。事件所绑定的动作就在这个遍历的过程中触发。一来一回经过了两次标签啊为啥只触发一次事件呢?这是因为在没有指定的情况下,动作只在从内到外的过程中触发,这就是事件冒泡。事件捕获就是在从外到内的过程中触发动作。整个遍历过程有点像二叉树的w遍历的简化(树简化成了链表,w简化成了u)。

因为遍历这个过程的存在,就可以在一个元素上绑定了某个动作后,通过将事件作用于其子元素来触发。这样一来,一路上绑定了多个动作,就会有事件执行顺序的问题。可以通过一个例子来理解。

<div id="div0">
    <div id="div1">
        <div id="div2"> 点我 </div>
    </div>
</div>

<script>
    div0 = document.getElementById('div0');
    div1 = document.getElementById('div1');
    div2 = document.getElementById('div2');
    div0.onclick = function () { console.log('0'); };
    div1.addEventListener('click', function () { console.log('1'); }, true); // 捕获
    div2.addEventListener('click', function () { console.log('2'); }, false);
</script>

输出为1、2、0。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值