背景介绍
最近,公司需要开发一款在线图像压缩工具,其中的一个关键功能是让用户直观地比较压缩前后的图像效果。因此,我们设计了一个对比组件,它允许用户通过拖动滑块,动态调整两张图像的显示区域,从而清晰地看到压缩前后的差异。
目标效果
- 两张图片堆叠放置,一张始终可见,另一张可调整可见范围。
- 通过滑块拖动,控制上层图片的显示区域。
- 适配 PC 和移动端,提供流畅的交互体验。
效果如图:
开发思路
结构设计
- 创建一个外层容器,用于包裹两张图片和滑块。
- 底层图片(原始图像)始终可见。
- 顶层图片(优化后图像)放置在上方,并使用 clip-path 控制其显示范围。
- 滑块(拖动条) 用于调整顶层图片的可见区域。
<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 需求,进一步优化滑块的样式、动画效果,以及提升移动端的操作体验。
原文链接
图像滑块对比功能的开发记录