npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue-movable-box

v1.1.7

Published

A Vue 3 component for draggable and resizable containers

Readme

VueMovableBox

✨ 一个功能强大的 Vue 3 拖拽和调整大小组件

English | 中文

npm version License Vue 3 TypeScript Build

一个高性能、功能丰富的 Vue 3 可拖拽可调整大小的容器组件,适用于构建仪表盘、编辑器、可视化配置等场景。

特性

  • 🖱️ 拖拽移动 - 自由拖拽元素位置
  • 📐 调整大小 - 8个方向调整元素尺寸
  • 📱 移动端支持 - 完整的触摸事件支持
  • 🔒 比例锁定 - 保持宽高比缩放
  • 🎨 自定义主题 - 灵活的主题配置
  • 📏 单位支持 - 支持 px 和 % 单位
  • 🌍 边界限制 - 限制在父元素内移动
  • 🧲 元素吸附 - 边缘、中心对齐与内置辅助线
  • 💥 碰撞控制 - 检测重叠或阻止拖拽与缩放碰撞
  • 完整事件 - 丰富的事件回调
  • 🔧 TypeScript - 完整的类型支持
  • 🚀 高性能 - 使用 RAF 优化,硬件加速
  • 🧪 可测试 - 清晰的事件和 API 设计

安装

pnpm add vue-movable-box
# 或
npm install vue-movable-box

快速开始

<script setup>
import { ref } from 'vue'
import { MovableBox } from 'vue-movable-box'
import 'vue-movable-box/style.css'

const boxConfig = ref({
  left: 100,
  top: 100,
  width: 200,
  height: 150,
  zIndex: 1
})
</script>

<template>
  <MovableBox v-model="boxConfig">
    <div class="content">
      拖拽内容区域
    </div>
  </MovableBox>
</template>

在线演示

# 克隆项目后
pnpm install
pnpm dev

访问 http://localhost:5173 查看交互式演示。

API

Props

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | v-model | MovableBoxRect | 必填 | 绑定位置和尺寸 | | theme | string | #409EFD | 主题色(激活状态边框色) | | inActiveColor | string | #666666 | 失活状态边框颜色 | | unitType | 'px' \| '%' | 'px' | 尺寸单位类型 | | scale | number \| string | 1 | 组件整体缩放比例 | | isKeepDecimals | boolean | false | 是否保留小数 | | decimalPlaces | number | 2 | 保留小数位数 | | draggable | boolean | true | 是否可拖拽 | | resizable | boolean | true | 是否可调整大小(推荐名称) | | resizeable | boolean | true | resizable 的兼容旧别名,已废弃 | | limitAreaForParent | boolean | true | 是否限制在父元素区域内 | | limitAreaClass | string | - | 自定义限制区域的 CSS 选择器 | | maxWidth | number \| string | - | 最大宽度 | | maxHeight | number \| string | - | 最大高度 | | minWidth | number \| string | 0 | 最小宽度 | | minHeight | number \| string | 0 | 最小高度 | | ratioLock | boolean | false | 调整大小时是否锁定宽高比 | | active | boolean | false | 是否处于激活状态 | | disabled | boolean | false | 是否完全禁用 | | disabledUserSelect | boolean | true | 拖拽时是否禁止文本选择 | | initRect | boolean | false | 只读模式(仅展示位置尺寸) | | handles | HandlePosition[] | 全部8个 | 允许显示的调整手柄 | | 网格与吸附 | | | | | snapToGrid | boolean | false | 是否吸附到网格 | | gridSize | number | 20 | 网格大小(当前坐标单位) | | snapToElements | boolean | false | 吸附到 snapTargets 的边缘或中心 | | snapThreshold | number | 10 | 元素吸附阈值 | | snapTargets | SnapTarget[] | [] | 其他元素的矩形数据;调用方应排除自身 | | collisionEnabled | boolean | false | 对 snapTargets 启用碰撞检测 | | allowOverlap | boolean | false | 检测到碰撞时是否仍允许重叠 | | 方向控制 | | | | | dragDirections | string[] | ['top','bottom','left','right'] | 允许拖拽的方向 | | resizeDirections | string[] | 全部8个 | 允许调整的方向 | | 边界与边距 | | | | | edgeDistance | number | 0 | 四边统一边距 | | boundsMargin | Object | {top:0,right:0,bottom:0,left:0} | 每侧附加边距,与 edgeDistance 相加 | | 交互 | | | | | enableTransition | boolean | false | 启用过渡动画 | | keyboardEnabled | boolean | false | 启用键盘操作 | | keyboardStep | number | 1 | 键盘移动步长 |

HandlePosition 类型

type HandlePosition = 'tl' | 'tm' | 'tr' | 'ml' | 'mr' | 'bl' | 'bm' | 'br'
// tl: 左上, tm: 上中, tr: 右上
// ml: 左中, mr: 右中
// bl: 左下, bm: 下中, br: 右下

MovableBoxRect 类型

interface MovableBoxRect {
  left: number | string
  top: number | string
  width: number | string
  height: number | string
  zIndex?: number
}

Events

| 事件名 | 参数 | 说明 | |--------|------|------| | update:modelValue | (value: MovableBoxRect) | v-model 更新时触发 | | drag-start | (event: MouseEvent \| TouchEvent, value: MovableBoxRect) | 开始拖拽时触发 | | drag | (value: MovableBoxRect) | 拖拽过程中触发(节流) | | drag-stop | (event: MouseEvent \| TouchEvent, oldValue: MovableBoxRect, newValue: MovableBoxRect) | 停止拖拽时触发 | | resize-start | (event: MouseEvent \| TouchEvent, value: MovableBoxRect) | 开始调整大小时触发 | | resize | (value: MovableBoxRect) | 调整大小过程中触发(节流) | | resize-stop | (event: MouseEvent \| TouchEvent, oldValue: MovableBoxRect, newValue: MovableBoxRect) | 停止调整大小时触发 | | active | (value: MovableBoxRect) | 组件被激活时触发 | | inactive | (value: MovableBoxRect) | 组件失去激活时触发 | | disabled | (value: boolean) | 禁用状态变化时触发 | | dblclick | (event: MouseEvent) | 双击组件时触发 | | out-of-bounds | (direction: 'left' \| 'top' \| 'right' \| 'bottom') | 超出边界时触发 | | move | (value: MovableBoxRect) | drag 的兼容旧别名,已废弃 | | snap | (value: SnapEventPayload) | 吸附状态、吸附点或目标发生变化时触发 | | guides | (value: GuidesEventPayload) | 吸附目标或辅助线坐标发生变化时触发 | | collision | (value: CollisionEventPayload) | 进入、改变或离开碰撞状态时触发 |

交互处理顺序为:方向限制 → 网格吸附 → 元素吸附 → 边界限制 → 碰撞校验。高级事件只在状态变化时触发,不会在每个相同的拖拽帧重复触发。目标矩形、网格、阈值和边距均使用 unitType 对应的坐标单位;unitType="%" 时数值代表百分点。

interface SnapEventPayload {
  snapped: boolean
  point?: SnapPoint // 已废弃的单吸附点兼容字段
  points?: SnapPoint[]
  targetId?: string
  targetIds?: {
    horizontal?: string
    vertical?: string
  }
}

interface GuidesEventPayload {
  vertical: number[]
  horizontal: number[]
}

interface CollisionEventPayload {
  colliding: boolean
  direction?: 'left' | 'right' | 'top' | 'bottom'
  targetId?: string
}

双轴同时吸附时,targetId 保留为兼容旧用法的主要目标;targetIds.horizontaltargetIds.vertical 分别表示两个坐标轴选中的目标。

Methods

通过 ref 调用:

<template>
  <MovableBox ref="boxRef" v-model="config" />
</template>

<script setup>
const boxRef = ref()

// 获取当前配置
boxRef.value.getConfig()

// 设置位置
boxRef.value.setPosition(100, 100)

// 设置大小
boxRef.value.setSize(300, 200)

// 重置到初始位置
boxRef.value.reset()

// 激活组件
boxRef.value.activate()

// 停用组件
boxRef.value.deactivate()
</script>

Slots

| 插槽名 | 说明 | |--------|------| | default | 组件内容区域 |

高级用法

自定义主题色

<MovableBox 
  v-model="config"
  theme="#ff6b6b"
  inActiveColor="#ccc"
/>

使用百分比单位

<MovableBox 
  v-model="config"
  unit-type="%"
  :max-width="100"
  :max-height="100"
/>

锁定宽高比

<MovableBox 
  v-model="config"
  :ratio-lock="true"
/>

自定义调整手柄

<!-- 只显示右下角手柄 -->
<MovableBox 
  v-model="config"
  :handles="['br']"
/>

<!-- 显示四个角 -->
<MovableBox 
  v-model="config"
  :handles="['tl', 'tr', 'bl', 'br']"
/>

限制在指定区域内

<!-- 限制在父元素内(默认) -->
<MovableBox v-model="config" />

<!-- 限制在自定义区域 -->
<div class="custom-area">
  <MovableBox 
    v-model="config"
    limit-area-class=".custom-area"
  />
</div>

网格吸附

<MovableBox 
  v-model="config"
  :snap-to-grid="true"
  :grid-size="20"
/>

元素吸附与碰撞

<MovableBox
  v-model="current"
  :snap-to-elements="true"
  :collision-enabled="true"
  :allow-overlap="false"
  :snap-targets="otherBoxes"
  @snap="handleSnap"
  @collision="handleCollision"
/>

otherBoxes 中的每项包含 lefttopwidthheight 和可选 id,组件会自动显示对齐辅助线。边缘接触不算碰撞;关闭重叠时,拖拽或缩放保持在最后一个合法矩形,初始已重叠的元素只允许向总重叠面积减小的方向移动。多目标碰撞以重叠面积最大的目标决定 directiontargetId;开启 allowOverlap 会提交候选矩形,但仍会上报碰撞。

键盘控制

<MovableBox 
  v-model="config"
  :keyboard-enabled="true"
  :keyboard-step="5"
/>
<!-- 
  按方向键 ↑↓←→ 移动
  按 Escape 取消激活
-->

限制拖拽/调整方向

<!-- 只允许左右拖拽,禁止上下移动 -->
<MovableBox 
  v-model="config"
  :drag-directions="['left', 'right']"
/>

<!-- 只显示左右调整手柄 -->
<MovableBox 
  v-model="config"
  :resize-directions="['ml', 'mr']"
/>

边界边距

<MovableBox 
  v-model="config"
  :edge-distance="20"
  :bounds-margin="{ top: 10, right: 10, bottom: 10, left: 10 }"
/>

过渡动画

<MovableBox 
  v-model="config"
  :enable-transition="true"
/>

事件监听示例

<script setup>
const handleDragStart = (e, value) => {
  console.log('开始拖拽', value)
}

const handleDragStop = (e, oldVal, newVal) => {
  console.log('停止拖拽', { 旧位置: oldVal, 新位置: newVal })
}

const handleOutOfBounds = (direction) => {
  console.log('超出边界:', direction)
  // direction: 'left' | 'top' | 'right' | 'bottom'
}
</script>

<template>
  <MovableBox
    v-model="config"
    @drag-start="handleDragStart"
    @drag-stop="handleDragStop"
    @out-of-bounds="handleOutOfBounds"
  />
</template>

多组件协调

<script setup>
import { ref } from 'vue'

const boxes = ref([
  { id: 1, config: { left: 50, top: 50, width: 200, height: 150, zIndex: 1 }},
  { id: 2, config: { left: 300, top: 100, width: 200, height: 150, zIndex: 2 }}
])

const activeId = ref(null)

const handleActive = (box, rect) => {
  // 点击激活时更新 zIndex
  const maxZ = Math.max(...boxes.value.map(b => b.config.zIndex))
  box.config.zIndex = maxZ + 1
  activeId.value = box.id
}
</script>

<template>
  <div class="container">
    <MovableBox
      v-for="box in boxes"
      :key="box.id"
      v-model="box.config"
      :active="activeId === box.id"
      @active="() => handleActive(box, $event)"
    >
      Box {{ box.id }}
    </MovableBox>
  </div>
</template>

TypeScript

完整 TypeScript 类型支持:

import { 
  MovableBox, 
  type MovableBoxProps,
  type MovableBoxRect,
  type ExtendsMovableBox,
  type HandlesSet 
} from 'vue-movable-box'

// 使用类型
const config: MovableBoxRect = {
  left: 100,
  top: 100,
  width: 200,
  height: 150,
  zIndex: 1
}

浏览器支持

| 浏览器 | 最低版本 | |--------|----------| | Chrome | >= 88 | | Firefox | >= 85 | | Safari | >= 14 | | Edge | >= 88 | | iOS Safari | >= 14 | | Android Chrome | >= 88 |

项目结构

vue-movable-box/
├── src/
│   ├── index.ts                 # 入口文件
│   ├── types/
│   │   └── MovableBox.ts        # 类型定义
│   └── components/
│       └── MovableBox/
│           ├── MovableBox.vue   # 主组件
│           ├── style.scss       # 样式
│           └── utils.ts         # 工具函数
├── examples/                    # 示例代码
│   ├── App.vue                  # 完整演示
│   └── main.ts
├── lib/                         # 构建输出
├── package.json
├── vite.config.ts
└── tsconfig.json

相关链接

许可证

MIT License - 详见 LICENSE 文件

贡献

欢迎贡献代码!请阅读 CONTRIBUTING.md 了解如何参与开发。

  1. Fork 本仓库
  2. 创建特性分支 (git checkout -b feature/amazing-feature)
  3. 提交更改 (git commit -m 'Add amazing feature')
  4. 推送分支 (git push origin feature/amazing-feature)
  5. 开启 Pull Request

Made with ❤️ by News777