重温新手村项目 -- 图片瀑布流(含演示网址)

2,797 阅读6分钟

Hello, 各位勇敢的小伙伴, 大家好, 我是你们的嘴强王者小五, 身体健康, 脑子没病.

本人有丰富的脱发技巧, 能让你一跃成为资深大咖.

一看就会一写就废是本人的主旨, 菜到抠脚是本人的特点, 卑微中透着一丝丝刚强, 傻人有傻福是对我最大的安慰.

欢迎来到小五随笔系列重温新手村项目 -- 图片瀑布流.

前言

本文目标意在实现横向和纵向图片瀑布流;横向采用 Flex布局,纵向则提供Absolute绝对定位 Grid布局 两种解决方案;数据上采用真实的接口请求,图片为爬虫爬取后存库;优化上做了懒加载和防抖,减少资源消耗的同时增强用户体验。

👉 双手奉上代码链接【 技术栈 -- React 】

演示网址:

纵向瀑布流 - 演示网址

横向瀑布流 - 演示网址

效果图:

tips:图片为爬虫爬取【 来源为 wallhaven.cc 】,过滤大小为200K,实际工作中可存取不同规格的图片,瀑布流时展示小图,预览在加载高清大图。

waterfall.gif

基础拾遗

此部分内容为本文所需知识点,如需扩充,请各位看官自行查阅相关资料

懒加载

瀑布流是一个大量加载图片的需求;一次性加载的话,浏览器无法一口气处理掉全部任务,会造成卡顿、白屏等现象,且浪费资源。

懒加载是对图片加载时机的优化,即图片进入视野后,在进行加载;

🤔 思路如下:

/* --- 可视区域高度 --- **/
winHeight = window.innerHeight

/* --- 页面滑动距离 --- **/
scrollTop = document.body.scrollTop || document.documentElement.scrollTop

/* --- 元素顶部到浏览器顶部的距离 --- **/
offsetTop = imageNode.offsetTop

/* --- 加载时机 --- **/
offsetTop < scrollTop + winHeight

/* --- 加载图片 --- **/
imageNode.src = imageNode.getAttribute('data-src')

节流与防抖

笔者在该项目中仅用了基础的防抖,此处,节流与防抖的优化做知识扩充;

👉 节流

核心思想:第一个说的算

所谓节流,是指一段时间内,无视后来产生的回调请求;

function throttle(fn, interval) {
  let last = 0;                    // 上次回调时间戳
  return function () {
    let now = +new Date();         // 本次回调时间戳
    if (now - last >= interval) {  // 本次 - 上次 >= 时间间隔,则触发回调
      last = now;                  // 重新赋值
      fn.apply(this, arguments);   // 执行函数
    }
  }
}

👉 防抖

核心思想:最后一个说的算

所谓防抖,是指一段时间内,若产生新的回调,则重新计时;

function debounce(fn, delay) {
  let timer = null;                  // 初始化
  return function () {
    if (timer) clearTimeout(timer);  // 在 delay 时间内重复调用,清除定时器
    timer = setTimeout(function () { // 设置定时器
      fn.apply(this, arguments);     // 触发函数
    }, delay);
  }
}

👉 节流优化防抖

避免频繁操作却迟迟没有响应,打造一个有底线的防抖

function debounce(fn, delay) {
  let last = 0;
  let timer = null;

  return function () {
    let now = +Date.now();
    if (now - last >= delay) {
      last = now;
      fn.apply(this, arguments);
    } else {
      if (timer) clearTimeout(timer);
      timer = setTimeout(function () {
        last = now;
        fn.apply(this, arguments);
      }, delay);
    }
  }
}

Grid 网格布局

tips:这里仅针对项目中用到的知识点加以说明

pic4.png

以上述选中元素为例,上代码:

.grid-wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  grid-auto-rows: 10px;
  grid-auto-flow: row dense;
  gap: 0 10px;
}
.grid-item {
  width: 100%;
  height: auto;
  grid-row-start: auto;
  grid-row-end: span 22;
}
.grid-item img {
  object-fit: cover;
  width: 100%;
  height: auto;
  background-color: #eee;
  box-sizing: border-box;
  border: 1px solid #ccc;
}
  1. grid-template-columnsgrid-template-rows

用于设置行高和列宽,如 grid-template-columns: 200px 100px 200px; 表示分为3列,分别为 200px 100px 和 200px;

-> repeat(重复次数, 子元素宽度)

-> auto-fit:宽度固定,数量自适应

-> minmax(150px, 1fr) 最小宽度150px,最大宽度1fr(类比 flex: 1),在该区间内自适应

  1. grid-auto-rowsgrid-row-startgrid-row-end
/* 父元素 **/
grid-auto-rows: 10px;
/* 子元素 **/
grid-row-start: auto;
grid-row-end: span 22; // 这里单元格数量需设置为整数

代表每个单元格为10px,子元素起始位置自适应,高度占据22个单元格高度,即220px

tips:设置10px是为了让截图的网格更清晰,实际操作中,笔者设置的最小单元格为1px

  1. grid-auto-flow

该属性为排列方式,设置为row即横向排列;第二个参数dense表示尽可能填充,减少空白(相当于absolute布局的插入方式,详见下文);

  1. gap

行间距和列间距,gap: 0 10px; 表示列间距为 10px

功能实现

接口:

👉 点我查看接口代码

tips:图片来源为 wallhaven.cc,获取方式为爬虫爬取

数据格式:

data = [
  {
    id: 1,
    image_url: "xxx",
    size: "xxx",
    title: "xxx",
    width: "xxx",
    height: "xxx"
  },
  ...
]

横向瀑布流

👉 点我查看演示效果

👉 点我查看完整代码

pic3.png

特点:定高不定宽

思路:Flex布局,利用 flex-wrap 换行,利用 flex-grow 分配剩余空间,达到自适应的目地。

纵向瀑布流

👉 点我查看演示效果

pic2.png

特点:定宽不定高

提供两种思路,分别为 Absolute绝对定位Grid网格布局

Absolute 方案

👉 点我查看完整代码 -- Absolute

目标:

  1. 为每个元素追加 top 和 left;

  2. 由于 absolute 脱离文档流,故需为父元素追加高度;

✍️ 子元素宽度计算

第一步,先来确定每个子元素的宽度和列数

这里为了效果更好,我们来模拟上文提到的minmax

两个已知的设定值:【gap 间距】【baseWidth 基础宽度】

由此可列出以下信息:

  • n * (baseWidth + x) + (n + 1) * gap = width,其中n为列数,x为分配长度,width为屏幕宽度

  • n 为正整数

  • x > 0 & x < baseWidth

  • 在符合上述条件的基础上,让n最大化

我们将n代入求x,找最优解,代码如下:

code3.png

✍️ 布局

已知子元素宽高及列数后,我们为其追加top、left属性

先来为第一排元素追加属性,如下:

itemNode.style.top = `${gap}px`;
itemNode.style.left = `${gap + i * (itemWidth + gap)}px`;

其中gap为间距;itemWidth为刚刚计算出的子元素宽度;i为循环的index,即第几个元素;

pic5.png

❓ 第二排元素的top该如何确定呢

👉 根据每列的高度计算,我们定义一个长度为列数的heights数组,用于存放每列的高度

❓ 顺序排列会如何

👉 图片长短不一,会导致一些列很长,一些列很短,如下图

pic6.png

故每次都应将元素推到最小高度的一列,已达到视觉效果最佳

pic7.png

实现如下:

code4.png

Math.max(...heights) 即为对父元素追加的高度

Grid 方案

👉 点我查看完整代码 -- Grid

思路在基础拾遗部分已逐一讲解,其中 grid-row-end 需通过计算获得;dense 可做到优先最短高度填充;

其余代码片段

懒加载

code5.png

计算子元素高度

code6.png

图片错误处理

code7.png

防抖

code8.png

结语

  • 图片过大时,应存储不同尺寸图片,瀑布流时展示小图,预览时展示大图

  • absolute 方案未作 resize 处理,大家可自行添加(笔者将其用在移动端,将 Grid 方案用在 PC 端)

  • 图片的宽高比是必要的,否则无法在图片加载前呈现良好的效果

excel7.gif