html
复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片拖动Clip对比功能</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
width: 100%;
height: 500px;
border-radius: 12px;
overflow: hidden;
background-color: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #e0e0e0;
}
.image-comparison {
position: relative;
height: 100%;
width: 100%;
display: flex;
overflow: hidden;
touch-action: none;
user-select: none;
}
.image-comparison img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
}
.clip-item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
user-select: none;
will-change: clip-path;
clip-path: inset(0 0 0 50%);
transition: clip-path 0.1s ease-out;
}
.handle-container {
position: absolute;
top: 0;
height: 100%;
background: none;
border: 0;
padding: 0;
pointer-events: all;
appearance: none;
outline: 0;
transform: translate3d(-50%, 0, 0);
left: 50%;
cursor: ew-resize;
z-index: 10;
}
.handle-root {
display: flex;
flex-direction: column;
place-items: center;
height: 100%;
cursor: ew-resize;
pointer-events: none;
color: white;
}
.handle-line {
flex-grow: 1;
height: 100%;
width: 2px;
background-color: currentColor;
pointer-events: auto;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
}
.handle-button {
display: grid;
grid-auto-flow: column;
gap: 8px;
place-content: center;
flex-shrink: 0;
width: 56px;
height: 56px;
border-radius: 50%;
border: 2px solid currentColor;
pointer-events: auto;
backdrop-filter: blur(7px);
background-color: rgba(0, 0, 0, 0.125);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.35);
}
.handle-arrow {
width: 0;
height: 0;
border-top: 8px solid transparent;
border-right: 10px solid currentColor;
border-bottom: 8px solid transparent;
}
.handle-arrow:last-child {
transform: rotate(180deg);
}
.info {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 15px;
border-radius: 8px;
font-size: 14px;
z-index: 5;
}
@media (max-width: 768px) {
.container {
height: 400px;
}
.handle-button {
width: 48px;
height: 48px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="image-comparison" id="imageComparison">
<img alt="Original Image" src="./img/demo5.png">
<div class="clip-item" id="clipItem">
<img alt="Translated Image" src="./img/demo6.png">
</div>
<button class="handle-container" id="handleContainer"
aria-label="拖动移动或聚焦并使用箭头键"
aria-orientation="horizontal"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
role="slider">
<div class="handle-root">
<div class="handle-line"></div>
<div class="handle-button">
<div class="handle-arrow"></div>
<div class="handle-arrow"></div>
</div>
<div class="handle-line"></div>
</div>
</button>
<div class="info">
拖动中间的滑块来对比两张图片
</div>
</div>
</div>
<script>
class ImageComparison {
constructor(container) {
this.container = container;
this.clipItem = container.querySelector('#clipItem');
this.handleContainer = container.querySelector('#handleContainer');
this.isDragging = false;
this.currentPosition = 50; // 初始位置 50%
this.init();
}
init() {
this.bindEvents();
this.updatePosition(this.currentPosition);
}
bindEvents() {
// 鼠标事件
this.handleContainer.addEventListener('mousedown', this.handleMouseDown.bind(this));
document.addEventListener('mousemove', this.handleMouseMove.bind(this));
document.addEventListener('mouseup', this.handleMouseUp.bind(this));
// 触摸事件
this.handleContainer.addEventListener('touchstart', this.handleTouchStart.bind(this));
document.addEventListener('touchmove', this.handleTouchMove.bind(this));
document.addEventListener('touchend', this.handleTouchEnd.bind(this));
// 键盘事件
this.handleContainer.addEventListener('keydown', this.handleKeyDown.bind(this));
// 防止拖动时选中文本
this.container.addEventListener('selectstart', (e) => e.preventDefault());
}
handleMouseDown(e) {
e.preventDefault();
this.isDragging = true;
this.container.style.cursor = 'ew-resize';
this.clipItem.style.transition = 'none';
}
handleMouseMove(e) {
if (!this.isDragging) return;
e.preventDefault();
const rect = this.container.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentage = (x / rect.width) * 100;
this.updatePosition(Math.max(0, Math.min(100, percentage)));
}
handleMouseUp() {
this.isDragging = false;
this.container.style.cursor = '';
this.clipItem.style.transition = 'clip-path 0.1s ease-out';
}
handleTouchStart(e) {
e.preventDefault();
this.isDragging = true;
this.clipItem.style.transition = 'none';
}
handleTouchMove(e) {
if (!this.isDragging) return;
e.preventDefault();
const touch = e.touches[0];
const rect = this.container.getBoundingClientRect();
const x = touch.clientX - rect.left;
const percentage = (x / rect.width) * 100;
this.updatePosition(Math.max(0, Math.min(100, percentage)));
}
handleTouchEnd() {
this.isDragging = false;
this.clipItem.style.transition = 'clip-path 0.1s ease-out';
}
handleKeyDown(e) {
const step = 5;
switch(e.key) {
case 'ArrowLeft':
e.preventDefault();
this.updatePosition(Math.max(0, this.currentPosition - step));
break;
case 'ArrowRight':
e.preventDefault();
this.updatePosition(Math.min(100, this.currentPosition + step));
break;
case 'Home':
e.preventDefault();
this.updatePosition(0);
break;
case 'End':
e.preventDefault();
this.updatePosition(100);
break;
}
}
updatePosition(percentage) {
this.currentPosition = percentage;
// 更新clip-path
this.clipItem.style.clipPath = `inset(0 0 0 ${percentage}%)`;
// 更新滑块位置
this.handleContainer.style.left = `${percentage}%`;
// 更新ARIA属性
this.handleContainer.setAttribute('aria-valuenow', Math.round(percentage));
}
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('imageComparison');
new ImageComparison(container);
});
</script>
</body>
</html>