实现多行人像网格的平滑走马灯(Marquee)效果

kkcode
kkcode
2026-06-24阅读 28

一、核心实现原理

这个多行人像网格的平滑走马灯(Marquee)效果,核心是「内容复制+位移动画」实现无缝循环,配合CSS遮罩做边缘融合,具体拆解为4个核心逻辑:

1. 无缝循环:双份内容 + 位移衔接

这是解决「滚动到末尾跳回开头出现卡顿空白」的核心方案:

  • 在每一行内放入两组完全相同的图片卡片,首尾拼接,整行总宽度 = 单组内容宽度 × 2
  • 动画仅控制整行从 translateX(0) 向左移动到 translateX(-50%)
  • 当位移达到 -50% 时,第二组内容刚好完全替代第一组的初始位置,视觉上和起点完全重合
  • 此时动画无限循环重置,人眼完全感知不到跳变,最终实现「持续滚动、无空白、无顿挫」的无缝效果

2. 边缘淡出:CSS 遮罩层自然融合

左侧图片逐渐透明消失的效果,依靠 CSS mask-image(遮罩图像)实现:

  • 给外层容器添加从左到右的线性渐变:transparent → 纯黑色
  • CSS遮罩的规则是:黑色区域完整显示内容,透明区域隐藏内容
  • 最终效果为左侧边缘的图片从透明逐步过渡到完全显示,和左侧文字区域自然融合,不会出现生硬的切割边界

3. 流畅性能:Transform GPU 硬件加速

动画选用 transform: translateX() 而非 leftmargin-left 等属性:

  • transform 会触发浏览器 GPU 硬件加速,动画过程不会触发页面重排(Reflow)
  • 搭配 linear 匀速动画曲线,保证滚动速度全程均匀,没有忽快忽慢的变速感,完全匹配走马灯的视觉预期

4. 错落视觉:奇偶行反向滚动

通过 nth-child(even) 选择器给偶数行设置 animation-direction: reverse,让偶数行向右滚动、奇数行向左滚动:

  • 无需编写第二套动画,仅靠反向播放就能实现双向错落效果
  • 多行同时滚动时视觉层次更丰富,避免单方向滚动的单调感

二、分步实现步骤

按照「结构搭建 → 基础样式 → 核心动画 → 细节优化」的顺序,5步即可完整实现。

步骤1:搭建 HTML 骨架

核心规则:外层容器包裹多行轨道,每行内部放置两组完全相同的卡片

<!-- 最外层容器:负责裁剪溢出内容、添加边缘遮罩 -->
<div class="avatar-marquee">
  <!-- 第一行滚动轨道 -->
  <div class="marquee-row">
    <!-- 第一组:原始图片内容 -->
    <div class="avatar-card"><img src="图1.jpg" alt=""></div>
    <div class="avatar-card"><img src="图2.jpg" alt=""></div>
    <div class="avatar-card"><img src="图3.jpg" alt=""></div>
    <!-- 第二组:和上方完全一致,用于无缝衔接 -->
    <div class="avatar-card"><img src="图1.jpg" alt=""></div>
    <div class="avatar-card"><img src="图2.jpg" alt=""></div>
    <div class="avatar-card"><img src="图3.jpg" alt=""></div>
  </div>

  <!-- 第二行、第三行... 结构完全一致 -->
  <div class="marquee-row">
    <!-- 同样放置两组相同的图片 -->
  </div>
</div>

关键要求:第二组必须和第一组的图片、顺序、样式完全一致,否则衔接处会出现明显跳变。

步骤2:设置外层容器基础样式

容器的核心作用是「隐藏溢出内容 + 实现左侧渐变淡出」。

.avatar-marquee {
  width: 100%; /* 占满父容器宽度 */
  overflow: hidden; /* 核心:超出容器的内容全部隐藏,形成滚动视口 */

  /* 左侧渐变淡出遮罩,兼容webkit内核浏览器 */
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 30%);
  mask-image: linear-gradient(to right, transparent 0%, #000 30%);
}
  • transparent 0%, #000 30% 表示:最左侧完全透明,0~30% 区域逐渐过渡为完全显示,30% 往右的区域完整展示图片。可调整百分比控制渐变过渡的宽度。

步骤3:完成行与卡片的布局

让行内卡片横向排列不换行,卡片尺寸固定不被压缩。

/* 每一行滚动轨道 */
.marquee-row {
  display: flex; /* 卡片横向排列 */
  gap: 16px; /* 卡片之间的间距 */
  margin-bottom: 16px; /* 行与行的垂直间距 */
  width: max-content; /* 核心:宽度由内容撑开,强制不换行、不折行 */
}

/* 人像卡片 */
.avatar-card {
  flex-shrink: 0; /* 核心:禁止卡片被压缩,保证所有卡片尺寸一致 */
  width: 320px; /* 卡片宽度,可按需调整 */
  height: 220px; /* 卡片高度,可按需调整 */
  border-radius: 16px;
  overflow: hidden;
  border: 2px solid rgba(180, 160, 255, 0.3); /* 示例中的浅紫色描边效果 */
}
.avatar-card img {
  width: 100%;
  height: 100%;
  object-fit: cover; /* 图片填满卡片,不变形裁切 */
  display: block;
}

步骤4:添加核心滚动动画

定义位移动画,并绑定到每一行,实现无限平滑滚动。

/* 定义滚动动画:从0位移到-50% */
@keyframes marqueeMove {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}

/* 给行元素绑定动画 */
.marquee-row {
  /* 动画名称 时长 匀速曲线 无限循环 */
  animation: marqueeMove 30s linear infinite;
}
  • 时长 30s 控制滚动速度:数值越大,滚动越慢;数值越小,滚动越快。
  • 必须使用 linear 匀速曲线,不能用默认的 ease,否则开头结尾会减速,出现顿挫感。

步骤5:细节优化与交互增强

补充两个体验优化点,和示例效果完全对齐。

  1. 偶数行反向滚动
.marquee-row:nth-child(even) {
  animation-direction: reverse; /* 反向播放动画,实现向右滚动 */
}
  1. 鼠标悬停暂停滚动
.avatar-marquee:hover .marquee-row {
  animation-play-state: paused; /* 暂停动画 */
}

鼠标移入图片区域时滚动停止,方便用户查看具体的数字人形象,移出后继续滚动,是这类展示组件的标准交互。


三、实现注意事项

  1. 内容宽度要求:单组图片的总宽度必须大于容器宽度,否则即使复制两份也会出现空白,无法实现无缝效果。
  2. 性能优化:图片数量较多时,建议给图片添加懒加载,降低首屏加载压力。
  3. 浏览器兼容mask-image 在 Safari 等 WebKit 内核浏览器中需要加 -webkit- 前缀,上述代码已包含兼容写法。
  4. 速度一致性:如果每行的卡片数量不同,需要单独设置不同的动画时长,才能保证视觉上滚动速度一致。

四、完整代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <style>
    /* 滚动容器 */
    .avatar-marquee {
      width: 100%;
      overflow: hidden;
      /* 左侧渐变遮罩,实现淡入淡出 */
      -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 30%);
      mask-image: linear-gradient(to right, transparent 0%, #000 30%);
    }

    /* 每一行滚动轨道 */
    .marquee-row {
      display: flex;
      gap: 16px;
      margin-bottom: 16px;
      width: max-content;
      animation: marqueeMove 30s linear infinite;
    }

    /* 偶数行反向滚动,错落感更强 */
    .marquee-row:nth-child(even) {
      animation-direction: reverse;
    }

    /* 图片卡片 */
    .avatar-card {
      flex-shrink: 0;
      width: 320px;
      height: 220px;
      border-radius: 16px;
      overflow: hidden;
      border: 2px solid rgba(180, 160, 255, 0.3);
    }
    .avatar-card img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      display: block;
    }

    /* 滚动动画:移动50%实现无缝循环 */
    @keyframes marqueeMove {
      0% {
        transform: translateX(0);
      }
      100% {
        transform: translateX(-50%);
      }
    }

    /* 鼠标悬停暂停 */
    .avatar-marquee:hover .marquee-row {
      animation-play-state: paused;
    }
  </style>
</head>
<body>
  <div class="avatar-marquee">
    <!-- 第一行 -->
    <div class="marquee-row">
      <!-- 第一组图片 -->
      <div class="avatar-card"><img src="图片1地址" alt=""></div>
      <div class="avatar-card"><img src="图片2地址" alt=""></div>
      <div class="avatar-card"><img src="图片3地址" alt=""></div>
      <div class="avatar-card"><img src="图片4地址" alt=""></div>
      <!-- 第二组完全相同的图片,保证无缝衔接 -->
      <div class="avatar-card"><img src="图片1地址" alt=""></div>
      <div class="avatar-card"><img src="图片2地址" alt=""></div>
      <div class="avatar-card"><img src="图片3地址" alt=""></div>
      <div class="avatar-card"><img src="图片4地址" alt=""></div>
    </div>

    <!-- 第二行 -->
    <div class="marquee-row">
      <div class="avatar-card"><img src="图片5地址" alt=""></div>
      <div class="avatar-card"><img src="图片6地址" alt=""></div>
      <div class="avatar-card"><img src="图片7地址" alt=""></div>
      <div class="avatar-card"><img src="图片8地址" alt=""></div>
      <div class="avatar-card"><img src="图片5地址" alt=""></div>
      <div class="avatar-card"><img src="图片6地址" alt=""></div>
      <div class="avatar-card"><img src="图片7地址" alt=""></div>
      <div class="avatar-card"><img src="图片8地址" alt=""></div>
    </div>

    <!-- 第三行 -->
    <div class="marquee-row">
      <div class="avatar-card"><img src="图片9地址" alt=""></div>
      <div class="avatar-card"><img src="图片10地址" alt=""></div>
      <div class="avatar-card"><img src="图片11地址" alt=""></div>
      <div class="avatar-card"><img src="图片12地址" alt=""></div>
      <div class="avatar-card"><img src="图片9地址" alt=""></div>
      <div class="avatar-card"><img src="图片10地址" alt=""></div>
      <div class="avatar-card"><img src="图片11地址" alt=""></div>
      <div class="avatar-card"><img src="图片12地址" alt=""></div>
    </div>
  </div>
</body>
</html>
评论数量:0