AI摘要
本文介绍了如何通过一行CSS代码实现前端网页的瀑布流图片布局,并通过示例代码展示了基本的瀑布流布局、手机端适配、懒加载和滚动加载等功能。文章还提供了升级代码,包括增加电脑端四列显示、手机端两列显示、懒加载和滚动加载功能,以及添加点击图片放大预览的功能。这些代码示例可以帮助开发者快速实现瀑布流布局,并根据需要进行功能扩展和样式调整。

大家在刷xx等短视频平台,会发现很多瀑布流布局。

如:小红书、抖音、快手等

这种布局可以通过砌体布局一行代码: columns: 300px auto; 就能实现瀑布流。

一栏两栏三栏四栏,不管有多少,它都可以: 自适应自填充

前端代码就是这样很简单,就是一个大框里面套图片,核心代码就这一行。

剩下就是一些 css 样式之类的可以根据自己喜欢的风格调整。

瀑布流其实是非常难做的,特别是图片不定宽高的,真的很多人用 flexgrid 做太难了。

实现代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>瀑布流布局示例</title>
  <style>
    .container {
      columns: 300px auto;
      column-gap: 15px;
      padding: 15px;
    }
    .item {
      break-inside: avoid;
      margin-bottom: 15px;
    }
    .item img {
      width: 100%;
      border-radius: 8px;
      display: block;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="item">
      <img src="https://picsum.photos/320/320" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/360/300" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/200" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/330/350" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/340/350" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/280" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/320" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/180/320" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/280/360" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/250/280" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/290/360" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/300/220" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/390/340" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/200/390" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/390/440" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/430/310" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/190/370" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/290/330" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/250/410" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/230/380" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/260/370" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/430" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/290/310" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/430/290" alt="随机图片">
    </div>
  </div>
</body>
</html>

升级代码1

上面的手机端是 1列 显示,将手机端改成 2列 瀑布流显示,其他不变:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>瀑布流布局示例</title>
  <style>
    .container {
      columns: 300px auto; /* 电脑端:最小列宽300px,自动计算列数 */
      column-gap: 15px;
      padding: 15px;
    }
    .item {
      break-inside: avoid; /* 防止元素跨列断裂 */
      margin-bottom: 15px;
    }
    .item img {
      width: 100%;
      border-radius: 8px;
      display: block;
    }

    /* 手机端适配:屏幕宽度≤768px时显示2列 */
    @media (max-width: 768px) {
      .container {
        columns: 2; /* 强制2列布局 */
      }
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="item">
      <img src="https://picsum.photos/320/320" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/360/300" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/200" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/330/350" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/340/350" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/280" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/320" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/180/320" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/280/360" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/250/280" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/290/360" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/300/220" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/390/340" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/200/390" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/390/440" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/430/310" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/190/370" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/290/330" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/250/410" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/230/380" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/260/370" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/210/430" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/290/310" alt="随机图片">
    </div>
    <div class="item">
      <img src="https://picsum.photos/430/290" alt="随机图片">
    </div>
  </div>
</body>
</html>

修改说明

  • 添加了 @media (max-width: 768px) 媒体查询,覆盖手机屏幕(宽度≤768px)的样式
  • 手机端将 columns: 300px auto 改为 columns: 2 ,强制固定 2列 布局
  • 其他样式(间距、圆角、图片自适应等)完全保留,确保视觉一致性

升级代码2

手机端增加 懒加载下滑加载剩余的 ,不一次性加载,只加载少量图片(8张),减轻首次加载压力:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>瀑布流布局(懒加载+滚动加载)</title>
  <style>
    .container {
      columns: 300px auto;
      column-gap: 15px;
      padding: 15px;
    }
    .item {
      break-inside: avoid;
      margin-bottom: 15px;
      opacity: 0;
      transform: translateY(20px);
      transition: opacity 0.3s ease, transform 0.3s ease;
    }
    /* 图片加载完成后显示动画 */
    .item.loaded {
      opacity: 1;
      transform: translateY(0);
    }
    .item img {
      width: 100%;
      border-radius: 8px;
      display: block;
      /* 避免图片加载时布局跳动 */
      object-fit: cover;
    }
    /* 加载提示样式 */
    .loading {
      text-align: center;
      padding: 20px;
      color: #666;
      font-size: 14px;
    }
    /* 手机端2列布局 */
    @media (max-width: 768px) {
      .container {
        columns: 2;
      }
    }
  </style>
</head>
<body>
  <div class="container" id="waterfallContainer"></div>
  <div class="loading" id="loadingTip">加载中...</div>

  <script>
    // 所有图片的尺寸数据(避免重复写URL,方便管理)
    const imageList = [
      { width: 320, height: 320 },
      { width: 360, height: 300 },
      { width: 210, height: 200 },
      { width: 330, height: 350 },
      { width: 340, height: 350 },
      { width: 210, height: 280 },
      { width: 210, height: 320 },
      { width: 180, height: 320 },
      { width: 280, height: 360 },
      { width: 250, height: 280 },
      { width: 290, height: 360 },
      { width: 300, height: 220 },
      { width: 390, height: 340 },
      { width: 200, height: 390 },
      { width: 390, height: 440 },
      { width: 430, height: 310 },
      { width: 190, height: 370 },
      { width: 290, height: 330 },
      { width: 250, height: 410 },
      { width: 230, height: 380 },
      { width: 260, height: 370 },
      { width: 210, height: 430 },
      { width: 290, height: 310 },
      { width: 430, height: 290 }
    ];

    const container = document.getElementById('waterfallContainer');
    const loadingTip = document.getElementById('loadingTip');
    let currentPage = 1; // 当前加载页码
    const pageSize = 8; // 每次加载8张
    let isLoading = false; // 防止重复加载的锁

    // 初始化加载第一页
    initLoad();

    // 监听滚动事件,实现滚动加载
    window.addEventListener('scroll', () => {
      // 当滚动到页面底部200px内,且不在加载中,且还有未加载的图片
      if (
        window.innerHeight + document.documentElement.scrollTop >= 
        document.documentElement.offsetHeight - 200 &&
        !isLoading &&
        (currentPage - 1) * pageSize < imageList.length
      ) {
        loadMore();
      }
    });

    // 初始化加载(第一页)
    function initLoad() {
      const firstPageImages = imageList.slice(0, pageSize);
      renderImages(firstPageImages);
      currentPage++;
      
      // 如果第一页已经加载完所有图片,隐藏加载提示
      if (pageSize >= imageList.length) {
        loadingTip.textContent = "已加载全部图片";
      }
    }

    // 加载更多图片
    function loadMore() {
      isLoading = true;
      loadingTip.textContent = "加载中...";

      // 模拟网络请求延迟(实际项目中替换为真实接口请求)
      setTimeout(() => {
        const startIndex = (currentPage - 1) * pageSize;
        const endIndex = Math.min(startIndex + pageSize, imageList.length);
        const newImages = imageList.slice(startIndex, endIndex);

        renderImages(newImages);
        currentPage++;

        // 全部加载完成
        if (endIndex >= imageList.length) {
          loadingTip.textContent = "已加载全部图片";
        }

        isLoading = false;
      }, 500); // 延迟500ms,模拟加载过程
    }

    // 渲染图片到页面
    function renderImages(images) {
      images.forEach(imgInfo => {
        const item = document.createElement('div');
        item.className = 'item';

        const img = document.createElement('img');
        // 使用picsum.photos的尺寸参数生成对应图片
        img.src = `https://picsum.photos/${imgInfo.width}/${imgInfo.height}?random=${Math.random()}`;
        img.alt = "随机图片";
        img.loading = "lazy"; // 原生懒加载,图片进入视口才加载

        // 图片加载完成后添加显示动画
        img.onload = () => {
          item.classList.add('loaded');
        };

        // 图片加载失败时显示备用图
        img.onerror = () => {
          img.src = `https://picsum.photos/300/300?random=${Math.random()}`;
          item.classList.add('loaded');
        };

        item.appendChild(img);
        container.appendChild(item);
      });
    }
  </script>
</body>
</html>

修改说明

滚动加载更多

  • 初始只加载 8 张图片,减轻首次渲染压力
  • 滚动到页面底部 200px 内时,自动加载下一批(8张)
  • 使用 isLoading 锁防止重复触发加载
  • 模拟网络延迟(500ms),实际项目可替换为接口请求

图片懒加载

  • 给图片添加 loading="lazy" 原生属性(现代浏览器均支持)
  • 图片只有进入视口时才会真正加载,减少初始 HTTP 请求数
  • 避免一次性加载 24 张图片导致的带宽占用和卡顿

体验优化

  • 图片加载完成后添加淡入动画(opacity + transform),过渡更自然
  • 加载过程显示 "加载中..." 提示,加载完成显示 "已加载全部图片"
  • 图片加载失败时自动替换为备用图,提升容错性
  • 图片添加 object-fit: cover ,避免布局跳动

兼容性

  • 原生懒加载兼容:Chrome 76+、Firefox 75+、Edge 79+、Safari 15.4+ (覆盖 95% 以上现代浏览器)
  • 如需兼容更旧浏览器,可添加 IntersectionObserver 降级方案(可选)

可自定义调整的参数

  • pageSize:每次加载的图片数量(默认 8 张,可根据需求改为 6/10 等)
  • 滚动触发距离: offsetHeight - 200 中的 200px(可调整为 100/300px
  • 加载延迟:setTimeout 中的 500ms (模拟网络请求,实际项目可删除)
  • 动画效果:可修改 .itemtransition 属性调整淡入速度

升级代码3

电脑改成 4排 显示 手机端其他功能不变:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>瀑布流布局(4列电脑端+2列手机端+懒加载)</title>
  <style>
    .container {
      columns: 4; /* 电脑端强制4列布局 */
      column-gap: 15px;
      padding: 15px;
      max-width: 1600px; /* 可选:限制最大宽度,避免大屏过度拉伸 */
      margin: 0 auto; /* 可选:居中显示 */
    }
    .item {
      break-inside: avoid;
      margin-bottom: 15px;
      opacity: 0;
      transform: translateY(20px);
      transition: opacity 0.3s ease, transform 0.3s ease;
    }
    /* 图片加载完成后显示动画 */
    .item.loaded {
      opacity: 1;
      transform: translateY(0);
    }
    .item img {
      width: 100%;
      border-radius: 8px;
      display: block;
      /* 避免图片加载时布局跳动 */
      object-fit: cover;
    }
    /* 加载提示样式 */
    .loading {
      text-align: center;
      padding: 20px;
      color: #666;
      font-size: 14px;
    }
    /* 手机端2列布局(保持不变) */
    @media (max-width: 768px) {
      .container {
        columns: 2;
      }
    }

    /* 可选:平板横屏适配(介于手机和电脑之间,可设为3列) */
    @media (min-width: 769px) and (max-width: 1200px) {
      .container {
        columns: 3;
      }
    }
  </style>
</head>
<body>
  <div class="container" id="waterfallContainer"></div>
  <div class="loading" id="loadingTip">加载中...</div>

  <script>
    // 所有图片的尺寸数据(保持不变)
    const imageList = [
      { width: 320, height: 320 },
      { width: 360, height: 300 },
      { width: 210, height: 200 },
      { width: 330, height: 350 },
      { width: 340, height: 350 },
      { width: 210, height: 280 },
      { width: 210, height: 320 },
      { width: 180, height: 320 },
      { width: 280, height: 360 },
      { width: 250, height: 280 },
      { width: 290, height: 360 },
      { width: 300, height: 220 },
      { width: 390, height: 340 },
      { width: 200, height: 390 },
      { width: 390, height: 440 },
      { width: 430, height: 310 },
      { width: 190, height: 370 },
      { width: 290, height: 330 },
      { width: 250, height: 410 },
      { width: 230, height: 380 },
      { width: 260, height: 370 },
      { width: 210, height: 430 },
      { width: 290, height: 310 },
      { width: 430, height: 290 }
    ];

    const container = document.getElementById('waterfallContainer');
    const loadingTip = document.getElementById('loadingTip');
    let currentPage = 1; // 当前加载页码
    const pageSize = 8; // 每次加载8张
    let isLoading = false; // 防止重复加载的锁

    // 初始化加载第一页(保持不变)
    initLoad();

    // 监听滚动事件,实现滚动加载(保持不变)
    window.addEventListener('scroll', () => {
      if (
        window.innerHeight + document.documentElement.scrollTop >= 
        document.documentElement.offsetHeight - 200 &&
        !isLoading &&
        (currentPage - 1) * pageSize < imageList.length
      ) {
        loadMore();
      }
    });

    // 初始化加载(第一页)(保持不变)
    function initLoad() {
      const firstPageImages = imageList.slice(0, pageSize);
      renderImages(firstPageImages);
      currentPage++;
      
      if (pageSize >= imageList.length) {
        loadingTip.textContent = "已加载全部图片";
      }
    }

    // 加载更多图片(保持不变)
    function loadMore() {
      isLoading = true;
      loadingTip.textContent = "加载中...";

      // 模拟网络请求延迟
      setTimeout(() => {
        const startIndex = (currentPage - 1) * pageSize;
        const endIndex = Math.min(startIndex + pageSize, imageList.length);
        const newImages = imageList.slice(startIndex, endIndex);

        renderImages(newImages);
        currentPage++;

        if (endIndex >= imageList.length) {
          loadingTip.textContent = "已加载全部图片";
        }

        isLoading = false;
      }, 500);
    }

    // 渲染图片到页面(保持不变)
    function renderImages(images) {
      images.forEach(imgInfo => {
        const item = document.createElement('div');
        item.className = 'item';

        const img = document.createElement('img');
        img.src = `https://picsum.photos/${imgInfo.width}/${imgInfo.height}?random=${Math.random()}`;
        img.alt = "随机图片";
        img.loading = "lazy"; // 原生懒加载

        // 图片加载完成后添加显示动画
        img.onload = () => {
          item.classList.add('loaded');
        };

        // 图片加载失败时显示备用图
        img.onerror = () => {
          img.src = `https://picsum.photos/300/300?random=${Math.random()}`;
          item.classList.add('loaded');
        };

        item.appendChild(img);
        container.appendChild(item);
      });
    }
  </script>
</body>
</html>

修改说明

  • 电脑端强制 4 列:将 .container 原有的 columns: 300px auto 改为 columns: 4 ,直接固定电脑端为 4 列布局
  • 优化大屏显示:新增 max-width: 1600pxmargin: 0 auto ,避免在超宽屏幕上列宽过大,保持布局紧凑美观(可选,可根据需求删除)
  • 新增平板适配(可选):添加 769px ~ 1200px 媒体查询,设为 3 列,让平板横屏显示更协调(不想要可直接删除该媒体查询)
  • 所有核心功能不变:手机端仍为 2 列、懒加载、滚动加载、加载动画、失败容错等功能完全保留

如果觉得电脑端 4 列时列宽太窄,可调整 column-gap (当前 15px)或 max-width (当前 1600px),列宽会自动适配容器宽度并均分 4

升级代码4

增加一个点击图片 放大预览 功能,其功能不变

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>瀑布流布局(4列电脑端+2列手机端+懒加载)</title>
  <style>
    .container {
      columns: 4; /* 电脑端强制4列布局 */
      column-gap: 15px;
      padding: 15px;
      max-width: 1600px; /* 可选:限制最大宽度,避免大屏过度拉伸 */
      margin: 0 auto; /* 可选:居中显示 */
    }
    .item {
      break-inside: avoid;
      margin-bottom: 15px;
      opacity: 0;
      transform: translateY(20px);
      transition: opacity 0.3s ease, transform 0.3s ease;
      cursor: pointer; /* 添加指针样式表示可点击 */
    }
    /* 图片加载完成后显示动画 */
    .item.loaded {
      opacity: 1;
      transform: translateY(0);
    }
    .item img {
      width: 100%;
      border-radius: 8px;
      display: block;
      /* 避免图片加载时布局跳动 */
      object-fit: cover;
    }
    /* 加载提示样式 */
    .loading {
      text-align: center;
      padding: 20px;
      color: #666;
      font-size: 14px;
    }
    /* 手机端2列布局(保持不变) */
    @media (max-width: 768px) {
      .container {
        columns: 2;
      }
    }

    /* 可选:平板横屏适配(介于手机和电脑之间,可设为3列) */
    @media (min-width: 769px) and (max-width: 1200px) {
      .container {
        columns: 3;
      }
    }

    /* 图片预览模态框样式 */
    .modal {
      display: none;
      position: fixed;
      z-index: 1000;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.9);
      overflow: auto;
    }
    .modal-content {
      display: block;
      margin: auto;
      max-width: 90%;
      max-height: 90%;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    .close {
      position: absolute;
      top: 20px;
      right: 30px;
      color: white;
      font-size: 40px;
      font-weight: bold;
      cursor: pointer;
    }
    .close:hover {
      color: #ccc;
    }
  </style>
</head>
<body>
  <div class="container" id="waterfallContainer"></div>
  <div class="loading" id="loadingTip">加载中...</div>

  <!-- 图片预览模态框 -->
  <div id="imageModal" class="modal">
    <span class="close">&times;</span>
    <img class="modal-content" id="modalImage">
  </div>

  <script>
    // 所有图片的尺寸数据(保持不变)
    const imageList = [
      { width: 320, height: 320 },
      { width: 360, height: 300 },
      { width: 210, height: 200 },
      { width: 330, height: 350 },
      { width: 340, height: 350 },
      { width: 210, height: 280 },
      { width: 210, height: 320 },
      { width: 180, height: 320 },
      { width: 280, height: 360 },
      { width: 250, height: 280 },
      { width: 290, height: 360 },
      { width: 300, height: 220 },
      { width: 390, height: 340 },
      { width: 200, height: 390 },
      { width: 390, height: 440 },
      { width: 430, height: 310 },
      { width: 190, height: 370 },
      { width: 290, height: 330 },
      { width: 250, height: 410 },
      { width: 230, height: 380 },
      { width: 260, height: 370 },
      { width: 210, height: 430 },
      { width: 290, height: 310 },
      { width: 430, height: 290 }
    ];

    const container = document.getElementById('waterfallContainer');
    const loadingTip = document.getElementById('loadingTip');
    const modal = document.getElementById('imageModal');
    const modalImg = document.getElementById('modalImage');
    const closeBtn = document.querySelector('.close');
    let currentPage = 1; // 当前加载页码
    const pageSize = 8; // 每次加载8张
    let isLoading = false; // 防止重复加载的锁

    // 初始化加载第一页(保持不变)
    initLoad();

    // 监听滚动事件,实现滚动加载(保持不变)
    window.addEventListener('scroll', () => {
      if (
        window.innerHeight + document.documentElement.scrollTop >= 
        document.documentElement.offsetHeight - 200 &&
        !isLoading &&
        (currentPage - 1) * pageSize < imageList.length
      ) {
        loadMore();
      }
    });

    // 初始化加载(第一页)(保持不变)
    function initLoad() {
      const firstPageImages = imageList.slice(0, pageSize);
      renderImages(firstPageImages);
      currentPage++;
      
      if (pageSize >= imageList.length) {
        loadingTip.textContent = "已加载全部图片";
      }
    }

    // 加载更多图片(保持不变)
    function loadMore() {
      isLoading = true;
      loadingTip.textContent = "加载中...";

      // 模拟网络请求延迟
      setTimeout(() => {
        const startIndex = (currentPage - 1) * pageSize;
        const endIndex = Math.min(startIndex + pageSize, imageList.length);
        const newImages = imageList.slice(startIndex, endIndex);

        renderImages(newImages);
        currentPage++;

        if (endIndex >= imageList.length) {
          loadingTip.textContent = "已加载全部图片";
        }

        isLoading = false;
      }, 500);
    }

    // 渲染图片到页面(保持不变)
    function renderImages(images) {
      images.forEach(imgInfo => {
        const item = document.createElement('div');
        item.className = 'item';

        const img = document.createElement('img');
        img.src = `https://picsum.photos/${imgInfo.width}/${imgInfo.height}?random=${Math.random()}`;
        img.alt = "随机图片";
        img.loading = "lazy"; // 原生懒加载
        img.dataset.fullsize = `https://picsum.photos/${imgInfo.width * 2}/${imgInfo.height * 2}?random=${Math.random()}`; // 存储大图URL

        // 图片加载完成后添加显示动画
        img.onload = () => {
          item.classList.add('loaded');
        };

        // 图片加载失败时显示备用图
        img.onerror = () => {
          img.src = `https://picsum.photos/300/300?random=${Math.random()}`;
          img.dataset.fullsize = `https://picsum.photos/600/600?random=${Math.random()}`;
          item.classList.add('loaded');
        };

        // 点击图片事件
        img.onclick = function() {
          modal.style.display = "block";
          modalImg.src = this.dataset.fullsize;
        }

        item.appendChild(img);
        container.appendChild(item);
      });
    }

    // 关闭模态框
    closeBtn.onclick = function() {
      modal.style.display = "none";
    }

    // 点击模态框背景关闭
    modal.onclick = function(event) {
      if (event.target === modal) {
        modal.style.display = "none";
      }
    }

    // 按ESC键关闭模态框
    document.onkeydown = function(event) {
      if (event.key === "Escape" && modal.style.display === "block") {
        modal.style.display = "none";
      }
    }
  </script>
</body>
</html>

修改说明

  • 添加了模态框HTML结构:
html
<div id="imageModal" class="modal">
  <span class="close">&times;</span>
  <img class="modal-content" id="modalImage">
</div>
  • 添加了模态框相关CSS样式:模态框背景、大图显示样式、关闭按钮样式
  • 增强了图片元素:为每个图片添加了 data-fullsize 属性,存储大图URL (原始尺寸的2倍)为图片添加了点击事件处理程序
  • 添加了模态框交互逻辑:点击图片显示大图、点击关闭按钮、模态框背景或按 ESC键 关闭大图预览
  • 错误处理增强:图片加载失败时不仅设置备用小图,还设置了对应的大图备用 URL

基于升级的增强代码4,还可以增加上一张、下一张等功能,具体代码自行实现吧。