useLayer
用于实现可调整各个方向大小以及位置的组合式函数
注意
目标元素需要开启绝对定位 absolute 或 fixed
使用示例
点我查看代码
vue
<script lang="ts" setup>
import { useLayer } from '@summeruse/layer'
import { NCheckbox, NForm, NFormItem, NInputNumber, NSelect } from 'naive-ui'
import { computed, ref } from 'vue'
const container = ref<HTMLElement>()
const header = ref<HTMLElement>()
const content = ref<HTMLElement>()
const parentRef = ref<HTMLElement>()
const minWidth = ref(100)
const minHeight = ref(100)
const maxWidth = ref(600)
const maxHeight = ref(150)
const disabledResize = ref(false)
const disabledDrag = ref(false)
const initRect = ref({
width: 300,
height: 100,
x: 850,
y: 500,
})
const isRatio = ref(true)
const ratio = computed(() => {
if (isRatio.value)
return initRect.value.width / initRect.value.height
else
return undefined
})
const parentValue = ref('body')
const parentOptions = [{
label: 'parent',
value: 'parent',
}, {
label: 'body',
value: 'body',
}]
const parent = computed(() => {
if (parentValue.value === 'parent') {
return parentRef.value
}
return document.body
})
const allowOverParent = ref(false)
const dragValue = ref('container')
const dragOptions = [{
label: 'container',
value: 'container',
}, {
label: 'header',
value: 'header',
}, {
label: 'content',
value: 'content',
}]
const dragElement = computed(() => {
if (dragValue.value === 'header') {
return header.value
}
else if (dragValue.value === 'content') {
return content.value
}
return container.value
})
const directions = ref({
'left': true,
'right': true,
'top': true,
'bottom': true,
'top-left': true,
'top-right': true,
'bottom-left': true,
'bottom-right': true,
})
const { rect } = useLayer(container, {
directions,
initRect,
minWidth,
minHeight,
maxWidth,
maxHeight,
ratio,
disabledResize,
disabledDrag,
parent,
allowOverParent,
dragElement,
})
const style = computed(() => {
return {
width: `${rect.value.width}px`,
height: `${rect.value.height}px`,
left: `${rect.value.x}px`,
top: `${rect.value.y}px`,
}
})
</script>
<template>
<Teleport to="body">
<div ref="parentRef" class="fixed w-800px h-350px bg-#abf5 z--1 top-400px left-350px pointer-events-none">
parent
</div>
</Teleport>
<NForm label-placement="left" label-width="120px" class="flex flex-wrap">
<NFormItem label="Disabled Resize">
<NCheckbox v-model:checked="disabledResize" />
</NFormItem>
<NFormItem label="Disabled Drag">
<NCheckbox v-model:checked="disabledDrag" />
</NFormItem>
<NFormItem label="Is Ratio">
<NCheckbox v-model:checked="isRatio" />
</NFormItem>
<NFormItem label="Over Parent">
<NCheckbox v-model:checked="allowOverParent" />
</NFormItem>
<NFormItem label="parent">
<NSelect v-model:value="parentValue" :options="parentOptions" />
</NFormItem>
<NFormItem label="drag Element">
<NSelect v-model:value="dragValue" :options="dragOptions" />
</NFormItem>
<NFormItem label="Resize">
<NCheckbox v-model:checked="directions.left">
left
</NCheckbox>
<NCheckbox v-model:checked="directions.right">
right
</NCheckbox>
<NCheckbox v-model:checked="directions.top">
top
</NCheckbox>
<NCheckbox v-model:checked="directions.bottom">
bottom
</NCheckbox>
</NFormItem>
<NFormItem label="directions">
<NCheckbox v-model:checked="directions['top-left']">
top-left
</NCheckbox>
<NCheckbox v-model:checked="directions['top-right']">
top-right
</NCheckbox>
<NCheckbox v-model:checked="directions['bottom-left']">
bottom-left
</NCheckbox>
<NCheckbox v-model:checked="directions['bottom-right']">
bottom-right
</NCheckbox>
</NFormItem>
<NFormItem label="Init Width">
<NInputNumber v-model:value="initRect.width" />
</NFormItem>
<NFormItem label="Init Height">
<NInputNumber v-model:value="initRect.height" />
</NFormItem>
<NFormItem label="Init X">
<NInputNumber v-model:value="initRect.x" />
</NFormItem>
<NFormItem label="Init Y">
<NInputNumber v-model:value="initRect.y" />
</NFormItem>
<NFormItem label="Min Width">
<NInputNumber v-model:value="minWidth" />
</NFormItem>
<NFormItem label="Min Height">
<NInputNumber v-model:value="minHeight" />
</NFormItem>
<NFormItem label="Max Width">
<NInputNumber v-model:value="maxWidth" />
</NFormItem>
<NFormItem label="Max Height">
<NInputNumber v-model:value="maxHeight" />
</NFormItem>
</NForm>
<Teleport to="body">
<div ref="container" class="fixed flex flex-col z-1000 bg-black text-#fff" :style>
<div ref="header" class="h-20px bg-#abf">
header
</div>
<div ref="content" class="h-100%">
{{ rect.width }} * {{ rect.height }}
</div>
</div>
</Teleport>
</template>源代码
点我查看代码
ts
import type { MaybeRefOrGetter } from 'vue'
import type {
Directions,
LayerOptions,
Rect,
ResizeDirection,
} from './types'
import {
tryOnScopeDispose,
unrefElement,
useEventListener,
useResizeObserver,
} from '@vueuse/core'
import { computed, ref, toValue, watch, watchEffect } from 'vue'
import { drag } from './drag'
import {
resizeBottom,
resizeBottomLeft,
resizeBottomRight,
resizeLeft,
resizeRight,
resizeTop,
resizeTopLeft,
resizeTopRight,
} from './resize'
import './index.scss'
const defaultOptions = {
initRect: {
width: 200,
height: 200,
x: 0,
y: 0,
},
}
const defaultDirections: Directions = {
'left': true,
'right': true,
'top': true,
'bottom': true,
'top-left': true,
'top-right': true,
'bottom-left': true,
'bottom-right': true,
}
function createResizeElement() {
const resizeElement = document.createElement('div')
resizeElement.classList.add('summer-use-resize')
const resizeElementChildren: {
[key in ResizeDirection]: HTMLElement
} = {} as any
// 遍历options,创建resize元素
for (const key in defaultDirections) {
if (Object.prototype.hasOwnProperty.call(defaultDirections, key)) {
const element = defaultDirections[key as keyof Directions]
if (element) {
const resizeElementChild = document.createElement('div')
resizeElementChild.classList.add(`summer-use-resize-${key}`)
resizeElementChildren[key as ResizeDirection] = resizeElementChild
resizeElement.append(resizeElementChild)
}
}
}
return {
resizeElement,
resizeElementChildren,
}
}
function getParentRect(el: Element) {
const rect = el.getBoundingClientRect()
// 获取页面的宽高
const { innerHeight, innerWidth } = window
return {
width: rect.width === 0 ? innerWidth : Math.min(innerWidth, rect.width),
height: rect.height === 0 ? innerHeight : Math.min(innerHeight, rect.height),
left: Math.max(0, rect.left),
top: Math.max(0, rect.top),
}
}
function getRect(initRect: MaybeRefOrGetter<Rect> | undefined) {
const _initRect = toValue(initRect)
if (!_initRect)
return defaultOptions.initRect
return {
..._initRect,
}
}
export function useLayer(target: MaybeRefOrGetter<HTMLElement | SVGElement | null | undefined>, options?: LayerOptions) {
const rect = ref(getRect(options?.initRect))
const { resizeElement, resizeElementChildren } = createResizeElement()
const _parentRect = ref((getParentRect(unrefElement(options?.parent) || document.body)))
const isResize = ref(false)
const dragElement = computed(() => {
return unrefElement(options?.dragElement) || unrefElement(target)
})
const parent = computed(() => {
return unrefElement(options?.parent) || document.body
})
const allowOverParent = computed(() => {
return toValue(options?.allowOverParent) || false
})
const parentRect = computed(() => {
return allowOverParent.value
? {
width: window.screen.width * 3,
height: window.screen.height * 3,
top: -window.screen.height,
left: -window.screen.width,
}
: _parentRect.value
})
const disabledResize = computed(() => {
return toValue(options?.disabledResize || false)
})
const disabledDrag = computed(() => {
return isResize.value || toValue(options?.disabledDrag || false)
})
const minWidth = computed(() => {
return (toValue(options?.minWidth) || 0)
})
const minHeight = computed(() => {
return (toValue(options?.minHeight) || 0)
})
const maxWidth = computed(() => {
const _maxWidth = (toValue(options?.maxWidth) || parentRect.value.width)
return Math.min(_maxWidth, parentRect.value.width)
})
const maxHeight = computed(() => {
const _maxHeight = (toValue(options?.maxHeight) || parentRect.value.height)
return Math.min(_maxHeight, parentRect.value.height)
})
// 左边界
const minX = computed(() => {
return parentRect.value.left
})
// 上边界
const minY = computed(() => {
return parentRect.value.top
})
// 下边界
const maxBottom = computed(() => {
return minY.value + parentRect.value.height
})
// 右边界
const maxRight = computed(() => {
return minX.value + parentRect.value.width
})
const directions = computed(() => {
return toValue(options?.directions) || defaultDirections
})
const ratio = computed(() => {
return toValue(options?.ratio) || undefined
})
const check = () => {
const _minX = minX.value
const _minY = minY.value
const _maxBottom = maxBottom.value
const _maxRight = maxRight.value
const _ratio = ratio.value
const _minWidth = _ratio ? Math.max(minWidth.value, minHeight.value * _ratio) : minWidth.value
const _maxWidth = _ratio ? Math.min(maxWidth.value, maxHeight.value * _ratio) : maxWidth.value
const _minHeight = _ratio ? Math.max(minHeight.value, minWidth.value / _ratio) : minHeight.value
const _maxHeight = _ratio ? Math.min(maxHeight.value, maxWidth.value / _ratio) : maxHeight.value
if (_ratio) {
if (rect.value.width / rect.value.height !== _ratio) {
rect.value.height = rect.value.width / _ratio
}
}
if (rect.value.width < _minWidth) {
rect.value.width = _minWidth
if (_ratio) {
rect.value.height = _minWidth / _ratio
}
}
if (rect.value.height < _minHeight) {
rect.value.height = _minHeight
if (_ratio) {
rect.value.width = _maxHeight * _ratio
}
}
if (rect.value.width > _maxWidth) {
rect.value.width = _maxWidth
if (_ratio) {
rect.value.height = _maxWidth / _ratio
}
}
if (rect.value.height > _maxHeight) {
rect.value.height = _maxHeight
if (_ratio) {
rect.value.width = _maxHeight * _ratio
}
}
if (rect.value.x < _minX) {
rect.value.x = _minX
}
if (rect.value.y < _minY) {
rect.value.y = _minY
}
if (rect.value.x + rect.value.width > _maxRight) {
rect.value.x = _maxRight - rect.value.width
}
if (rect.value.y + rect.value.height > _maxBottom) {
rect.value.y = _maxBottom - rect.value.height
}
}
check()
useEventListener(dragElement, 'mousedown', (e: MouseEvent) => {
if (disabledDrag.value)
return
drag({
e,
rect,
maxBottom,
maxRight,
minX,
minY,
})
})
const LeftElement = resizeElementChildren.left
useEventListener(LeftElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value.left === false)
return
resizeLeft({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const RightElement = resizeElementChildren.right
useEventListener(RightElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value.right === false)
return
resizeRight({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const TopElement = resizeElementChildren.top
useEventListener(TopElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value.top === false)
return
resizeTop({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const BottomElement = resizeElementChildren.bottom
useEventListener(BottomElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value.bottom === false)
return
resizeBottom({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const TopLeftElement = resizeElementChildren['top-left']
useEventListener(TopLeftElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value['top-left'] === false)
return
resizeTopLeft({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const TopRightElement = resizeElementChildren['top-right']
useEventListener(TopRightElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value['top-right'] === false)
return
resizeTopRight({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const BottomLeftElement = resizeElementChildren['bottom-left']
useEventListener(BottomLeftElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value['bottom-left'] === false)
return
resizeBottomLeft({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const BottomRightElement = resizeElementChildren['bottom-right']
useEventListener(BottomRightElement, 'mousedown', (e: MouseEvent) => {
if (disabledResize.value)
return
if (directions.value['bottom-right'] === false)
return
resizeBottomRight({
e,
rect,
minWidth,
minHeight,
ratio,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
isResize,
})
})
const close = () => {
const el = unrefElement(target)
if (resizeElement && el) {
el.removeChild(resizeElement)
}
}
const stopElement = watch(
() => unrefElement(target),
(el) => {
if (!el)
return
if (el.lastChild === resizeElement)
return
el.append(resizeElement)
},
)
watch(dragElement, (dragElement, oldDragElement) => {
oldDragElement?.classList.remove('summer-use-drag')
if (!disabledDrag.value) {
dragElement?.classList.add('summer-use-drag')
}
}, {
immediate: true,
})
watch(disabledDrag, (disabledDrag) => {
if (disabledDrag) {
dragElement.value?.classList.remove('summer-use-drag')
}
else {
dragElement.value?.classList.add('summer-use-drag')
}
}, {
immediate: true,
})
watchEffect(() => {
const directionList = Object.entries(directions.value)
const disabledDirection = directionList
.filter(([_, status]) => {
return !status
})
.map(([key]) => {
return `.summer-use-resize-${key}`
})
const openDirection = directionList
.filter(([_, status]) => {
return status
})
.map(([key]) => {
return `.summer-use-resize-${key}`
})
openDirection.forEach((key) => {
resizeElement.querySelector(key)?.classList.remove('summer-use-resize-direction-disabled')
})
disabledDirection.forEach((key) => {
resizeElement.querySelector(key)?.classList.add('summer-use-resize-direction-disabled')
})
})
watchEffect(() => {
if (disabledResize.value) {
resizeElement.classList.add('summer-use-resize-disabled')
}
else {
resizeElement.classList.remove('summer-use-resize-disabled')
}
})
const parentResizeObserver = useResizeObserver(parent, (entries) => {
if (!entries[0])
return
_parentRect.value = getParentRect(entries[0].target)
})
tryOnScopeDispose(() => {
parentResizeObserver.stop()
stopElement()
close()
})
return {
rect,
check,
}
}ts
import type { ComputedRef, Ref } from 'vue'
import type { Rect } from './types'
function initMousedownData({
e,
rect,
minWidth,
minHeight,
maxWidth,
maxHeight,
minX,
minY,
maxBottom,
maxRight,
ratio,
}:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
}) {
const startX = e.clientX
const startY = e.clientY
const x = rect.value.x
const y = rect.value.y
const height = rect.value.height
const width = rect.value.width
const startLeft = x + width
const startTop = y + height
const _minX = minX.value
const _maxBottom = maxBottom.value
const _maxRight = maxRight.value
const _minY = minY.value
const _ratio = ratio.value
const _minWidth = _ratio ? Math.max(minWidth.value, minHeight.value * _ratio) : minWidth.value
const _maxWidth = _ratio ? Math.min(maxWidth.value, maxHeight.value * _ratio) : maxWidth.value
const _minHeight = _ratio ? Math.max(minHeight.value, minWidth.value / _ratio) : minHeight.value
const _maxHeight = _ratio ? Math.min(maxHeight.value, maxWidth.value / _ratio) : maxHeight.value
return {
startX,
startY,
x,
y,
height,
width,
startLeft,
startTop,
_minX,
_maxBottom,
_maxRight,
_minY,
_ratio,
_minWidth,
_maxWidth,
_minHeight,
_maxHeight,
}
}
function _initResize({ isResize }:
{
isResize: Ref<boolean>
}) {
isResize.value = true
document.body.classList.add('summer-use-un-select')
const close = () => {
isResize.value = false
document.body.classList.remove('summer-use-un-select')
}
return {
close,
}
}
/** @Description: 左边拉伸 */
export function resizeLeft(
data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
},
) {
const {
startX,
x,
y,
height,
width,
startLeft,
_minX,
_maxBottom,
_minY,
_ratio,
_minWidth,
_maxWidth,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveX = e.clientX - startX
let nextWidth = width - moveX
let nextX = x + moveX
let nextHeight = height
let nextY = y
if (nextWidth > _maxWidth) {
nextWidth = _maxWidth
nextX = startLeft - nextWidth
}
if (nextX < _minX) {
nextX = _minX
nextWidth = width - (nextX - x)
}
if (nextWidth < _minWidth) {
nextWidth = _minWidth
nextX = startLeft - nextWidth
}
if (_ratio) {
nextHeight = nextWidth / _ratio
nextY = y - (nextHeight - height) / 2
if (nextY < _minY) {
nextY = _minY
nextHeight = (y - nextY) * 2 + height
nextWidth = nextHeight * _ratio
nextX = startLeft - nextWidth
}
if ((nextY + nextHeight) > _maxBottom) {
nextY = y - (_maxBottom - y - height)
nextHeight = _maxBottom - nextY
nextWidth = nextHeight * _ratio
nextX = startLeft - nextWidth
}
}
rect.value.height = nextHeight
rect.value.width = nextWidth
rect.value.x = nextX
rect.value.y = nextY
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
/** @Description: 右边拉伸 */
export function resizeRight(
data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
},
) {
const {
startX,
x,
y,
height,
width,
_maxRight,
_maxBottom,
_minY,
_ratio,
_minWidth,
_maxWidth,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveX = e.clientX - startX
let nextWidth = width + moveX
let nextHeight = height
let nextY = y
if (nextWidth > _maxWidth) {
nextWidth = _maxWidth
}
if (nextWidth < _minWidth) {
nextWidth = _minWidth
}
if (nextWidth + x > _maxRight) {
nextWidth = _maxRight - x
}
if (_ratio) {
nextHeight = nextWidth / _ratio
nextY = y - (nextHeight - height) / 2
if (nextY < _minY) {
nextY = _minY
nextHeight = (y - nextY) * 2 + height
nextWidth = nextHeight * _ratio
}
if ((nextY + nextHeight) > _maxBottom) {
nextY = y - (_maxBottom - y - height)
nextHeight = _maxBottom - nextY
nextWidth = nextHeight * _ratio
}
}
rect.value.height = nextHeight
rect.value.width = nextWidth
rect.value.y = nextY
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
/** @Description: 上边拉伸 */
export function resizeTop(data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
}) {
const {
startY,
x,
y,
height,
width,
startLeft,
startTop,
_minX,
_maxRight,
_minY,
_ratio,
_minHeight,
_maxHeight,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveY = e.clientY - startY
let nextHeight = height - moveY
let nextY = y + moveY
let nextWidth = width
let nextX = x
if (nextHeight + nextY >= startTop) {
nextHeight = Math.max(nextHeight, _minHeight)
nextY = startTop - nextHeight
nextY = Math.min(nextY, startTop)
}
if (nextY < _minY) {
nextY = _minY
nextHeight = startTop - nextY
}
if (nextHeight > _maxHeight) {
nextHeight = Math.min(nextHeight, _maxHeight)
nextY = startTop - nextHeight
}
if (_ratio) {
nextWidth = nextHeight * _ratio
nextX = x - (nextWidth - width) / 2
if (nextX < _minX) {
nextX = _minX
nextWidth = (startLeft - _minX - width) * 2 + width
nextHeight = nextWidth / _ratio
nextY = startTop - nextHeight
}
if (nextX + nextWidth > _maxRight) {
nextWidth = (_maxRight - x - width) * 2 + width
nextX = _maxRight - nextWidth
nextHeight = nextWidth / _ratio
nextY = startTop - nextHeight
}
}
rect.value.height = nextHeight
rect.value.y = nextY
rect.value.width = nextWidth
rect.value.x = nextX
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
/** @Description: 下边拉伸 */
export function resizeBottom(data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
}) {
const {
startY,
x,
y,
height,
width,
startLeft,
_minX,
_maxBottom,
_maxRight,
_ratio,
_minHeight,
_maxHeight,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveY = e.clientY - startY
let nextHeight = height + moveY
let nextWidth = width
let nextX = x
if (nextHeight > _maxHeight) {
nextHeight = Math.min(nextHeight, _maxHeight)
}
if (nextHeight < _minHeight) {
nextHeight = Math.max(nextHeight, _minHeight)
}
if (nextHeight + y >= _maxBottom) {
nextHeight = _maxBottom - y
}
if (_ratio) {
nextWidth = nextHeight * _ratio
nextX = x - (nextWidth - width) / 2
if (nextX < _minX) {
nextX = _minX
nextWidth = (startLeft - _minX - width) * 2 + width
nextHeight = nextWidth / _ratio
}
if (nextX + nextWidth > _maxRight) {
nextWidth = (_maxRight - x - width) * 2 + width
nextX = _maxRight - nextWidth
nextHeight = nextWidth / _ratio
}
}
rect.value.height = nextHeight
rect.value.width = nextWidth
rect.value.x = nextX
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
/* @Description: 左上角拉伸 */
export function resizeTopLeft(data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
}) {
const {
startX,
startY,
x,
y,
height,
width,
startTop,
startLeft,
_maxWidth,
_minWidth,
_maxRight,
_minX,
_minY,
_ratio,
_minHeight,
_maxHeight,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveX = e.clientX - startX
const moveY = e.clientY - startY
let nextWidth = width - moveX
let nextHeight = height - moveY
let nextY = y + moveY
let nextX = x + moveX
if (nextWidth > _maxWidth) {
nextWidth = _maxWidth
nextX = startLeft - nextWidth
}
if (nextWidth < _minWidth) {
nextWidth = _minWidth
nextX = startLeft - nextWidth
}
if (nextHeight + nextY >= startTop) {
nextHeight = Math.max(nextHeight, _minHeight)
nextY = startTop - nextHeight
nextY = Math.min(nextY, startTop)
}
if (nextY < _minY) {
nextY = _minY
nextHeight = startTop - nextY
}
if (nextHeight > _maxHeight) {
nextHeight = Math.min(nextHeight, _maxHeight)
nextY = startTop - nextHeight
}
if (_ratio) {
const _nextWidth = nextHeight * _ratio
nextWidth = Math.max(_nextWidth, nextWidth)
nextHeight = nextWidth / _ratio
nextY = startTop - nextHeight
nextX = startLeft - nextWidth
if (nextWidth + x > _maxRight) {
nextWidth = _maxRight - x
nextHeight = nextWidth / _ratio
nextY = startTop - nextHeight
}
if (nextY < _minY) {
nextY = _minY
nextHeight = startTop - nextY
nextWidth = nextHeight * _ratio
nextX = startLeft - nextWidth
}
if (nextX < _minX) {
nextX = _minX
nextWidth = (startLeft - _minX - width) + width
nextHeight = nextWidth / _ratio
nextY = startTop - nextHeight
}
}
rect.value.height = nextHeight
rect.value.width = nextWidth
rect.value.x = nextX
rect.value.y = nextY
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
/** @Description: 右上角拉伸 */
export function resizeTopRight(data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
}) {
const {
startX,
startY,
x,
y,
height,
width,
startTop,
_maxWidth,
_minWidth,
_maxRight,
_minY,
_ratio,
_minHeight,
_maxHeight,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveX = e.clientX - startX
const moveY = e.clientY - startY
let nextWidth = width + moveX
let nextHeight = height - moveY
let nextY = y + moveY
if (nextWidth > _maxWidth) {
nextWidth = _maxWidth
}
if (nextWidth < _minWidth) {
nextWidth = _minWidth
}
if (nextWidth + x > _maxRight) {
nextWidth = _maxRight - x
}
if (nextHeight + nextY >= startTop) {
nextHeight = Math.max(nextHeight, _minHeight)
nextY = startTop - nextHeight
nextY = Math.min(nextY, startTop)
}
if (nextY < _minY) {
nextY = _minY
nextHeight = startTop - nextY
}
if (nextHeight > _maxHeight) {
nextHeight = Math.min(nextHeight, _maxHeight)
nextY = startTop - nextHeight
}
if (_ratio) {
const _nextWidth = nextHeight * _ratio
nextWidth = Math.max(_nextWidth, nextWidth)
nextHeight = nextWidth / _ratio
nextY = startTop - nextHeight
if (nextWidth + x > _maxRight) {
nextWidth = _maxRight - x
nextHeight = nextWidth / _ratio
nextY = startTop - nextHeight
}
if (nextY < _minY) {
nextY = _minY
nextHeight = startTop - nextY
nextWidth = nextHeight * _ratio
}
}
rect.value.height = nextHeight
rect.value.width = nextWidth
rect.value.y = nextY
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
/** @Description: 左下角拉伸 */
export function resizeBottomLeft(data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
}) {
const {
startX,
startY,
x,
y,
height,
width,
startLeft,
_maxWidth,
_minWidth,
_maxBottom,
_minX,
_ratio,
_minHeight,
_maxHeight,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveX = e.clientX - startX
const moveY = e.clientY - startY
let nextWidth = width - moveX
let nextHeight = height + moveY
const nextY = y
let nextX = x + moveX
if (nextWidth > _maxWidth) {
nextWidth = _maxWidth
nextX = startLeft - nextWidth
}
if (nextWidth < _minWidth) {
nextWidth = _minWidth
nextX = startLeft - nextWidth
}
if (nextHeight < _minHeight) {
nextHeight = _minHeight
}
if (nextHeight > _maxHeight) {
nextHeight = Math.min(nextHeight, _maxHeight)
}
if (nextHeight + y > _maxBottom) {
nextHeight = _maxBottom - y
}
if (_ratio) {
const _nextWidth = nextHeight * _ratio
nextWidth = Math.max(_nextWidth, nextWidth)
nextHeight = nextWidth / _ratio
nextX = startLeft - nextWidth
if (nextHeight + y > _maxBottom) {
nextHeight = _maxBottom - y
nextWidth = nextHeight * _ratio
nextX = startLeft - nextWidth
}
if (nextX < _minX) {
nextX = _minX
nextWidth = (startLeft - _minX - width) + width
nextHeight = nextWidth / _ratio
nextX = startLeft - nextWidth
}
}
rect.value.height = nextHeight
rect.value.width = nextWidth
rect.value.y = nextY
rect.value.x = nextX
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
/** @Description: 右下角拉伸 */
export function resizeBottomRight(data:
{
e: MouseEvent
rect: Ref<Rect>
minWidth: Ref<number>
minHeight: Ref<number>
maxWidth: Ref<number>
maxHeight: Ref<number>
minX: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
maxRight: Ref<number>
ratio: ComputedRef<number | undefined>
isResize: Ref<boolean>
}) {
const {
startX,
startY,
x,
y,
height,
width,
_maxRight,
_maxWidth,
_minWidth,
_maxBottom,
_ratio,
_minHeight,
_maxHeight,
} = initMousedownData(data)
const { isResize, rect } = data
const { close } = _initResize({ isResize })
const mouseMoveHandler = (e: MouseEvent) => {
const moveX = e.clientX - startX
const moveY = e.clientY - startY
let nextWidth = width + moveX
let nextHeight = height + moveY
const nextY = y
if (nextWidth > _maxWidth) {
nextWidth = _maxWidth
}
if (nextWidth < _minWidth) {
nextWidth = _minWidth
}
if (nextHeight < _minHeight) {
nextHeight = _minHeight
}
if (nextHeight > _maxHeight) {
nextHeight = Math.min(nextHeight, _maxHeight)
}
if (nextHeight + y > _maxBottom) {
nextHeight = _maxBottom - y
}
if (nextWidth + x > _maxRight) {
nextWidth = _maxRight - x
}
if (_ratio) {
const _nextWidth = nextHeight * _ratio
nextWidth = Math.max(_nextWidth, nextWidth)
nextHeight = nextWidth / _ratio
if (nextHeight + y > _maxBottom) {
nextHeight = _maxBottom - y
nextWidth = nextHeight * _ratio
}
if (nextWidth + x > _maxRight) {
nextWidth = _maxRight - x
nextHeight = nextWidth / _ratio
}
}
rect.value.height = nextHeight
rect.value.width = nextWidth
rect.value.y = nextY
}
const mouseUpHandler = () => {
close()
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}ts
import type { Ref } from 'vue'
import type { Rect } from './types'
export function drag(
{ e, rect, minX, maxRight, minY, maxBottom }: {
e: MouseEvent
rect: Ref<Rect>
minX: Ref<number>
maxRight: Ref<number>
minY: Ref<number>
maxBottom: Ref<number>
},
) {
const startX = e.clientX
const startY = e.clientY
const startLeft = rect.value.x
const startTop = rect.value.y
const width = rect.value.width
const height = rect.value.height
document.body.classList.add('summer-use-un-select')
const mouseMoveHandler = (e: MouseEvent) => {
let x = e.clientX - startX + startLeft
let y = e.clientY - startY + startTop
if (x < minX.value) {
x = minX.value
}
if (x > maxRight.value - width) {
x = maxRight.value - width
}
if (y < minY.value) {
y = minY.value
}
if (y > maxBottom.value - height) {
y = maxBottom.value - height
}
rect.value.x = x
rect.value.y = y
}
const mouseUpHandler = () => {
document.body.classList.remove('summer-use-un-select')
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}scss
.summer-use-resize {
width: 100%;
height: 100%;
position: absolute;
pointer-events: none;
left: 0;
top: 0;
* {
pointer-events: auto;
z-index: 99999;
}
.summer-use-resize-bottom-right {
position: absolute;
right: -5px;
bottom: -5px;
width: 10px;
height: 10px;
cursor: se-resize;
}
.summer-use-resize-bottom-left {
position: absolute;
left: -5px;
bottom: -5px;
width: 10px;
height: 10px;
cursor: sw-resize;
}
.summer-use-resize-top-right {
position: absolute;
right: -5px;
top: -5px;
width: 10px;
height: 10px;
cursor: ne-resize;
}
.summer-use-resize-top-left {
position: absolute;
left: -5px;
top: -5px;
width: 10px;
height: 10px;
cursor: nw-resize;
}
.summer-use-resize-top {
position: absolute;
left: 5px;
right: 5px;
top: -5px;
height: 10px;
cursor: n-resize;
}
.summer-use-resize-bottom {
position: absolute;
left: 5px;
right: 5px;
bottom: -5px;
height: 10px;
cursor: s-resize;
}
.summer-use-resize-left {
position: absolute;
left: -5px;
top: 5px;
bottom: 5px;
width: 10px;
cursor: w-resize;
}
.summer-use-resize-right {
position: absolute;
right: -5px;
top: 5px;
bottom: 5px;
width: 10px;
cursor: e-resize;
}
}
.summer-use-resize-disabled {
* {
pointer-events: none;
}
}
.summer-use-resize-direction-disabled{
pointer-events: none;
}
.summer-use-un-select {
user-select: none;
}
.summer-use-drag {
cursor: move;
}类型定义
点我查看代码
ts
import type { MaybeRefOrGetter } from 'vue'
export type ResizeDirection = 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
export interface Directions {
'left'?: boolean
'right'?: boolean
'top'?: boolean
'bottom'?: boolean
'top-left'?: boolean
'top-right'?: boolean
'bottom-left'?: boolean
'bottom-right'?: boolean
}
export interface Rect {
height: number
width: number
x: number
y: number
}
export interface LayerOptions {
// 方向
directions?: MaybeRefOrGetter<Directions | undefined>
// 初始位置
initRect?: MaybeRefOrGetter<Rect>
// 拖动元素
dragElement?: MaybeRefOrGetter<HTMLElement | undefined>
// 禁止拉伸
disabledResize?: MaybeRefOrGetter<boolean>
// 禁止拖动
disabledDrag?: MaybeRefOrGetter<boolean>
// 最小宽度
minWidth?: MaybeRefOrGetter<number | undefined>
// 最小高度
minHeight?: MaybeRefOrGetter<number | undefined>
// 最大宽度
maxWidth?: MaybeRefOrGetter<number | undefined>
// 最大高度
maxHeight?: MaybeRefOrGetter<number | undefined>
// 宽高比
ratio?: MaybeRefOrGetter<number | undefined>
// 限位元素
parent?: MaybeRefOrGetter<HTMLElement | undefined>
// 允许拉伸到父元素外面
allowOverParent?: MaybeRefOrGetter<boolean>
}