【使用vue3写一个拖拽分割面板】
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
需求
如VS code面板,文件展示和代码区域可进行宽度拖拽。由于使用了vue3,split-pane这个插件目前没有vue3版本。
原理
- 鼠标悬停到拖拽线上按下,获取当前MouseEvent的clientX并记录为startX
- 移动鼠标,获取当前MouseEvent的clientX并计算移动距离
- 给拖拽线左右容器重设宽度
- 竖向拖拽分割面板是计算clientY
vue3代码
<script lang="ts" setup name="SplitPanel">
import { onMounted, reactive, ref } from "vue";
import { useResizeObserver } from "@vueuse/core";
type mouseDataType = {
id: number;
width: number;
startX: number;
clientX: number;
};
type panelType = {
id: number;
width: number;
type: string;
};
const MIN_WIDTH = 100;
const splitPanelRef = ref<HTMLElement | null>(null);
const isDown = ref(false);
// 整个容器的宽高
const container = reactive({
width: 0,
});
// 子容器
const panelList = ref<panelType[]>([
{ id: 1, width: 100, type: "left" },
{ id: 2, width: 400, type: "center" },
{ id: 3, width: 10, type: "right" },
]);
const mouseData = ref<mouseDataType>({
id: 0,
width: 0,
startX: 0,
clientX: 0,
});
function setMouseData(options: { [key: string]: number }) {
mouseData.value = {
...mouseData.value,
...options,
};
}
useResizeObserver(splitPanelRef, (entries) => {
const entry = entries[0];
const { width } = entry.contentRect;
container.width = width;
initWidth();
});
onMounted(() => {
document.onmouseup = onMouseUp;
});
function initWidth() {
container.width = splitPanelRef.value?.clientWidth || 0;
panelList.value[0].width = 100;
panelList.value[1].width = container.width - 100 - 400;
panelList.value[2].width = 400;
}
function resizePanel(options: mouseDataType) {
if (options.id && options.clientX) {
const index = drapIndex();
const max_width = panelList.value.reduce((prev, curr, i) => {
if (index === i || index + 1 === i) {
prev += curr.width;
}
return prev;
}, 0);
panelList.value.forEach((el, i) => {
if (el.id === options.id) {
const offset = options.clientX - options.startX;
el.width = options.width + offset;
panelList.value[i + 1].width = max_width - (options.width + offset);
}
});
}
}
function drapIndex() {
return panelList.value.reduce((prev, curr, i) => {
if (curr.id === mouseData.value.id) prev = i;
return prev;
}, -1);
}
function drapLine(item: panelType) {
if (item.id === mouseData.value.id && isDown.value) {
return {
opacity: 0.5,
right: -(mouseData.value.clientX - mouseData.value.startX) + "px",
zIndex: 10,
};
} else {
return { opacity: 0, right: 0, zIndex: 0 };
}
}
function onMouseDown(e: MouseEvent, item: panelType) {
// console.log("down");
isDown.value = true;
setMouseData({ id: item.id, width: item.width, startX: e.clientX });
}
function onMouseMove(e: MouseEvent) {
if (isDown.value) {
const index = drapIndex();
const min = panelList.value[index].width - MIN_WIDTH;
const max = panelList.value[index + 1].width - MIN_WIDTH;
const { startX } = mouseData.value;
// 两侧容器的最小宽度为 MIN_WIDTH
if (e.clientX - startX > max) {
setMouseData({ clientX: startX + max });
} else if (e.clientX - startX < -min) {
setMouseData({ clientX: startX - min });
} else {
setMouseData({ clientX: e.clientX });
}
}
}
function onMouseUp() {
console.log("up");
if (isDown.value) {
resizePanel(mouseData.value);
isDown.value = false;
setMouseData({ id: 0, startX: 0, clientX: 0 });
}
}
</script>
<template>
<div class="split-panel" @mousemove="onMouseMove" ref="splitPanelRef">
<div
v-for="(item, index) of panelList"
:key="index + ''"
class="panel-item"
:style="{
width: item.width + 'px',
}"
>
<slot :name="item.type"></slot>
<div
class="handle"
@mousedown="(e) => onMouseDown(e, item)"
:style="drapLine(item)"
v-if="index !== panelList.length - 1"
></div>
</div>
</div>
</template>
<style lang="scss" scoped>
.split-panel {
width: 100%;
// 定义容器高度
height: calc(100vh - 33px - 30px);
display: flex;
.panel-item {
position: relative;
border-right: 1px solid #ccc;
.handle {
position: absolute;
top: 0;
right: 0px;
width: 4px;
height: 100%;
border-right: 1px solid #ccc;
cursor: e-resize;
user-select: none;
}
}
}
</style>
使用
<SplitPane>
<template #left>
<div>left</div>
</template>
<template #center>
<div>center</div>
</template>
<template #right>
<div>right</div>
</template>
</SplitPane>
完了
GitHub 加速计划 / vu / vue
207.52 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:1 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 3 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 3 个月前
更多推荐
已为社区贡献3条内容
所有评论(0)