本组件是一个基于原生 HTML/CSS/JS 开发的交互式图片对比工具(Image Comparison Slider),常用于展示产品渲染前后、照片修图前后或场景变化的效果。
效果如图:

代码如下:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Halo 图片对比效果</title>
<style>
/* 基础样式复现 */
body {
margin: 0;
padding: 0;
background: #f5f5f5;
}
.sp-ba-wrap {
max-width: 1440px;
min-width: 343px;
margin: 0 auto;
margin-top: 120px;
padding: 0 20px;
}
.sp-ba {
position: relative;
margin: 0 auto;
max-width: 1200px;
z-index: 2;
}
/* 核心对比滑块样式 */
.banda-slider {
display: block;
overflow: hidden;
position: relative;
border-radius: 16px;
width: 100%;
line-height: 0;
}
.banda-slider img {
width: 100%;
height: auto;
display: block;
user-select: none;
}
.banda-reveal {
left: 0;
top: 0;
bottom: 0;
overflow: hidden;
position: absolute;
right: 50%;
/* 初始位置 */
z-index: 1;
border-right: 2px solid #fff;
}
.banda-reveal>img {
height: 100%;
width: 200%;
/* 这里必须是父容器的2倍才能保证内容不拉伸 */
max-width: none;
object-fit: cover;
}
/* 交互控件:透明滑块 */
.banda-range {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
opacity: 0;
cursor: ew-resize;
z-index: 10;
}
/* 装饰用的中间圆圈 */
.banda-handle {
background: #000;
border-radius: 50%;
color: #fff;
height: 48px;
width: 48px;
left: 50%;
top: 50%;
position: absolute;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 5;
display: flex;
align-items: center;
justify-content: center;
}
.banda-handle:before,
.banda-handle:after {
content: "";
border: solid white;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 3px;
}
.banda-handle:before {
transform: rotate(135deg);
margin-right: 4px;
}
.banda-handle:after {
transform: rotate(-45deg);
margin-left: 4px;
}
@media only screen and (max-width: 888px) {
.sp-ba-wrap {
margin-top: 40px;
}
}
</style>
</head>
<body>
<div class="sp-ba-wrap">
<div class="sp-ba">
<div class="banda-slider" id="mySlider">
<img src="https://cdn.shopify.com/s/files/1/0268/7297/1373/files/55379ad14f3eb2af576acdc527686e4e_3840x2000_6af3220c-9331-4d97-bec8-3687cd8745f9.jpg?v=1711012998"
alt="Before">
<div class="banda-reveal" id="revealLayer">
<img src="https://cdn.shopify.com/s/files/1/0268/7297/1373/files/83793ec91a3bdd17ce22ed844b4e4aeb_3840x2000_b58d63a4-2a39-4550-b890-ff6519f49952.jpg?v=1711012992"
alt="After" id="revealImg">
</div>
<input type="range" min="0" max="100" value="50" class="banda-range" id="rangeInput">
<div class="banda-handle" id="handle"></div>
</div>
</div>
</div>
<script>
// 逻辑实现:监听滑动条并实时更新 UI
const range = document.getElementById('rangeInput');
const revealLayer = document.getElementById('revealLayer');
const revealImg = document.getElementById('revealImg');
const handle = document.getElementById('handle');
const slider = document.getElementById('mySlider');
range.addEventListener('input', (e) => {
const value = e.target.value;
// 1. 更新遮罩层的宽度(实际上是修改 right 距离)
// 原理:当 value 增加,左侧显示更多,revealLayer 需要向右移
revealLayer.style.right = (100 - value) + '%';
// 2. 更新分隔小圆圈的位置
handle.style.left = value + '%';
// 3. 动态调整内部图片的宽度,防止拉伸
// 因为 revealLayer 的宽度在变,其内部图片需要反向维持比例
const containerWidth = slider.offsetWidth;
revealImg.style.width = containerWidth + 'px';
});
// 窗口大小改变时重置图片宽度
window.addEventListener('resize', () => {
revealImg.style.width = slider.offsetWidth + 'px';
});
// 初始化执行一次
revealImg.style.width = slider.offsetWidth + 'px';
</script>
</body>
</html>
一、 实现的效果
-
视觉表现:页面中间展示一张图片,通过一条可移动的垂直分割线将画面分为左右两部分,分别显示不同的内容(如:白昼与黑夜、修图前与修图后)。中心配有一个黑色圆形手柄提示用户可进行操作。
-
交互表现:
- 手动拖拽:用户点击并左右拖动中间的手柄,即可实时改变两侧图片的显示比例。
- 移动端适配:支持触摸滑动,在手机或平板上拥有流畅的交互体验。
- "揭开"感:手柄移动的过程类似于拨开一张蒙版,视觉反馈直观且平滑。
二、 实现思路
-
分层堆叠(Layering)
将两张分辨率完全一致的图片放置在同一个父容器中。底层图片(Bottom Image)作为固定基准,顶层图片(Top Image)嵌套在一个带有遮罩属性的容器中。
-
动态裁剪(Clipping)
给顶层图片容器设置
overflow: hidden。通过改变这个容器的宽度(例如从50%变为30%),它就像一扇"移动的门",遮挡掉上层图片的一部分,从而露出底层的图片。 -
视觉对齐(Alignment Fix)
- 挑战:默认情况下,如果父容器变窄,内部图片通常会随之缩小或变形。
- 对策:给上层图片设置一个固定的宽度(通常等于外层大容器的宽度),使其不随遮罩容器的缩放而缩放。这样,上下两张图片的内容就能在视觉上完美重合。
-
隐形控制(Invisible Control)
在整个组件最顶层覆盖一个完全透明的 HTML 滑动条
<input type="range">。这样做可以利用浏览器原生的高性能滑动监听,无需自己写复杂的鼠标位移计算逻辑。
三、 实现原理
1. 核心 CSS 结构
- 遮罩原理 :利用
position: absolute进行定位。遮罩层banda-reveal充当"视口",通过修改它的right或width属性来控制露出的比例。 - 布局优化 :使用
line-height: 0和display: block消除图片底部常见的像素间隙,确保容器高度完全由图片撑开。
2. 数值映射
组件通过 JavaScript 实时获取滑块的数据并进行映射:
- 滑块当前值 : <math xmlns="http://www.w3.org/1998/Math/MathML"> X X </math>X (取值范围 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 ∼ 100 0 \sim 100 </math>0∼100)
- 遮罩层宽度 : <math xmlns="http://www.w3.org/1998/Math/MathML"> W = X % W = X\% </math>W=X% (决定揭开多少内容)
- 手柄偏移量 : <math xmlns="http://www.w3.org/1998/Math/MathML"> L = X % L = X\% </math>L=X% (确保手柄始终在分割线上)
3. 同步逻辑代码
JavaScript 监听 input 事件,实现数据驱动 UI:
JavaScript
ini
// 核心同步逻辑示例
rangeInput.addEventListener('input', (e) => {
const sliderValue = e.target.value;
// 1. 改变遮罩层宽度(拨开视觉效果)
revealLayer.style.width = sliderValue + '%';
// 2. 同步移动中间的控制手柄
handle.style.left = sliderValue + '%';
});