From 1729f82ea0e294ee2ea32e235760e78810fbe8e3 Mon Sep 17 00:00:00 2001 From: wuming230 <1819845645@qq.com> Date: Wed, 19 Mar 2025 16:10:36 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=BC=A9=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/src/@types/type-zoom.ts | 52 --- .../.vitepress/src/components/ImgZoomDrag.vue | 215 ++++++++-- .../.vitepress/src/composables/useZoomDrag.ts | 369 ------------------ docs/.vitepress/src/layouts/LayoutDoc.vue | 18 +- 4 files changed, 180 insertions(+), 474 deletions(-) delete mode 100644 docs/.vitepress/src/@types/type-zoom.ts delete mode 100644 docs/.vitepress/src/composables/useZoomDrag.ts diff --git a/docs/.vitepress/src/@types/type-zoom.ts b/docs/.vitepress/src/@types/type-zoom.ts deleted file mode 100644 index 2cbf72e..0000000 --- a/docs/.vitepress/src/@types/type-zoom.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { type Ref, type ComputedRef } from 'vue'; - -export interface ZoomDragSizeT { - width: number; - height: number; - left: number; - top: number; -} - -export type ZoomDragMethodsT = { - fitSize: (animate?: boolean) => void; - focus: (dom: HTMLElement, padding: number) => void; -}; - -export type useZoomDragOptionsT = { - /** - * 容器区域 - */ - board: Ref | ComputedRef; - /** - * 目标区域 - */ - target?: Ref | ComputedRef; - /** - * 目标变化事件 - */ - onTargetChange?: (info: ZoomDragSizeT & { zoom: number }, methods: ZoomDragMethodsT) => void; - /** - * 容器大小变化事件 - */ - onBoardChange?: (info: ZoomDragSizeT, methods: ZoomDragMethodsT) => void; - /** - * 初始化完成事件 - */ - onReady?: () => void; - /** - * 放大缩小速率(默认 0.1) - */ - zoomSpeed?: number; - /** - * 最高放大倍速(默认 3) - */ - zoomMax?: number; - /** - * 最低缩小倍速(默认 0.2) - */ - zoomMin?: number; - /** - * 内边距 - */ - padding?: [number, number, number, number]; -}; diff --git a/docs/.vitepress/src/components/ImgZoomDrag.vue b/docs/.vitepress/src/components/ImgZoomDrag.vue index f4672e6..d5e6165 100644 --- a/docs/.vitepress/src/components/ImgZoomDrag.vue +++ b/docs/.vitepress/src/components/ImgZoomDrag.vue @@ -1,48 +1,191 @@ +const left = ref(0); +const top = ref(0); + +const zoomRef = ref(); +const targetRef = ref(); + +// 状态值 +const state = reactive({ + lastLeft: 0, // 上次的left + lastTop: 0, // 上次的top + + startX: 0, // 长按开始坐标x + startY: 0, // 长按开始坐标y + isDown: false, // 鼠标是否长按中 + moveX: 0, // 长按移动坐标x + moveY: 0, // 长按移动坐标y +}); + +const zoomBoxStyle = ref({ + width: props.width, + height: props.height, + left: 0, + top: 0, +}); + +const targetBoxStyle = ref({ + width: 0, + height: 0, + left: 0, + top: 0, + zoom: 1, +}); + +// 获取元素大小 +async function getSize(ele: HTMLElement | undefined): Promise<[number, number, number, number]> { + function inner(resolve: (res: [number, number, number, number]) => void) { + if (ele) { + const { left, top } = ele.getBoundingClientRect(); + const [width, height] = [ele.clientWidth, ele.clientHeight]; + resolve([Math.round(width), Math.round(height), Math.round(left), Math.round(top)]); + } else { + resolve([0, 0, 0, 0]); + } + } + return new Promise((resolve) => { + if (ele) { + if (ele instanceof HTMLImageElement) { + if (ele.complete) { + inner(resolve); + } else { + ele.onload = () => { + inner(resolve); + }; + } + } else { + inner(resolve); + } + } else { + resolve([0, 0, 0, 0]); + } + }); +} +const changeZoom = async (val: number) => { + scale.value += val; + scale.value = Math.round(scale.value * 1000) / 1000; + + const [targetWidth, targetHeight, targetLeft, targetTop] = await getSize(targetRef.value); + + targetBoxStyle.value.width = Math.round(targetWidth * scale.value); + targetBoxStyle.value.height = Math.round(targetHeight * scale.value); + + zoomBoxStyle.value.width = Math.round(targetWidth * scale.value); + zoomBoxStyle.value.height = Math.round(targetHeight * scale.value); +}; +const onWheel = (el: WheelEvent) => { + // 放大 + if (el.deltaY < 0) { + if (scale.value <= maxScale) { + changeZoom(wheelRatio); + } + } else { + // 缩小 + if (scale.value >= minScale + wheelRatio) { + changeZoom(-wheelRatio); + } + } +}; + +const onMouseStart = async (e: MouseEvent) => { + if (e.button === 0) { + state.startX = e.clientX; + state.startY = e.clientY; + state.isDown = true; + } +}; +const onMousemove = async (e: MouseEvent) => { + if (state.isDown) { + state.moveX = e.clientX; + state.moveY = e.clientY; + + left.value = Math.round(state.lastLeft + state.moveX - state.startX); + top.value = Math.round(state.lastTop + state.moveY - state.startY); + } +}; +const onMouseEnd = () => { + state.isDown = false; + state.lastLeft = left.value; + state.lastTop = top.value; +}; + +onMounted(() => { + const resizeObserver = new ResizeObserver(async () => { + if (!targetRef.value) { + return; + } + targetRef.value.addEventListener('wheel', onWheel); + targetRef.value.addEventListener('mousedown', onMouseStart); + targetRef.value.addEventListener('mousemove', onMousemove); + targetRef.value.addEventListener('mouseup', onMouseEnd); + targetRef.value.addEventListener('mouseleave', onMouseEnd); + }); + resizeObserver.observe(targetRef.value); +}); + +onUnmounted(() => { + if (targetRef.value) { + targetRef.value.removeEventListener('wheel', onWheel); + targetRef.value.removeEventListener('mousedown', onMouseStart); + targetRef.value.removeEventListener('mousemove', onMousemove); + targetRef.value.removeEventListener('mouseup', onMouseEnd); + targetRef.value.removeEventListener('mouseleave', onMouseEnd); + } +}); + + + diff --git a/docs/.vitepress/src/composables/useZoomDrag.ts b/docs/.vitepress/src/composables/useZoomDrag.ts deleted file mode 100644 index 239a613..0000000 --- a/docs/.vitepress/src/composables/useZoomDrag.ts +++ /dev/null @@ -1,369 +0,0 @@ -import { ref, type Ref, reactive, watch } from 'vue'; - -import type { useZoomDragOptionsT, ZoomDragMethodsT, ZoomDragSizeT } from '@/@types/type-zoom'; - -const DefaultOptions: Partial = { - zoomSpeed: 0.1, - zoomMax: 3, - zoomMin: 1, - padding: [0, 0, 0, 0], -}; - -export default function useZoomDrag(opts: useZoomDragOptionsT): { - target: Ref; - board: Ref; - methods: ZoomDragMethodsT; -} { - const options = { ...DefaultOptions, ...opts }; - - // 状态值 - const state = reactive({ - lastLeft: 0, // 上次的left - lastTop: 0, // 上次的top - overX: 0, // 鼠标移动坐标x - overY: 0, // 鼠标移动坐标y - boardLeft: 0, // 容器区域距离浏览器左边距离 - boardTop: 0, // 容器区域距离浏览器上边距离 - - startX: 0, // 长按开始坐标x - startY: 0, // 长按开始坐标y - isDown: false, // 鼠标是否长按中 - moveX: 0, // 长按移动坐标x - moveY: 0, // 长按移动坐标y - - boardWidth: 0, // 容器区域宽 - boardHeight: 0, // 容器区高 - targetWidth: 0, // 目标区域宽 - targetHeight: 0, // 目标区域高 - }); - - const zoom = ref(0); - const left = ref(0); - const top = ref(0); - - const targetInfoRef: Ref = ref({ - width: 0, - height: 0, - left: 0, - top: 0, - zoom: 1, - }); - const boardInfoRef: Ref = ref({ - width: 0, - height: 0, - left: 0, - top: 0, - }); - - function getTarget() { - if (options.target === void 0) { - if (options.board.value !== void 0) { - return options.board.value.children[0] as HTMLElement; - } - } - - return options.target?.value; - } - - function updateTargetStyle() { - const target = getTarget(); - - if (target) { - target.style.transform = `scale(${zoom.value + 1})`; - target.style.left = `${left.value}px`; - target.style.top = `${top.value}px`; - - targetInfoRef.value = { - width: Math.round(state.targetWidth * (zoom.value + 1)), - height: Math.round(state.targetHeight * (zoom.value + 1)), - left: left.value, - top: top.value, - zoom: zoom.value, - }; - - options.onTargetChange && options.onTargetChange(targetInfoRef.value, { fitSize, focus }); - } - } - - // 自适应大小 - async function fitSize(animate = false) { - const target = getTarget(); - if (options.board.value && target) { - // 记录容器、目标大小 - [state.boardWidth, state.boardHeight, state.boardLeft, state.boardTop] = await getSize(options.board.value); - [state.targetWidth, state.targetHeight] = await getSize(target); - // - - if (animate) { - target.style.transition = 'all 0.3s ease-in'; - } - - const rateBoard = state.boardWidth / state.boardHeight; - const rateTarget = state.targetWidth / state.targetHeight; - - const [boardWidth, boardHeight] = [ - state.boardWidth - (options.padding?.[1] ?? 0) - (options.padding?.[3] ?? 0), - state.boardHeight - (options.padding?.[0] ?? 0) - (options.padding?.[2] ?? 0), - ]; - - if (rateBoard > rateTarget) { - zoom.value = boardHeight / state.targetHeight - 1; - } else if (rateBoard < rateTarget) { - zoom.value = boardWidth / state.targetWidth - 1; - } - - zoom.value = Math.floor(zoom.value * 1000) / 1000; - - if (zoom.value > 0) { - zoom.value = 0; - } - left.value = Math.round((boardWidth - state.targetWidth * (1 + zoom.value)) / 2 + (options.padding?.[3] ?? 0)); - top.value = Math.round((boardHeight - state.targetHeight * (1 + zoom.value)) / 2 + (options.padding?.[0] ?? 0)); - state.lastLeft = left.value; - state.lastTop = top.value; - - updateTargetStyle(); - - if (animate) { - setTimeout(() => { - if (target) { - target.style.transition = 'none'; - } - }, 300); - } - } - } - - // 聚焦目标 - async function focus(dom: HTMLElement, padding: number = 4) { - const target = getTarget(); - if (options.board.value && target) { - [state.boardWidth, state.boardHeight, state.boardLeft, state.boardTop] = await getSize(options.board.value); - [state.targetWidth, state.targetHeight] = await getSize(target); - - const [domWidth, domHeight, domLeft, domTop] = [dom.offsetWidth + padding * 2, dom.offsetHeight + padding * 2, dom.offsetLeft, dom.offsetTop]; - - const rateBoard = state.boardWidth / state.boardHeight; - const rateDom = domWidth / domHeight; - - const [boardWidth, boardHeight] = [ - state.boardWidth - (options.padding?.[1] ?? 0) - (options.padding?.[3] ?? 0), - state.boardHeight - (options.padding?.[0] ?? 0) - (options.padding?.[2] ?? 0), - ]; - - if (rateBoard > rateDom) { - zoom.value = boardHeight / domHeight - 1; - } else if (rateBoard < rateDom) { - zoom.value = boardWidth / domWidth - 1; - } - - zoom.value = Math.floor(zoom.value * 1000) / 1000; - - left.value = Math.round( - (boardWidth - domWidth * (1 + zoom.value)) / 2 + (options.padding?.[3] ?? 0) - domLeft * (1 + zoom.value) + padding * (1 + zoom.value) - ); - top.value = Math.round( - (boardHeight - domHeight * (1 + zoom.value)) / 2 + (options.padding?.[0] ?? 0) - domTop * (1 + zoom.value) + padding * (1 + zoom.value) - ); - state.lastLeft = left.value; - state.lastTop = top.value; - - updateTargetStyle(); - } - } - - // 放大缩小 - function changeZoom(value: number) { - const target = getTarget(); - - if (options.board.value && target) { - const lastTargetWidth = state.targetWidth * (1 + zoom.value); - const lastTargetHeight = state.targetHeight * (1 + zoom.value); - const lastOffsetX = state.overX - state.lastLeft - state.boardLeft; - const lastOffsetY = state.overY - state.lastTop - state.boardTop; - const rateX = lastOffsetX / lastTargetWidth; - const rateY = lastOffsetY / lastTargetHeight; - - zoom.value += value; - zoom.value = Math.round(zoom.value * 1000) / 1000; - - const newTargetWidth = state.targetWidth * (1 + zoom.value); - const newTargetHeight = state.targetHeight * (1 + zoom.value); - - const newSpanX = newTargetWidth * rateX - lastOffsetX; - const newSpanY = newTargetHeight * rateY - lastOffsetY; - - left.value = Math.round(state.lastLeft - newSpanX); - top.value = Math.round(state.lastTop - newSpanY); - state.lastLeft = left.value; - state.lastTop = top.value; - - updateTargetStyle(); - } - } - - // 获取元素大小 - async function getSize(ele: HTMLElement | undefined): Promise<[number, number, number, number]> { - function inner(resolve: (res: [number, number, number, number]) => void) { - if (ele) { - const { left, top } = ele.getBoundingClientRect(); - const [width, height] = [ele.clientWidth, ele.clientHeight]; - resolve([width, height, left, top]); - } else { - resolve([0, 0, 0, 0]); - } - } - return new Promise((resolve) => { - if (ele) { - if (ele instanceof HTMLImageElement) { - if (ele.complete) { - inner(resolve); - } else { - ele.onload = () => { - inner(resolve); - }; - } - } else { - inner(resolve); - } - } else { - resolve([0, 0, 0, 0]); - } - }); - } - - const eventHandlers = { - zoom: (e: WheelEvent) => { - if (e.deltaY < 0) { - if (zoom.value <= options.zoomMax! - options.zoomSpeed!) { - changeZoom(options.zoomSpeed!); - } - } else if (e.deltaY > 0) { - if (zoom.value >= options.zoomMin! - 0.5 + options.zoomSpeed!) { - changeZoom(-options.zoomSpeed!); - } - } - - e.preventDefault(); - }, - contextmenu: (e: MouseEvent) => { - e.preventDefault(); - }, - dragStart: (e: MouseEvent) => { - if (e.button === 0) { - state.startX = e.clientX; - state.startY = e.clientY; - state.isDown = true; - } - }, - dragMove: (e: MouseEvent) => { - state.overX = e.clientX; - state.overY = e.clientY; - if (state.isDown) { - state.moveX = e.clientX; - state.moveY = e.clientY; - left.value = Math.round(state.lastLeft + state.moveX - state.startX); - top.value = Math.round(state.lastTop + state.moveY - state.startY); - - updateTargetStyle(); - } - }, - dragEnd: () => { - state.isDown = false; - state.lastLeft = left.value; - state.lastTop = top.value; - }, - }; - - // 事件处理 - function eventHandle() { - const target = getTarget(); - - if (options.board.value && target) { - options.board.value.addEventListener('wheel', eventHandlers.zoom); - // - options.board.value.addEventListener('mousedown', eventHandlers.dragStart); - options.board.value.addEventListener('mousemove', eventHandlers.dragMove); - options.board.value.addEventListener('mouseup', eventHandlers.dragEnd); - options.board.value.addEventListener('mouseleave', eventHandlers.dragEnd); - // - options.board.value.addEventListener('contextmenu', eventHandlers.contextmenu); - // - const resizeObserver = new ResizeObserver(async () => { - [state.boardWidth, state.boardHeight, state.boardLeft, state.boardTop] = await getSize(options.board.value); - - boardInfoRef.value = { - width: state.boardWidth, - height: state.boardHeight, - left: state.boardLeft, - top: state.boardTop, - }; - - options.onBoardChange && options.onBoardChange(boardInfoRef.value, { fitSize, focus }); - }); - resizeObserver.observe(options.board.value); - } - } - - // 切换鼠标 cursor - watch( - () => state.isDown, - () => { - options.board.value && (options.board.value.style.cursor = state.isDown ? 'pointer' : 'default'); - } - ); - - // 容器区域必须样式 - function boardStyle() { - if (options.board.value) { - const boardComputedStyle = getComputedStyle(options.board.value); - options.board.value.style.overflow = 'hidden'; - options.board.value.style.userSelect = 'none'; - if (!['absolute', 'relative', 'fixed'].includes(boardComputedStyle.position)) { - options.board.value.style.position = 'relative'; - } - } - } - - // 目标区域必须样式 - function targetStyle() { - const target = getTarget(); - - if (target) { - target.style.position = 'absolute'; - target.style.transform = 'scale(1)'; - target.style.transformOrigin = '0 0'; - target.style.userSelect = 'none'; - target.draggable = false; - } - } - - watch( - () => [options.board.value, options.target?.value], - async () => { - const target = getTarget(); - - if (options.board.value && target) { - // 必须样式 - boardStyle(); - targetStyle(); - // 事件控制 - eventHandle(); - // - await fitSize(); - - // 初始化完成 - options.onReady && options.onReady(); - } - }, - { - immediate: true, - } - ); - - return { - target: targetInfoRef, - board: boardInfoRef, - methods: { fitSize, focus }, - }; -} diff --git a/docs/.vitepress/src/layouts/LayoutDoc.vue b/docs/.vitepress/src/layouts/LayoutDoc.vue index f4b31b8..2161db7 100644 --- a/docs/.vitepress/src/layouts/LayoutDoc.vue +++ b/docs/.vitepress/src/layouts/LayoutDoc.vue @@ -592,23 +592,7 @@ const readyVisible = ref(false);
- - - +
-- Gitee