vue3+ts div自由拖拽改变元素宽度
实现原理:
事件监听:通过
@mousedown
触发拖拽,结合mousemove
和mouseup
实现实时调整尺寸计算:记录初始宽度和鼠标位置,通过差值计算新宽度
边界限制:设置
minWidth
和maxWidth
防止过度拉伸
事件监听
通过@mousedown
触发拖拽初始化,在mousemove
中动态计算宽度差值,mouseup
结束拖拽并移除事件监听。这种设计避免了事件泄露
const startDrag = (e) => {isDragging.value = true;startX.value = e.clientX; // 记录初始鼠标位置startWidth.value = leftWidth.value; // 记录初始宽度document.addEventListener('mousemove', handleDrag);document.addEventListener('mouseup', stopDrag);
};
宽度计算与边界限制
根据鼠标移动距离deltaX
调整宽度,通过Math.min/max
确保宽度在[100px, 800px]
范围内
const handleDrag = (e) => {const deltaX = e.clientX - startX.value;leftWidth.value = Math.min(800, Math.max(100, startWidth.value + deltaX));
};
拖拽手柄设计
CSS中通过cursor: e-resize
明确拖拽方向提示,绝对定位确保手柄与左侧面板右边界对齐
.drag-handle {width: 10px;background: #ccc;cursor: e-resize; /* 水平拖拽图标 */position: absolute;right: 0;
}
响应式与持久化
结合localStorage
保存用户调整后的宽度,提升用户体验
// 初始化时读取存储值
const leftWidth = ref(Number(localStorage.getItem('leftWidth')) || 200);// 拖拽时实时保存
watch(leftWidth, (newVal) => {localStorage.setItem('leftWidth', newVal);
});
效果图:
完整代码:
<template><div class="containerHome" ref="containerHome" :style="{ height: height + 'px' }"><div class="left-panel" :style="{ width: leftWidth + 'px' }">111</div><div class="resize-handle" @mousedown="startDrag"></div><div class="right-panel" :style="{ width: rightWidth + 'px' }">222</div></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue'const ShiftHandoverIndex = defineAsyncComponent(() => import('/@/views/shiftHandover/index.vue'));
const height = ref(800);
const leftWidth = ref(100);
const rightWidth = ref(0);
const startX = ref(0);
const startWidth = ref(0);
const containerHome = ref(null)const startDrag = (e) => {startX.value = e.clientX;startWidth.value = leftWidth.value;document.addEventListener('mousemove', onDrag);document.addEventListener('mouseup', stopDrag);
};const onDrag = (e) => {const newWidth = startWidth.value + e.clientX - startX.value;leftWidth.value = Math.max(100, Math.min(newWidth, 1500));rightWidth.value = containerHome.value.offsetWidth - (leftWidth.value+10)
};const stopDrag = () => {document.removeEventListener('mousemove', onDrag);document.removeEventListener('mouseup', stopDrag);
};let observer = null
let debounceTimer = nullconst handleResize = () => {clearTimeout(debounceTimer)debounceTimer = setTimeout(() => {const el = document.querySelector('.layout-parent');if (el) {height.value = el.offsetHeight-10;}leftWidth.value = el.offsetWidth / 2 - 20rightWidth.value = el.offsetWidth - (leftWidth.value+10)}, 100) // 200ms防抖时间
}onMounted(() => {if (containerHome.value) {observer = new ResizeObserver(handleResize)observer.observe(containerHome.value)}
})onUnmounted(() => {clearTimeout(debounceTimer)if (observer) observer.disconnect()
})
</script><style scoped>
.containerHome {display: flex;width: 100%;border: 1px solid #ddd;
}.left-panel {background: #f5f5f5;min-width: 100px;max-width: 1500px;overflow: auto;padding: 10px;
}.resize-handle {width: 5px;background: #e0e0e0;cursor: col-resize;transition: background 0.2s;
}.resize-handle:hover {background: #999;
}.right-panel {flex: 1;overflow: auto;padding: 10px;
}
</style>