vue3+ts实现拖拽缩放,全屏
DragResizeZoom.vue
背景:在cesium大屏项目需要拖拽,缩放模块,还需要全屏。
功能如下:
1. 拖拽组件
2. 拉升组件长宽
3. 对角线拉升会比例缩放
4. 双击组件则全屏
5. 复原长宽和位置
使用案例:
<script setup lang="ts">
import { ref } from "vue";
import DragResizeZoom from "./components/DragResizeZoom.vue";
const dragResizeZoom = ref<InstanceType<typeof DragResizeZoom> | null>(null);function handleReset() {dragResizeZoom.value?.resetPosition();
}
</script><template><div id="app"><button @click="handleReset">reset</button><DragResizeZoomref="dragResizeZoom":initial-x="100":initial-y="100":initial-width="400":initial-height="300"xdirection="right"><div class="c1"><div><h1>这是一个标题</h1></div><div>这是一个段落fdsafdsafdsafdsfdsa</div></div></DragResizeZoom></div>
</template><style scoped style="less">
#app {width: 100vw;height: 100vh;background-color: #b4b4b4;
}
.c1 {/* width: 300px;height: 300px; */background-color: #fff;color: pink;
}
</style>
<template><divref="containerRef"class="drag-resize-zoom":style="containerStyle"@mousedown="handleMouseDown"@mouseenter="showHandles = true"@mouseleave="showHandles = false"@dblclick="toggleFullscreen"><slot></slot><!-- Resize handles --><!-- <div class="resize-handle resize-nw" @mousedown="(e: any) => handleResizeMouseDown(e, 'nw')"></div> --><!-- <divclass="resize-handle resize-ne"@mousedown="(e: any) => handleResizeMouseDown(e, 'ne')"></div> --><!-- <divclass="resize-handle resize-sw"@mousedown="(e: any) => handleResizeMouseDown(e, 'sw')"></div> --><divclass="resize-handle resize-se":class="{ 'handle-visible': showHandles }"@mousedown="(e: any) => handleResizeMouseDown(e, 'se')"></div><!-- <div class="resize-handle resize-n" @mousedown="(e: any) => handleResizeMouseDown(e, 'n')"></div> --><divclass="resize-handle resize-s":class="{ 'handle-visible': showHandles }"@mousedown="(e: any) => handleResizeMouseDown(e, 's')"></div><!-- <divclass="resize-handle resize-w"@mousedown="(e: any) => handleResizeMouseDown(e, 'w')"></div> --><divclass="resize-handle resize-e":class="{ 'handle-visible': showHandles }"@mousedown="(e: any) => handleResizeMouseDown(e, 'e')"></div></div>
</template><script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, computed } from "vue";// 添加props定义
const props = defineProps({initialX: {type: Number,default: 0,},initialY: {type: Number,default: 0,},initialWidth: {type: Number,default: 300,},initialHeight: {type: Number,default: 300,},// 添加xdirection属性,支持'left'和'right'值xdirection: {type: String,default: "left",validator: (value: string) => ["left", "right"].includes(value),},
});const containerRef = ref<HTMLElement | null>(null);// 添加控制句柄显示的状态
const showHandles = ref(false);// 添加全屏状态
const isFullscreen = ref(false);// 组件状态
const state = ref({x: props.initialX, // 使用props.initialX初始化y: props.initialY, // 使用props.initialY初始化width: props.initialWidth, // 使用props.initialWidth初始化height: props.initialHeight, // 使用props.initialHeight初始化zoom: 1,isDragging: false,isResizing: false,dragStartX: 0,dragStartY: 0,startX: 0,startY: 0,startWidth: 0,startHeight: 0,resizeDirection: "",// 添加right和bottom属性right: undefined as number | undefined,bottom: undefined as number | undefined,
});// 容器样式
const containerStyle = computed(() => {// 根据xdirection决定使用left还是right定位if (props.xdirection === "right") {return {position: "absolute" as const,right: state.value.x + "px",top: state.value.y + "px",// 添加bottom,当它有值时使用...(state.value.bottom !== undefined && {bottom: state.value.bottom + "px",}),width: state.value.width + "px",height: state.value.height + "px",transform: `scale(${state.value.zoom})`,transformOrigin: "top left",};} else {return {position: "absolute" as const,left: state.value.x + "px",top: state.value.y + "px",// 添加right和bottom,当它们有值时使用...(state.value.right !== undefined && {right: state.value.right + "px",}),...(state.value.bottom !== undefined && {bottom: state.value.bottom + "px",}),width: state.value.width + "px",height: state.value.height + "px",transform: `scale(${state.value.zoom})`,transformOrigin: "top left",};}
});// 处理鼠标按下事件(拖拽)
const handleMouseDown = (e: MouseEvent) => {if ((e.target as HTMLElement).classList.contains("resize-handle")) {return;}state.value.isDragging = true;state.value.dragStartX = e.clientX;state.value.dragStartY = e.clientY;state.value.startX = state.value.x;state.value.startY = state.value.y;document.addEventListener("mousemove", handleMouseMove);document.addEventListener("mouseup", handleMouseUp);
};// 处理鼠标移动事件(拖拽)
const handleMouseMove = (e: MouseEvent) => {if (state.value.isDragging) {// 根据xdirection决定如何更新x坐标if (props.xdirection === "right") {state.value.x = state.value.startX - (e.clientX - state.value.dragStartX);} else {state.value.x = state.value.startX + (e.clientX - state.value.dragStartX);}state.value.y = state.value.startY + (e.clientY - state.value.dragStartY);}if (state.value.isResizing) {resizeComponent(e);}
};// 处理鼠标释放事件
const handleMouseUp = () => {state.value.isDragging = false;state.value.isResizing = false;document.removeEventListener("mousemove", handleMouseMove);document.removeEventListener("mouseup", handleMouseUp);
};// 处理调整大小鼠标按下事件
const handleResizeMouseDown = (e: MouseEvent, direction: string) => {e.stopPropagation();state.value.isResizing = true;state.value.resizeDirection = direction;state.value.startX = e.clientX;state.value.startY = e.clientY;state.value.startWidth = state.value.width;state.value.startHeight = state.value.height;document.addEventListener("mousemove", handleMouseMove);document.addEventListener("mouseup", handleMouseUp);
};// 调整组件大小
const resizeComponent = (e: MouseEvent) => {const deltaX = e.clientX - state.value.startX;const deltaY = e.clientY - state.value.startY;const direction = state.value.resizeDirection;// 记录调整前的尺寸用于计算缩放const oldWidth = state.value.width;const oldHeight = state.value.height;const oldX = state.value.x;const oldY = state.value.y;// 重置right和bottom值state.value.right = undefined;state.value.bottom = undefined;switch (direction) {case "nw": // 西北角state.value.width = Math.max(50, state.value.startWidth - deltaX);state.value.height = Math.max(50, state.value.startHeight - deltaY);// 修改:根据需求调整left和top定位if (state.value.width !== oldWidth || state.value.height !== oldHeight) {if (props.xdirection === "right") {state.value.x = oldX - (state.value.startWidth - state.value.width);} else {state.value.x = e.clientX;}state.value.y = oldY + (state.value.startHeight - state.value.height);}break;case "ne": // 东北角state.value.width = Math.max(50, state.value.startWidth + deltaX);state.value.height = Math.max(50, state.value.startHeight - deltaY);break;case "sw": // 西南角state.value.width = Math.max(50, state.value.startWidth - deltaX);state.value.height = Math.max(50, state.value.startHeight + deltaY);// 使用right定位if (state.value.width !== oldWidth) {if (props.xdirection === "right") {state.value.x = oldX - (state.value.startWidth - state.value.width);} else {state.value.right = window.innerWidth - oldX - state.value.startWidth;state.value.x = oldX + (state.value.startWidth - state.value.width);}}break;case "se": // 东南角state.value.width = Math.max(50, state.value.startWidth + deltaX);state.value.height = Math.max(50, state.value.startHeight + deltaY);break;case "n": // 上边state.value.height = Math.max(50, state.value.startHeight - deltaY);if (state.value.height !== oldHeight) {state.value.y = oldY + (state.value.startHeight - state.value.height);}break;case "s": // 下边state.value.height = Math.max(50, state.value.startHeight + deltaY);break;case "w": // 左边state.value.width = Math.max(50, state.value.startWidth - deltaX);if (state.value.width !== oldWidth) {if (props.xdirection === "right") {state.value.x = oldX - (state.value.startWidth - state.value.width);} else {state.value.x = oldX + (state.value.startWidth - state.value.width);}}break;case "e": // 右边state.value.width = Math.max(50, state.value.startWidth + deltaX);break;}// 计算缩放比例(仅对角调整时应用缩放)if (direction === "se" ||direction === "ne" ||direction === "sw" ||direction === "nw") {const widthRatio = state.value.width / state.value.startWidth;const heightRatio = state.value.height / state.value.startHeight;state.value.zoom = Math.min(widthRatio, heightRatio);}
};// 添加全屏切换功能
const toggleFullscreen = () => {if (!containerRef.value) return;const element = containerRef.value;if (!isFullscreen.value) {// 进入全屏if (element.requestFullscreen) {element.requestFullscreen();} else if ((element as any).mozRequestFullScreen) {// Firefox(element as any).mozRequestFullScreen();} else if ((element as any).webkitRequestFullscreen) {// Chrome, Safari and Opera(element as any).webkitRequestFullscreen();} else if ((element as any).msRequestFullscreen) {// IE/Edge(element as any).msRequestFullscreen();}} else {// 退出全屏if (document.exitFullscreen) {document.exitFullscreen();} else if ((document as any).mozCancelFullScreen) {// Firefox(document as any).mozCancelFullScreen();} else if ((document as any).webkitExitFullscreen) {// Chrome, Safari and Opera(document as any).webkitExitFullscreen();} else if ((document as any).msExitFullscreen) {// IE/Edge(document as any).msExitFullscreen();}}isFullscreen.value = !isFullscreen.value;
};// 监听全屏变化事件
const handleFullscreenChange = () => {isFullscreen.value = !!(document.fullscreenElement ||(document as any).mozFullScreenElement ||(document as any).webkitFullscreenElement ||(document as any).msFullscreenElement);
};// 添加恢复初始位置的函数
const resetPosition = () => {state.value.x = props.initialX;state.value.y = props.initialY;state.value.width = props.initialWidth;state.value.height = props.initialHeight;state.value.zoom = 1;state.value.right = undefined;state.value.bottom = undefined;
};// 暴露函数给父组件使用
defineExpose({resetPosition,
});// 清理事件监听器
onBeforeUnmount(() => {document.removeEventListener("mousemove", handleMouseMove);document.removeEventListener("mouseup", handleMouseUp);document.removeEventListener("fullscreenchange", handleFullscreenChange);document.removeEventListener("webkitfullscreenchange",handleFullscreenChange);document.removeEventListener("mozfullscreenchange", handleFullscreenChange);document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
});// 添加全屏事件监听
onMounted(() => {document.addEventListener("fullscreenchange", handleFullscreenChange);document.addEventListener("webkitfullscreenchange", handleFullscreenChange);document.addEventListener("mozfullscreenchange", handleFullscreenChange);document.addEventListener("MSFullscreenChange", handleFullscreenChange);
});
</script><style scoped>
.drag-resize-zoom {/* margin: 3px; */position: relative;cursor: move;z-index: 9999;overflow: hidden;
}.resize-handle {position: absolute;/* background: linear-gradient(135deg, #4a90e2, #1a3a6a); */background-color: #5a9fff;z-index: 9999;opacity: 0; /* 默认隐藏 */transition: opacity 0.2s ease;
}.handle-visible {opacity: 1;
}.resize-handle::before {content: "";position: absolute;top: -3px;left: -3px;right: -3px;bottom: -3px;opacity: 0;
}.resize-handle:hover::before {opacity: 1;
}.resize-se {width: 12px;height: 12px;background: transparent;border: none;box-shadow: none;top: auto;right: 0px;bottom: 0px;cursor: se-resize; /* 添加斜对角鼠标样式 */
}.resize-se::before {display: none;
}.resize-se::after {content: "";position: absolute;width: 0;height: 0;border-style: solid;border-width: 0 0 12px 12px;border-color: transparent transparent #4a90e2 transparent;box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);/* transition: all 0.2s ease; */
}.resize-se:hover::after {border-color: transparent transparent #5aa0ff transparent;box-shadow: 2px 2px 4px rgba(74, 144, 226, 0.8);transform: scale(1.1);
}.resize-s {/* 下边句柄改为瘦高 */width: 100%;height: 3px;/* border-radius: 1px; */bottom: 0px;left: 0%;margin-left: -8px;cursor: s-resize;
}.resize-e {/* 右边句柄改为瘦高 */width: 3px;height: 100%;/* border-radius: 2px; */top: 0%;right: 0px;margin-top: -8px;cursor: e-resize;
}
</style>