瀑布流

瀑布流:多列的不规则排列,每一列由很多块构成,每一块宽度固定,高度不固定,按照某种规则排列,最终这些列之间高度不能相差太大。同时当页面滑动到底部时就去加载下一页,并且为图片设置懒加载功能。

  • 思路:首先获取我们需要的数组(假设20条,一共三列)。
  • 每次从数据中取三个,接着把数据绑定成 html,插入到每一列中。
  • 为了保证插入数据后这些列的高度相差不大,在插入之前,先按照这些列的现有高度进行升序排列,然后每次取三条数据,按照排好的数组顺序,依次从高度最矮的插,然后给第二矮的插,最后给最高的插。
  • HTML代码
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>珠峰-瀑布流</title>
  6. <link rel="stylesheet" href="css/reset.min.css">
  7. <link rel="stylesheet" href="css/index2.css">
  8. </head>
  9. <body>
  10. <ul class="flowBox clearfix" id="flowBox">
  11. <li data-height="0">
  12. <!--<a href="javascript:void 0;">
  13. <div><img src="img/13.jpg" alt=""></div>
  14. <span>泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美国宾州,美国歌手、演员。2006年出道,同年发行专辑《泰勒·斯威夫特》,该专辑获得美国唱片业协会的白金唱片认证</span>
  15. </a>-->
  16. </li>
  17. <li data-height="0"></li>
  18. <li data-height="0"></li>
  19. </ul>
  20. <script src="js/utils.js"></script>
  21. <script src="js/2-index.js"></script>
  22. </body>
  23. </html>
  • css
  1. html, body {
  2. background: #F4F4F4;
  3. overflow-x: hidden;
  4. }
  5. .flowBox {
  6. margin: 20px auto;
  7. width: 1000px;
  8. }
  9. .flowBox li {
  10. float: left;
  11. margin-right: 20px;
  12. width: 320px;
  13. }
  14. .flowBox li:nth-last-child(1) {
  15. margin-right: 0;
  16. }
  17. .flowBox li a {
  18. display: block;
  19. margin-top: 10px;
  20. padding: 10px;
  21. background: #fff;
  22. box-shadow: 3px 3px 10px 0 #666;
  23. /*box-shadow: 3px 3px 10px 0 #666*/
  24. }
  25. .flowBox li a div {
  26. background: url("../img/default.gif") no-repeat center center #eee;
  27. min-height: 50px;
  28. }
  29. .flowBox li a div img {
  30. display: block;
  31. width: 100%;
  32. }
  33. .flowBox li a span {
  34. display: block;
  35. margin: 10px;
  36. font-size: 12px;
  37. color: #555; /*span下的文字颜色555*/
  38. line-height: 20px;
  39. }
  • 分步骤JS代码
  1. // 瀑布流:多列的不规则排列,每一列由很多块构成,每一块宽度固定,高度不固定,按照某种规则排列,最终这些列之间高度不能相差太大。同时当页面滑动到底部时就去加载下一页,并且为图片设置懒加载功能。
  2. // 思路:首先获取我们需要的数组(假设20条,一共三列)。
  3. // 每次从数据中取三个,接着把数据绑定成html,插入到每一列中。
  4. // 为了保证插入数据后这些列的高度相差不大,在插入之前,先按照这些列的现有高度进行升序排列,然后先给最矮的插入数据????
  5. // 从页面中导入方法
  6. const { win, offset, toJSON } = utils;
  7. // 1. 获取元素
  8. let flowBox = document.getElementById('flowBox');
  9. let flowList = flowBox.getElementsByTagName('li');
  10. // 2. 从服务端获取数据
  11. let imgData = null;
  12. let page = 0; // 请求的是第几页
  13. let queryData = () => {
  14. // 真实项目中,瀑布流第一次打开时只加载第一页的数据,当用户滚动到底部时,我们才需要去请求第二页的数据。服务端的接口需要我们把请求的是第几页的数据这个参数告诉他,然后服务端会把对应页数的内容返回给前端。(分页技术)
  15. if (page > 10) {
  16. console.log('已经是最后一页了');
  17. return;
  18. }
  19. page++;
  20. let xhr = new XMLHttpRequest();
  21. xhr.open('GET', `json/data.json?page=${page}`, false);
  22. xhr.onreadystatechange = function () {
  23. if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
  24. imgData = toJSON(xhr.responseText);
  25. }
  26. };
  27. xhr.send();
  28. };
  29. queryData();
  30. // 3. 绑定数据
  31. let bindHTML = () => {
  32. for (let i = 0; i < imgData.length; i += 3) {
  33. // 每三个一组,如果有10条数据,第一次取三个,取得是索引为 0 1 2的者三个,下一次取得是 3 4 5,所以i += 3(你想几个一组就i+=几)
  34. // 假设数组一共10条,最大的索引值是9,每次取三个,在最后一次取的时候,索引为10 11的不存在。所以在使用之前还需要验证一次我们取出的是否存在
  35. let item1 = imgData[i];
  36. let item2 = imgData[i + 1];
  37. let item3 = imgData[i + 2];
  38. let flowListAry = Array.from(flowList);
  39. flowListAry.sort((a, b) => a.offsetHeight - b.offsetHeight);
  40. // 按照排好序的数组依次给每一列插入数据, flowListAry[0] 是最矮的
  41. if (item1) {
  42. flowListAry[0].innerHTML += `<a href="${item1.link}">
  43. <div>
  44. <img alt="" data-src="${item1.pic}">
  45. </div>
  46. <span>${item1.title}</span>
  47. </a>`
  48. }
  49. if (item2) {
  50. flowListAry[1].innerHTML += `<a href="${item2.link}">
  51. <div>
  52. <img alt="" data-src="${item2.pic}">
  53. </div>
  54. <span>${item2.title}</span>
  55. </a>`
  56. }
  57. if (item3) {
  58. flowListAry[2].innerHTML += `<a href="${item3.link}">
  59. <div>
  60. <img alt="" data-src="${item3.pic}">
  61. </div>
  62. <span>${item3.title}</span>
  63. </a>`
  64. }
  65. }
  66. };
  67. bindHTML();
  68. lazyLoad(); // 因为刚刚绑定完数据,一定有一部分数据展示在可视区中,但是此时如果不滚动就不会去加载这些图片,所以需要再绑定数据结束后手动执行lazyLoad方法去加载图片
  69. // 4. 加载更多
  70. // 4.1 什么时候去加载更多?页面滚动到底部时去加载更多。
  71. // 4.2 我们如何知道页面滚动到底了呢?监听页面的滚动条滚动事件,实时计算是否到底
  72. let timer = null;
  73. window.onscroll = function () {
  74. // 页面滚动就会触发 onscroll 事件,进而执行这个函数
  75. // 如何计算页面是否滚动到底了呢?
  76. // 页面真实的高度 - 滚动条卷去的高度 - 浏览器可视窗口的高度 === 0 表示页面已经滚动到底了
  77. lazyLoad();
  78. let pageH = win('scrollHeight');
  79. let winSctop = win('scrollTop');
  80. let winH = win('clientHeight');
  81. if (pageH - winSctop - winH <= 200) {
  82. // 为了提升用于体验,一般都是距离页面到底还有一段时就去加载,减少用户等待时间
  83. timer && clearTimeout(timer); // 为了减少对请求次数,采用函数节流和防抖
  84. timer = setTimeout(function () {
  85. queryData();
  86. bindHTML();
  87. lazyLoad();
  88. }, 300)
  89. }
  90. };
  91. // 5. 图片延时加载(多张)
  92. function lazyLoad() {
  93. // 1. 获取所有的 img 元素
  94. let imgList = document.querySelectorAll('img');
  95. // 2. 遍历 imgList,在遍历时计算每一张图片是否进入浏览器的可视窗口。
  96. for (let i = 0; i < imgList.length; i++) {
  97. let item = imgList[i];
  98. if (item.src) continue;
  99. let {top: imgOffsetTop} = offset(item);
  100. let winSctp = win('scrollTop');
  101. let winH = win('clientHeight');
  102. let dataSrc = item.getAttribute('data-src');
  103. if (imgOffsetTop - winSctp - winH <= 100) {
  104. let newImg = document.createElement('img');
  105. newImg.src = dataSrc;
  106. newImg.onload = function () {
  107. item.src = dataSrc;
  108. newImg = null;
  109. }
  110. }
  111. }
  112. }
  113. // 函数的节流和防抖:降低 js 代码函数的执行频率;
  • 单例模式封装
  1. let flowRender = (function () {
  2. // 从工具库中导入方法
  3. const {win, offset, toJSON, arrLikeToAry} = utils;
  4. let winH = win('clientHeight'); // 获取浏览器屏幕的高度
  5. // 1. 获取元素
  6. let flowList = document.querySelectorAll('#flowBox li');
  7. // 2. 请求数据的方法
  8. let page = 0;
  9. function queryData() {
  10. if (page > 10) {
  11. console.log('没有更多数据了');
  12. return;
  13. }
  14. page++;
  15. let xhr = new XMLHttpRequest();
  16. xhr.open('GET', `json/data.json?page=${page}`, false);
  17. xhr.onreadystatechange = function () {
  18. if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
  19. let data = JSON.parse(xhr.responseText);
  20. bindHTML(data);
  21. }
  22. };
  23. xhr.send();
  24. }
  25. // 3. 数据绑定
  26. function bindHTML(data) {
  27. for (let i = 0; i < data.length; i += 3) {
  28. // 每次从数据中取三个数据
  29. let dataArr = [
  30. data[i],
  31. data[i + 1],
  32. data[i + 2]
  33. ];
  34. // 2. 给页面中的三列排序
  35. let flowListAry = arrLikeToAry(flowList);
  36. flowListAry.sort((a, b) => a.offsetHeight - b.offsetHeight);
  37. flowListAry.forEach((liItem, liIndex) => {
  38. dataArr[liIndex]
  39. ? liItem.innerHTML += queryHTML(dataArr[liIndex])
  40. : null;
  41. })
  42. }
  43. }
  44. // 3. 拼接模板字符串
  45. function queryHTML({pic, link, title}) {
  46. return `<a href="${link}">
  47. <div>
  48. <img alt="" data-src="${pic}">
  49. </div>
  50. <span>${title}</span>
  51. </a>`
  52. }
  53. // 4. 图片懒加载
  54. function lazyLoad() {
  55. let imgList = document.querySelectorAll('img');
  56. [...imgList].forEach(item => {
  57. if (item.src) return;
  58. let dataSrc = item.getAttribute('data-src');
  59. let {top: imgOffsetTop } = offset(item);
  60. let winSctp = win('scrollTop');
  61. if (imgOffsetTop - winH - winSctp <= 100) {
  62. let newImg = new Image();
  63. newImg.src = dataSrc;
  64. newImg.onload = function () {
  65. item.src = dataSrc;
  66. newImg = null
  67. }
  68. }
  69. })
  70. }
  71. // 5. 加载更多
  72. function loadMore() {
  73. let pageH = win('scrollHeight');
  74. let winSctp = win('scrollTop');
  75. if (pageH - winSctp - winH <= 100) {
  76. queryData();
  77. lazyLoad();
  78. }
  79. }
  80. // 5. 处理滚动
  81. let timer = null;
  82. function handleScroll() {
  83. window.onscroll = function () {
  84. timer && clearTimeout(timer);
  85. timer = setTimeout(() => {
  86. lazyLoad();
  87. loadMore();
  88. }, 300);
  89. }
  90. }
  91. return {
  92. init() {
  93. queryData();
  94. lazyLoad();
  95. loadMore();
  96. handleScroll();
  97. }
  98. }
  99. })();
  100. flowRender.init();
  • 异步编程解决图片乱序问题(了解内容)
  1. /*
  2. * 本次优化了瀑布流排列会出现偶尔插入乱序的问题,!!不要求掌握!!!只是告诉大家可以有这样的解决方案。
  3. * Promise + async + await 异步编程解决方案!!!
  4. * */
  5. let flowRender = (function () {
  6. let timer = null;
  7. // 获取元素
  8. let flowList = document.querySelectorAll('#flowBox li');
  9. // 1. 请求数据的方法
  10. let page = 0;
  11. let queryData = () => {
  12. if (page > 3) {
  13. alert('没有更多数据了!');
  14. window.onscroll = null;
  15. return;
  16. }
  17. page++;
  18. let xhr = new XMLHttpRequest();
  19. xhr.open('GET', `json/data.json?page=${page}`, false);
  20. xhr.onreadystatechange = function () {
  21. if (xhr.readyState === 4 && /^2\d{2}$/.test(xhr.status)) {
  22. let data = JSON.parse(xhr.responseText);
  23. bindHTML(data);
  24. }
  25. };
  26. xhr.send(null);
  27. };
  28. let resolveImg = (url) => {
  29. return new Promise((resolve, reject) => {
  30. let newImg = document.createElement('img');
  31. newImg.src = url;
  32. newImg.onload = function () {
  33. let {width, height} = newImg;
  34. let cal = Math.round(300 / width * height); // 按比例计算出最后图片的高度 300 是列宽 320px 减去左右 padding 10px,
  35. resolve(cal);
  36. };
  37. newImg.onerror = reject;
  38. });
  39. };
  40. let fetchImg = (ary) => {
  41. let mappings = ary.map(async (item, index) => {
  42. if (item) {
  43. return await resolveImg(item.pic);
  44. } else {
  45. return 0;
  46. }
  47. });
  48. return Promise.all(mappings);
  49. };
  50. // 2. bindHTML
  51. async function bindHTML(data) {
  52. for (let i = 0; i < data.length; i += 3) {
  53. let dataArr = [
  54. data[i],
  55. data[i + 1],
  56. data[i + 2]
  57. ];
  58. let result = await fetchImg(dataArr);
  59. Array.from(flowList).sort(
  60. (a, b) => a.getAttribute('data-height') - b.getAttribute('data-height')
  61. ).forEach((li, liIndex) => {
  62. if (dataArr[liIndex]) {
  63. li.innerHTML += queryHTML(dataArr[liIndex]);
  64. li.setAttribute('data-height', +li.getAttribute('data-height') + result[liIndex])
  65. }
  66. });
  67. // 不使用await
  68. // fetchImg(dataArr).then((result) => {
  69. // // 给三列排序
  70. // Array.from(flowList).sort(
  71. // (a, b) => a.getAttribute('data-height') - b.getAttribute('data-height')
  72. // ).forEach((li, liIndex) => {
  73. // if (dataArr[liIndex]) {
  74. // li.innerHTML += queryHTML(dataArr[liIndex]);
  75. // li.setAttribute('data-height', +li.getAttribute('data-height') + result[liIndex])
  76. // }
  77. // });
  78. // isRun = false;
  79. // lazyLoad()
  80. // });
  81. }
  82. lazyLoad();
  83. }
  84. // 3. 拼接模板字符串
  85. function queryHTML({link, pic, title}) {
  86. return `<a href="${link}">
  87. <div>
  88. <img alt="" data-src="${pic}">
  89. </div>
  90. <span>${title}</span>
  91. </a>`
  92. }
  93. // 4. 加载更多
  94. function loadMore() {
  95. window.onscroll = function () {
  96. lazyLoad();
  97. let pageH = document.documentElement.scrollHeight;
  98. let winScrollTop = document.documentElement.scrollTop;
  99. let winH = document.documentElement.clientHeight;
  100. if (pageH - winScrollTop - winH <= 100) {
  101. timer && clearTimeout(timer);
  102. timer = setTimeout(() => {
  103. queryData();
  104. }, 300)
  105. }
  106. }
  107. }
  108. // 5. 图片懒加载
  109. function lazyLoad() {
  110. let imgList = document.getElementsByTagName('img');
  111. for (let i = 0; i < imgList.length; i++) {
  112. let item = imgList[i];
  113. let imgOffsetTop = item.offsetTop;
  114. let winScrollTop = document.documentElement.scrollTop;
  115. let winH = document.documentElement.clientHeight;
  116. let dataSrc = item.getAttribute('data-src');
  117. if (
  118. imgOffsetTop - winScrollTop - winH <= 100 &&
  119. !item.src
  120. ) {
  121. //!item.src => 优化点:如果图片没有 src 属性时,说明 img 还没进行过懒加载。如果图片已经有 src 属性说明已经被加载过了,就不需要再进行懒加载处理了。
  122. let newImg = document.createElement('img');
  123. newImg.src = dataSrc;
  124. newImg.onload = function () {
  125. item.src = dataSrc;
  126. newImg = null
  127. }
  128. }
  129. }
  130. }
  131. return {
  132. init() {
  133. queryData();
  134. loadMore();
  135. }
  136. }
  137. })();
  138. flowRender.init();

【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】