Skip to content

useLayer

用于实现可调整各个方向大小以及位置的组合式函数

注意

目标元素需要开启绝对定位 absolute 或 fixed

使用示例

点我查看代码
vue
<script lang="ts" setup>
import { useLayer } from '@summeruse/ui'
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
export * from './types'
export * from './useLayer'
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>
}

Released under the ISC License.