通过原生JS实现瀑布流布局

关于瀑布流布局

          • 一、需要掌握的知识点
          • 二、思路
          • 三、具体解决方法

由于工作需要,花了几天时间研究瀑布流布局。该布局可以使用原生JS、JQuery、以及CSS3来实现,目前的工作场景不允许使用CSS3(主要是IE),所以本文主要针对原生JS。

一、需要掌握的知识点
  • window.onload,onload事件会在整个页面文档全部加载完成后执行

  • scrollTop、scrollHeight、clientHeight、offsetHeight、offsetTop ,这几个属性主要用来计算页面、元素、滚动区域的高度

    • offsetHeight,元素高度(不包含margin值)padding + border + 水平滚动条 + content;
      通过原生JS实现瀑布流布局_第1张图片
    • clientHeight,浏览器可视区域的高度,只包括元素本身的padding值,content + padding;
      通过原生JS实现瀑布流布局_第2张图片
    • scrollHeight,网页内容的实际高度,如果scrollHeight > clientHeight,返回scrollHeight;如果scrollHeight < clientHeight,则返回clientHeight
      通过原生JS实现瀑布流布局_第3张图片
    • scrollTop,页面有滚动条时,向上滚动的距离
      通过原生JS实现瀑布流布局_第4张图片
    • offsetTop,子元素的元素顶部距离父元素顶部的高度
      通过原生JS实现瀑布流布局_第5张图片
  • document.body 与 document.documentElement的兼容性

    • document.body.scrollTop 非IE;document.documentElement.scrollTop IE;
    • document.body始终指向文档中的body元素
    • document.documentElement始终指向文档中的html元素,与firstChild、childNodes[0]的指向是一样的
var html = document.documentElement;
console.log(html == document.firstChild);     // true
console.log(html == document.childNodes[0]);  // true
二、思路
  • 瀑布流布局的特点:图片元素等宽,高度自适应
  • 图片分为几列来展示
  • 所有图片的排列顺序:计算第一行所有图片(N)的高度,将N+1张图片插入高度最小的图片下面,依次类推,直到图片加载完毕
  • 随着页面的拖动,实现自动加载剩余图片:设置加载条件,符合加载条件后触发事件
三、具体解决方法
  • 页面打开固定展示8张图片
<body>
	<div id="main">
		<div class="box">
			<div class="pic">
				<img src="images/0.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/1.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/2.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/3.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/4.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/5.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/6.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/7.jpg">
			div>
		div>
		<div class="box">
			<div class="pic">
				<img src="images/8.jpg">
			div>
		div>		
	div>
	<div class="footer">页脚div>
body>
#main {
	width: 1000px;
	margin: 0 auto;
	position: relative;
}
.box {
	padding-top: 15px;
	padding-left: 15px;
	padding-right: 15px;
	float: left;
	overflow:hidden;
	background-color: #FFF;
}
.pic {
	padding: 10px;
	border: 1px solid #ccc;
	overflow:hidden;
}
.pic img {
	width: 198px;
	height: auto;
}
.footer {
	width: 100%;
	height: 60px;
	line-height: 60px;
	background-color: #177cca;
	color: #FFF;
	font-family: "Microsoft YaHei";
	font-size: 20px;
	text-align: center;
}
  • 从整体来看瀑布流布局,给人的第一感觉就是图片有序的排列在眼前,为了呈现更好的用户体验,一定会将第一次展示出来的图片 (写在HTML文档内的图片) 全部加载完毕后再执行脚本,文档加载过程中有很多不确定因素,比如网速,当我们所处环境网速偏慢时,会出现文档还未加载完成就执行脚本,可能导致所有展示图片只显示一半;为了避免这种情况,我们需要用到window.onload事件来告诉浏览器,你必须等文档全部加载完成后再执行JS
window.onload = function(){
	var oParent = document.getElementById("main");   
	waterfall(oParent, "box");
}
  • 计算一行显示几列图片:首先要获取main下所有子元素;然后通过遍历这些子元素从中摘取出class=box的图片元素,将这些图片元素全部保存至数组中;最后,在所有图片元素中,获取第一行所有元素,对比它们的高度,单独拿出高度最小的元素,将剩下的图片插入这个元素后,依次类推
// getClassBox函数用于获取所有图片元素
function getClassBox(parent, element){
	var elements = parent.getElementsByTagName("*");    // 获取所有子元素
	var arrBox = [];    // 创建数组,用来存储获取到的的图片元素
	for(var i=0; i<elements.length; i++){    // 通过for循环遍历main下的所有子元素
		if(elements[i].className == element){   
			arrBox.push(elements[i]);    // 如果遍历的子元素中有名为“box”的,将它们保存至arrBox数组中
		}
	}
	return arrBox;
}
function waterfall(parent, element){
	var oParent = document.getElementById("main");
	var oBoxs = getClassBox(oParent, "box");   // 调用getClassBox函数,将返回值(名为box的子元素)存入oBoxs中
	var oBoxW = oBoxs[0].offsetWidth;    // 图片元素是等宽的,所以只需要获取其中一个元素的宽度即可
	var pageW = oParent.offsetWidth;     // 父元素的宽度
	var cols = pageW / oBoxW;   // 父元素宽 / 子元素宽 = 列数
	var Ahrr = [];    // 创建数组Ahrr用于存储一行所有图片元素的高度  
	for(var i=0; i<oBoxs.length; i++){    // 遍历所有图片元素
		if(i<cols){    
			Ahrr.push(oBoxs[i].offsetHeight);    // 假如一行有4张图片,那么符合索引号i<4的元素,高度就被存入数组中,也就是一行中的所有图片的高度
		} else {    // 计算索引号i>4的元素该如何排列
			var minH = Math.min.apply(null, Ahrr);    // 找到一行元素中,高度最小的元素
			var index = getminHindex(Ahrr, minH);     // 调用getminHindex函数,获取高度最小的元素的索引号
			oBoxs[i].style.position = "absolute";     // 为元素添加绝对定位,将它放入适合的位置
			oBoxs[i].style.top = minH + "px";         // 高度为第一行图片中的最小高度
			oBoxs[i].style.left = oBoxs[index].offsetLeft + "px";    // 第一行图片中高度最小图片距左侧的距离
			Ahrr[index] = oBoxs[i].offsetHeight + minH;      // 重新计算第一行的两张图片高度,找到高度最小
		}
	}
}
// 索引号
function getminHindex(arr, val){
	for(var i in arr){
		if(arr[i] == val){
			return i;
		}
	}
}
  • 做到这一步,瀑布流布局也基本完成了一大半,图片按照设定好的顺序排列;但是页脚却出现在了页面顶部,这是由于为了让图片元素更精准的找到自己的位置而设定了绝对定位,父元素给了相对定位(绝对定位使元素脱离了文档流,再加上未给父元素高度,导致父元素高度塌陷),从调试里可以看到,main元素的height为0,页脚自然就居于顶部了
    通过原生JS实现瀑布流布局_第6张图片
  • 要解决父元素高度塌陷的问题,通过CSS是无法解决的,只有在JS里去获取高度 ,要考虑到后期还有图片需要加载,所以不能取clientHeight,如果取 clientHeight ,高度会被固定住,不会动态更新
oParent.style.height = getPageHeight() + "px";

// 获取高度
function getPageHeight(){
	var oParent = document.getElementById("main");
	var oBoxs = getClassBox(oParent, "box");
	//获取最后一张图片自身高度
	var lastBoxHeight = Math.floor(oBoxs[oBoxs.length - 1].offsetHeight);

	//获取最后一张图片距父元素顶部的高度
	var lastBoxTop = Math.floor(oBoxs[oBoxs.length - 1].offsetTop);
	return lastBoxHeight + lastBoxTop;
}

通过原生JS实现瀑布流布局_第7张图片
通过原生JS实现瀑布流布局_第8张图片

  • 设置剩余图片的加载条件,由于布局的特点,打开网页首先加载的是前8张图片,后续的图片何时何地加载都需要先预设好,比如:我想让滚动条滑动到最后一张图片的中间位置时就加载剩下的图片,这样就需要取两个值(最后一张图片自身的1/2位置,最后一张图片顶部距离父元素顶部的高度),这两个值相加就可以作为判断标准,现在我们的页面向下滚动,当滚动位置超过判断标准时,页面加载,以下图来说明
    通过原生JS实现瀑布流布局_第9张图片
// 当页面滚动到设定位置后开始加载剩余图片
function checkScrollSlide(){
	var oParent = document.getElementById("main");
	var oBoxs = getClassBox(oParent, "box");

	//获取最后一张图片自身高度的一半距离父元素顶部的高度
	var lastBoxH = Math.floor(oBoxs[oBoxs.length - 1].offsetHeight / 2) + Math.floor(oBoxs[oBoxs.length - 1].offsetTop);

	//获取滚动页面的高度
	var scrollHeight = document.body.scrollHeight||document.documentElement.scrollHeight;

	//获取页面视图区域的高度
	var clientHeight = document.body.clientHeight||document.documentElement.clientHeight;

	//判断
	if(lastBoxH < scrollHeight+clientHeight){
		return true;
	}else{
		return false;
	}
}
//数据
	var data = [
		{"src":"9.jpg"},
		{"src":"10.jpg"},
		{"src":"11.jpg"},
		{"src":"12.jpg"},
	];
//滚动条事件
	window.onscroll=function(){
		if(checkScrollSlide()){      // 当条件判断成立时,执行以下代码
			var oParent = document.getElementById("main"); 
			for(var i=0; i<data.length; i++){
				var dataBox = document.createElement("div");    // 创建一个div元素
				dataBox.className = "box";    // 给这个div赋予一个box的类名
				oParent.appendChild(dataBox);    // 将新创建的div插入父元素中

				var dataPic = document.createElement("div");
				dataBox.appendChild(dataPic);

				var dataImg = document.createElement("img");
				dataImg.src = "images/" + data[i].src;
				dataPic.appendChild(dataImg);
			}
			waterfall(oParent, "box");   // 再调用一次waterfall,让后续加载的图片应用其方法
		}
		oParent.style.height = getPageHeight() + "px";
	}

最后需要注意三个部分:

  • 需要在滚动事件执行体内再调用一次waterfall函数,让后面加载的图片再应用一次图片排序的方法
  • 由于页面不断的加载新图片,每一次加载最后一张图都在动态更新,所以需要重新计算页面高度
  • 本文中的JS适用于无限加载项目,如果需要对加载的数据有数量限制,那么就需要if(checkScrollSlide())后面再加一个限制条件,比如:我不想让整体的图片超过25张,checkScrollSlide()&&oBoxs.length<25

以上就是瀑布流布局用原生JS的实现方式,个人做项目时的一点小心得,虽然能实现出来,但是并不完善,欢迎大家指正;如果有需要得小伙伴可以直接下载源码!源码正在审核,通过后会将链接贴出来

你可能感兴趣的:(javascript,瀑布流布局,布局,瀑布流布局,JavaScript,HTML,CSS,布局)