预览链接:aicoding.juejin.cn/aicoding/wo…
有一天做blender 3d的时候,看到了这么一个效果,觉得有点意思,于是想着来做了,做了就是做了,就是这么个事:
据我看下面这个效果,用一个块加3张图,然后进行交互式,去控制三张不同图片的显示区域,就能成:
思路是这样哈:
三张图叠在一起,同框展示:左侧区域显示原始图片,右上区域显示环境渲染图,右下区域显示几何渲染图。
这是我写的效果:
动态分割控制:
- 可拖动
垂直分割线调整左右区域比例 - 可拖动
水平分割线调整右上和右下区域比例 - 可拖动
中心交叉点同时调整两个方向的分割比例
图片切割拖动效果
实现点
🎯 核心功能
- 可拖动分割线:垂直和水平分割线可以独立拖动
- 交叉点拖动:中心交叉点可以同时调整两个方向的分割位置
- 实时比例调整:四个区域会根据分割线位置实时调整大小
- 数值控制:通过输入框可以精确设置分割位置(10%-90%范围)
- 重置功能:一键恢复到默认的50%:50%分割比例
1. HTML结构设计
<div class="image-container" id="imageContainer">
<!-- 三层图片采用相同的容器结构 -->
<div class="image-layer left-image" id="leftImage">
<img src="original.png" alt="原始图片">
</div>
<div class="image-layer top-right-image" id="topRightImage">
<img src="env-render.png" alt="环境渲染">
</div>
<div class="image-layer bottom-right-image" id="bottomRightImage">
<img src="geo-render.png" alt="几何渲染">
</div>
<!-- 交互控制元素 -->
<div class="divider vertical-divider" id="verticalDivider"></div>
<div class="divider horizontal-divider" id="horizontalDivider"></div>
<div class="intersection" id="intersection"></div>
<!-- 区域标签 -->
<div class="section-label label-left">原始图片</div>
<div class="section-label label-top-right">环境渲染</div>
<div class="section-label label-bottom-right">几何渲染</div>
</div>
设计要点:
- 使用三层完全重叠的图片容器作为基础
- 独立的DOM元素实现分割线和控制点
- 标签元素使用绝对定位悬浮在对应区域
2. CSS样式深度解析
核心样式代码(带详细注释):
/* 基础容器样式 - 创建定位上下文和固定尺寸 */
.image-container {
position: relative; /* 创建定位上下文 */
width: 100%;
height: 500px; /* 固定高度确保比例一致 */
border-radius: 12px; /* 圆角美观设计 */
overflow: hidden; /* 隐藏溢出内容 */
background: #333; /* 默认背景色 */
}
/* 图片层通用样式 */
.image-layer {
position: absolute; /* 绝对定位实现重叠 */
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* 图片元素样式 - 确保完美填充 */
.image-layer img {
width: 100%;
height: 100%;
object-fit: cover; /* 保持比例填充整个容器 */
display: block;
-webkit-user-drag: none; /* 禁止拖动干扰 */
user-drag: none;
}
/* 左侧图片裁剪样式 - 使用CSS变量实现动态更新 */
.left-image {
/*
* inset()函数定义矩形裁剪区域
* 参数顺序:上 右 下 左
* 使用calc()计算动态值
*/
clip-path: var(--left-clip-path, inset(0px calc(100% - 250px) 0px 0px));
}
/* 分割线基础样式 */
.divider {
position: absolute;
background: white; /* 醒目白色 */
z-index: 10; /* 确保在图片上方 */
}
/* 垂直分割线特定样式 */
.vertical-divider {
width: 2px; /* 细线设计 */
height: 100%; /* 贯穿整个高度 */
cursor: ew-resize; /* 东西方向调整光标 */
}
/* 交叉点控制元素样式 */
.intersection {
position: absolute;
width: 20px;
height: 20px;
background: white;
border-radius: 50%; /* 圆形设计 */
cursor: move; /* 移动光标 */
z-index: 20; /* 最高层级 */
transform: translate(50%, -50%); /* 精确居中定位 */
}
3. js交互逻辑完整解析
核心类结构:
class ClipPathSplitter {
constructor() {
// 获取所有DOM元素引用
this.container = document.getElementById('imageContainer');
this.verticalDivider = document.getElementById('verticalDivider');
this.horizontalDivider = document.getElementById('horizontalDivider');
this.intersection = document.getElementById('intersection');
this.verticalPosInput = document.getElementById('verticalPos');
this.horizontalPosInput = document.getElementById('horizontalPos');
// 图片层引用
this.leftImage = document.getElementById('leftImage');
this.topRightImage = document.getElementById('topRightImage');
this.bottomRightImage = document.getElementById('bottomRightImage');
// 状态变量
this.isDragging = false; // 是否正在拖动
this.dragType = null; // 当前拖动类型
this.containerRect = null; // 容器尺寸缓存
// 初始化
this.init();
}
// ...其他方法...
}
完整方法实现(带详细注释):
/**
* 初始化方法 - 绑定事件监听器
*/
init() {
this.updateLayout(); // 初始布局计算
this.bindEvents(); // 事件绑定
}
/**
* 事件绑定方法
*/
bindEvents() {
// 垂直分割线鼠标按下事件
this.verticalDivider.addEventListener('mousedown', (e) => {
this.startDrag(e, 'vertical');
});
// 水平分割线鼠标按下事件
this.horizontalDivider.addEventListener('mousedown', (e) => {
this.startDrag(e, 'horizontal');
});
// 交叉点鼠标按下事件
this.intersection.addEventListener('mousedown', (e) => {
this.startDrag(e, 'both');
});
// 文档级鼠标移动事件
document.addEventListener('mousemove', (e) => this.onMouseMove(e));
// 文档级鼠标释放事件
document.addEventListener('mouseup', () => this.stopDrag());
// 输入框变化事件
this.verticalPosInput.addEventListener('input', () => this.updateFromInputs());
this.horizontalPosInput.addEventListener('input', () => this.updateFromInputs());
// 窗口大小变化事件
window.addEventListener('resize', () => {
this.containerRect = this.container.getBoundingClientRect();
});
}
/**
* 开始拖动处理
* @param {Event} e 鼠标事件对象
* @param {string} type 拖动类型 ('vertical'|'horizontal'|'both')
*/
startDrag(e, type) {
e.preventDefault();
this.isDragging = true;
this.dragType = type;
this.containerRect = this.container.getBoundingClientRect();
// 根据拖动类型设置不同光标样式
document.body.style.cursor =
type === 'vertical' ? 'ew-resize' :
type === 'horizontal' ? 'ns-resize' : 'move';
}
/**
* 鼠标移动处理
* @param {Event} e 鼠标事件对象
*/
onMouseMove(e) {
if (!this.isDragging || !this.containerRect) return;
// 计算相对于容器的坐标
const x = e.clientX - this.containerRect.left;
const y = e.clientY - this.containerRect.top;
// 限制在容器范围内
const verticalPos = Math.max(0, Math.min(800, x));
const horizontalPos = Math.max(0, Math.min(500, y));
// 根据拖动类型更新对应值
if (this.dragType === 'vertical' || this.dragType === 'both') {
this.verticalPosInput.value = Math.round(verticalPos);
}
if (this.dragType === 'horizontal' || this.dragType === 'both') {
this.horizontalPosInput.value = Math.round(horizontalPos);
}
this.updateLayout(); // 更新界面布局
}
/**
* 停止拖动处理
*/
stopDrag() {
this.isDragging = false;
this.dragType = null;
document.body.style.cursor = 'default'; // 恢复默认光标
}
/**
* 从输入框更新布局
*/
updateFromInputs() {
this.updateLayout();
}
/**
* 更新整个布局
*/
updateLayout() {
// 获取当前分割位置
const verticalPos = parseFloat(this.verticalPosInput.value);
const horizontalPos = parseFloat(this.horizontalPosInput.value);
// 获取容器尺寸
const containerWidth = this.container.offsetWidth;
const containerHeight = this.container.offsetHeight;
// 更新分割线位置和样式
this.verticalDivider.style.left = `${verticalPos}px`;
this.horizontalDivider.style.top = `${horizontalPos}px`;
this.horizontalDivider.style.left = `${verticalPos}px`;
this.horizontalDivider.style.width = `${containerWidth - verticalPos}px`;
// 更新交叉点位置(考虑元素自身尺寸)
this.intersection.style.left = `${verticalPos - 10}px`;
this.intersection.style.top = `${horizontalPos}px`;
// 计算各区域裁剪路径
const rightWidth = containerWidth - verticalPos;
const bottomHeight = containerHeight - horizontalPos;
/*
* clip-path: inset() 参数详解:
* inset(上剪切距离 右剪切距离 下剪切距离 左剪切距离)
* 例如:inset(0px 100px 0px 0px) 表示从右侧向内剪切100px
*/
const leftClipPath = `inset(0px ${rightWidth}px 0px 0px)`;
const topRightClipPath = `inset(0px 0px ${bottomHeight}px ${verticalPos}px)`;
const bottomRightClipPath = `inset(${horizontalPos}px 0px 0px ${verticalPos}px)`;
// 应用裁剪路径
this.leftImage.style.setProperty('--left-clip-path', leftClipPath);
this.topRightImage.style.setProperty('--top-right-clip-path', topRightClipPath);
this.bottomRightImage.style.setProperty('--bottom-right-clip-path', bottomRightClipPath);
}
优化
1. 性能优化实践
1.1 减少重绘与回流
// 在频繁操作时使用requestAnimationFrame
onMouseMove(e) {
if (!this.isDragging) return;
requestAnimationFrame(() => {
// 计算和更新逻辑...
});
}
1.2 尺寸缓存策略
// 在resize事件中使用防抖
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
this.containerRect = this.container.getBoundingClientRect();
}, 100);
});
2. 响应式设计增强
/* 添加媒体查询适应不同屏幕 */
@media (max-width: 768px) {
.image-container {
height: 300px; /* 小屏幕降低高度 */
}
.controls {
flex-direction: column; /* 垂直排列控制元素 */
}
.divider {
width: 4px; /* 触控设备增加可点击区域 */
}
}
3. 触摸屏支持扩展
// 添加触摸事件支持
this.verticalDivider.addEventListener('touchstart', (e) => {
this.startDrag(e.changedTouches[0], 'vertical');
});
document.addEventListener('touchmove', (e) => {
if (!this.isDragging) return;
e.preventDefault();
this.onMouseMove(e.changedTouches[0]);
});
document.addEventListener('touchend', () => this.stopDrag());
技术点
-
纯CSS裁剪技术:完全依赖
clip-path实现图片裁剪,无需Canvas或SVG- 优势:硬件加速、性能优异、代码简洁
- 创新点:结合CSS变量实现动态更新
-
分层渲染架构:
- 视觉层:三张图片绝对定位叠加
- 控制层:独立的分割线DOM元素
- 交互层:统一的事件处理系统
-
精准的坐标计算:
// 考虑元素自身尺寸的精确定位 this.intersection.style.left = `${verticalPos - 10}px`; // 10是元素宽度的一半 this.intersection.style.top = `${horizontalPos}px`; -
可扩展的设计模式:
- 易于添加更多分割区域
- 支持动态更换图片源
- 可集成到任何现有系统中
在哪里用得到
-
专业图像处理:
- 照片编辑前后对比
- 3D渲染效果比对
- 医学影像分析
-
电商产品展示:
<!-- 产品多角度展示示例 --> <div class="image-layer left-image"> <img src="product-front.jpg" alt="正面"> </div> <div class="image-layer top-right-image"> <img src="product-side.jpg" alt="侧面"> </div> <div class="image-layer bottom-right-image"> <img src="product-detail.jpg" alt="细节"> </div> -
数据可视化仪表盘:
- 多图表联动分析
- 时间序列对比
- 地理信息叠加