目录

图像滑块对比功能的开发记录

图像滑块对比功能的开发记录

背景介绍

最近,公司需要开发一款在线图像压缩工具,其中的一个关键功能是让用户直观地比较压缩前后的图像效果。因此,我们设计了一个对比组件,它允许用户通过拖动滑块,动态调整两张图像的显示区域,从而清晰地看到压缩前后的差异。

目标效果

  • 两张图片堆叠放置,一张始终可见,另一张可调整可见范围。
  • 通过滑块拖动,控制上层图片的显示区域。
  • 适配 PC 和移动端,提供流畅的交互体验。

效果如图:

https://i-blog.csdnimg.cn/img_convert/32aede5c760357f52f12668911ddd6a2.jpeg

开发思路

结构设计

  1. 创建一个外层容器,用于包裹两张图片和滑块。
  2. 底层图片(原始图像)始终可见。
  3. 顶层图片(优化后图像)放置在上方,并使用 clip-path 控制其显示范围。
  4. 滑块(拖动条) 用于调整顶层图片的可见区域。
<div class="image-change-block">
  <div class="desc-container">
    <div class="before-desc">BEFORE (827 KB)</div>
    <div class="after-desc">AFTER (94 KB)</div>
  </div>
  <img src="after.jpg" class="new-img" />
  <div class="clip-container">
    <img src="before.jpg" class="old-img" />
  </div>
  <div class="handle-container">
    <div class="bar-btn" id="barBtn"></div>
    <div class="bar-line" id="barLine"></div>
  </div>
</div>

样式布局

  • 两张图片:底层图片 position: absolute; 覆盖整个容器,顶层图片使用 clip-path 或 width 控制显示区域。
  • 滑块样式:自定义 div + 伪元素 作为滑块,并放在 absolute 位置。
.image-change-block {
  position: relative;
  max-width: 44rem;
  overflow: hidden;
  border-radius: 1.25rem;
}

.desc-container {
  top: 1.25rem;
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  width: 100%;
  padding: 0 1.25rem;
  gap: 0.3125rem;
}

.after-desc,
.before-desc {
  background-color: #000000;
  opacity: 0.6;
  color: #fff;
  border-radius: 0.25rem;
  z-index: 10;
  font-size: var(--global--font-size-sm);
  padding: 0.3125rem 1.5rem;
}

.old-img,
.new-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.clip-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.old-img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  clip-path: inset(0 50% 0 0);
  transition: clip-path 0.01s ease;
}

.handle-container {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  pointer-events: none;
}

.bar-btn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  pointer-events: all;
  cursor: ew-resize;
  z-index: 2;
}

.bar-line {
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  width: 3px;
  background-color: var(--theme-green-color);
  z-index: 1;
}

.bar-btn::before {
  width: 28px;
  height: 28px;
  content: "";
  cursor: ew-resize;
  background: #00d4c9;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-45%, -50%);
  display: block;
  border-radius: 50%;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  z-index: 1;
}

.bar-btn::after {
  content: "";
  background: var(--theme-green-color);
  width: 3px;
  height: 100%;
  position: absolute;
  left: 50%;
  top: 0;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}

交互逻辑

封装一个兼容 PC 和移动端的拖拽对比函数,通过传入对应的 dom 实现鼠标或手指拖动滑块时图像的对比。

initClip({
  barBtn: document.querySelector(`.bar-btn`),
  barLine: document.querySelector(`.bar-line`),
  clipContainer: document.querySelector(`.clip-container`),
  oldImg: document.querySelector(`.old-img`),
});

/**
 * 初始化通过剪裁实现图像对比的功能
 * @param {Object} doms - 包含所需DOM元素的对象
 *  - barBtn: 滑动按钮元素
 *  - barLine: 滑动线条元素
 *  - clipContainer: 剪裁容器元素
 *  - oldImg: 被剪裁的图片元素
 */
function initClip(doms) {
  // 解构赋值获取所需的DOM元素
  const { barBtn, barLine, clipContainer, oldImg } = doms;
  // 定义变量以跟踪鼠标或触摸是否在拖动
  let isDragging = false;
  let isMDragging = false;

  /**
   * 更新图片剪裁位置
   * @param {number} x - 鼠标或触摸在剪裁容器上的x坐标
   */
  function updateImageClip(x) {
    // 计算剪裁容器的宽度
    const containerWidth = clipContainer.offsetWidth;
    // 计算并限制剪裁的百分比
    const percent = Math.min(Math.max(x / containerWidth, 0), 1);
    // 更新图片的剪裁路径
    oldImg.style.clipPath = `inset(0 ${100 - percent * 100}% 0 0)`;
    // 更新滑动按钮和线条的位置
    barBtn.style.left = `${percent * 100}%`;
    barLine.style.left = `${percent * 100}%`;
  }

  // 添加鼠标按下事件监听器到滑动按钮
  barBtn.addEventListener("mousedown", (e) => {
    // 开始拖动并阻止默认行为
    isDragging = true;
    e.preventDefault();
  });

  // 添加鼠标抬起事件监听器到文档
  document.addEventListener("mouseup", () => {
    // 结束拖动
    isDragging = false;
  });

  // 添加鼠标移动事件监听器到文档
  document.addEventListener("mousemove", (e) => {
    // 如果正在拖动,则更新图片剪裁
    if (isDragging) {
      const x = e.clientX - clipContainer.getBoundingClientRect().left;
      updateImageClip(x);
    }
  });

  // 添加点击事件监听器到剪裁容器
  clipContainer.addEventListener("click", (e) => {
    // 点击时更新图片剪裁
    const x = e.clientX - clipContainer.getBoundingClientRect().left;
    updateImageClip(x);
  });

  // 添加触摸开始事件监听器到滑动按钮
  barBtn.addEventListener("touchstart", (e) => {
    // 开始拖动并阻止默认行为
    isMDragging = true;
    e.preventDefault();
  });

  // 添加触摸结束事件监听器到文档
  document.addEventListener("touchend", () => {
    // 结束拖动
    isMDragging = false;
  });

  // 添加触摸移动事件监听器到文档
  document.addEventListener("touchmove", (e) => {
    // 如果正在拖动,则更新图片剪裁
    if (isMDragging) {
      const touch = e.touches[0];
      const x = touch.clientX - clipContainer.getBoundingClientRect().left;
      updateImageClip(x);
    }
  });
}

总结

这个滑块对比组件利用 clip-path 来裁剪图像,并结合鼠标和触摸事件监听,实现了流畅的交互体验。它不仅适用于图像压缩前后的对比,还可以扩展到滤镜效果、照片修复等其他图像对比场景。在实际开发中,我们可以根据 UI 需求,进一步优化滑块的样式、动画效果,以及提升移动端的操作体验。

原文链接