之前参与的项目做过基于Cesium的地图影像卷帘对比,正好最近负责的项目也有相关功能(普通图片版),就挑战一下不用Cesium原生api该如何实现卷帘对比。
编辑
其主要实现思路:
- “本图”、“原图”图片宽高固定为容器的大小
- “原图”容器大小随分屏轴拖拽改变,裁剪超出容器大小的图片
裁剪图片需要设置CSS样式为:overflow-clip-margin: content-box; overflow: clip; 其中:
-
overflow-clip-margin: content-box;overflow-clip-margin是一个用于调整剪裁区域边缘的 CSS 属性。它定义了元素的内容在被剪裁时的额外外边距。content-box是一个值,表示剪裁区域的边缘与内容框相同。这意味着剪裁时不会为外边距、边框或内边距增加额外的空间。
-
overflow: clip;overflow是一个控制元素内容溢出行为的 CSS 属性。clip是overflow的一个值,它表示任何溢出的内容都将被简单地剪裁掉,而不会显示滚动条。这意味着超出元素边界的内容将不可见。
话不多说,放码过来。
template渲染
<!-- 卷帘 -->
<div ref="rollerRef" class="roller-wrapper" @dragstart.prevent>
<div class="roller-wrapper-current-image">
<!-- 本图 -->
<img :style="{ width: `${contentWidth}px` }" class="h-100%" :src="currentImage" draggable="false" />
</div>
<div class="roller-wrapper-origin-image" ref="originImgRef">
<!-- 原图 -->
<img :style="{ width: `${contentWidth}px` }" class="h-100%" :src="originalImage" draggable="false" />
</div>
<div class="roller-wrapper-slider" ref="draggable">
<el-image class="roller-wrapper-slider-btn" :src="sliderIcon" />
<div class="roller-wrapper-slider-text right-10px">原图</div>
<div class="roller-wrapper-slider-text left-10px">本图</div>
</div>
</div>
相关CSS样式:(注意 lang="less")
// 卷帘部分样式
.roller-wrapper {
width: 100%;
height: 100%;
min-height: 600px;
overflow: hidden;
position: relative;
&-current-image,
&-origin-image {
width: 100%;
height: 100%;
background-repeat: no-repeat;
background-size: cover;
background-position: center;
position: absolute;
top: 0;
left: 0;
pointer-events: none;
overflow: hidden;
img {
max-width: none;
overflow-clip-margin: content-box;
overflow: clip;
object-fit: contain;
}
}
&-origin-image {
width: 50%;
}
&-slider {
position: absolute;
left: 50%;
background-color: #f2f2f3;
width: 2px;
height: 100%;
z-index: 9999;
cursor: move;
&-btn {
width: 12px;
height: 27px;
position: absolute;
left: -5px;
top: 50%;
transform: translateY(-50%);
z-index: 9999;
}
&-text {
position: absolute;
top: 14px;
z-index: 9999;
width: 52px;
height: 33px;
padding: 0 12px;
color: #373e4c;
background: #f0f0f2;
border-radius: 17px;
font-weight: 500;
color: #373e4c;
line-height: 33px;
text-align: center;
}
&:before,
&:after {
content: ' ';
display: block;
width: 2px;
height: 9999px;
position: absolute;
left: 50%;
margin-left: -3.5px;
z-index: 30;
transition: 0.1s;
background: #fff;
}
&:before {
top: 100%;
}
&:after {
bottom: 100%;
}
}
}
接下来是实现鼠标拖拽
- 监听卷帘相关事件,注意在页面 onBeforeUnmount 生命周期时需要移除事件监听。
/** 卷帘相关监听事件 */
const addEventListeners = () => {
draggable.value?.addEventListener('mousedown', startDrag)
document.body.addEventListener('mouseup', endDrag)
document.body.addEventListener('mouseleave', endDrag)
document.body.addEventListener('mousemove', onDrag)
window.addEventListener('resize', onResize)
document.addEventListener('fullscreenchange', onResize)
contentWidth.value = rollerRef.value?.offsetWidth || 900
}
onMounted(() => {
addEventListeners()
})
2. 拖拽分屏轴改变容器大小,利用 Math.max 限制分屏轴的拖拽范围。
// 卷帘容器
const rollerRef = ref<HTMLElement>()
// 卷帘原图容器
const originImgRef = ref<HTMLElement>()
/** 图片容器宽度 */
const contentWidth = ref(0)
/** 是否拖拽 */
const isDragging = ref(false)
const draggable = ref<HTMLElement | null>(null)
/** 拖拽开始 */
const startDrag = () => {
isDragging.value = true
}
/** 拖拽事件 */
const onDrag = (e: MouseEvent) => {
if (isDragging.value) {
let x = e.pageX
x -= rollerRef.value!.getBoundingClientRect().left
let transform = Math.max(0, Math.min(x, rollerRef.value!.offsetWidth))
originImgRef.value!.style!.width = transform + 'px'
draggable.value!.style!.left = transform + 'px'
}
}
/** 拖拽结束 */
const endDrag = () => {
isDragging.value = false
}
编辑
细节处理
最后就是需要注意细节的问题,比如窗口大小改变时,卷帘的交互。此处我的处理方式是重新赋值容器宽度并将分屏轴恢复居中状态。
/** 窗口大小变化 */
const onResize = () => {
if (draggable.value) {
contentWidth.value = rollerRef.value?.offsetWidth || 900
originImgRef.value!.style!.width = '50%'
draggable.value.style.left = '50%'
}
}
以上就是图片卷帘对比的实现啦^-^ (AI生成的小猫好诡异啊哈哈)