简介
Draggable 是一个现代化的 JavaScript 拖拽库,由 Shopify 开发,提供了强大而灵活的拖拽功能。它不仅支持基本的拖拽操作,还提供了丰富的插件系统、事件处理和自定义选项,可以轻松实现复杂的拖拽交互。
主要特性
- 🎯 模块化设计: 基于插件架构,按需加载功能
- 📱 触摸友好: 完美支持移动设备和触摸操作
- 🎨 高度可定制: 丰富的配置选项和样式控制
- 🔧 插件系统: 内置多种插件扩展功能
- 🌐 现代浏览器: 支持所有现代浏览器
- ♿ 可访问性: 内置键盘导航和屏幕阅读器支持
- 🚀 性能优化: 高效的事件处理和 DOM 操作
安装
# npm
npm install @shopify/draggable
# yarn
yarn add @shopify/draggable
# pnpm
pnpm add @shopify/draggableVue 3.js 基础使用示例
基本拖拽功能
<template>
  <div
    class="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 py-12 px-4"
  >
    <div class="max-w-md mx-auto">
      <h3 class="text-2xl font-bold text-gray-800 text-center mb-8">
        🎯 基本拖拽排序
      </h3>
      <div ref="dragContainer" class="space-y-4">
        <div
          v-for="item in dragItems"
          :key="item.id"
          :data-id="item.id"
          class="draggable-item bg-white rounded-lg shadow-md hover:shadow-lg transition-all duration-200 p-6 cursor-move border border-gray-200 hover:border-emerald-300"
        >
          <div class="flex items-center">
            <svg
              class="w-5 h-5 text-gray-400 mr-3"
              fill="currentColor"
              viewBox="0 0 20 20"
            >
              <path
                d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
              ></path>
            </svg>
            <div>
              <h4 class="font-semibold text-gray-800">{{ item.title }}</h4>
              <p class="text-sm text-gray-500">{{ item.description }}</p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { Sortable } from "@shopify/draggable";
const dragContainer = ref(null);
const dragItems = ref([
  { id: 1, title: "任务 1", description: "完成项目设计" },
  { id: 2, title: "任务 2", description: "开发核心功能" },
  { id: 3, title: "任务 3", description: "编写测试用例" },
  { id: 4, title: "任务 4", description: "部署到生产环境" },
]);
let sortableInstance = null;
onMounted(() => {
  nextTick(() => {
    sortableInstance = new Sortable(dragContainer.value, {
      draggable: ".draggable-item",
      mirror: {
        appendTo: "body",
        constrainDimensions: true,
      },
    });
    // 监听拖拽排序事件
    sortableInstance.on("sortable:sorted", (evt) => {
      const { oldIndex, newIndex } = evt;
      // 更新数据顺序
      const item = dragItems.value.splice(oldIndex, 1)[0];
      dragItems.value.splice(newIndex, 0, item);
    });
    // 监听拖拽开始事件
    sortableInstance.on("drag:start", (evt) => {
      console.log("开始拖拽:", evt.source.dataset.id);
    });
    // 监听拖拽结束事件
    sortableInstance.on("drag:stop", (evt) => {
      console.log("拖拽结束:", evt.source.dataset.id);
    });
  });
});
</script>
<style scoped>
/* Draggable 镜像样式 */
.draggable-mirror {
  opacity: 0.8;
  transform: rotate(2deg);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  border-radius: 0.5rem;
}
/* 拖拽中的原始元素样式 */
.draggable--is-dragging {
  opacity: 0.3;
  transform: scale(0.95);
}
/* 拖拽悬停目标样式 */
.draggable-container--over {
  background-color: #f0fdf4;
}
/* 拖拽项目悬停样式 */
.draggable-item:hover {
  transform: translateY(-2px);
}
</style>
多容器拖拽交换
<template>
  <div
    class="min-h-screen bg-gradient-to-br from-purple-50 to-pink-50 py-12 px-4"
  >
    <div class="max-w-6xl mx-auto">
      <h3 class="text-3xl font-bold text-gray-800 text-center mb-8">
        🔄 多容器拖拽交换
      </h3>
      <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
        <!-- 源容器 -->
        <div class="bg-white rounded-xl shadow-lg p-6">
          <h4 class="text-xl font-semibold text-gray-700 mb-4 text-center">
            📦 可用组件
          </h4>
          <div
            ref="sourceContainer"
            class="space-y-3 min-h-[400px] p-4 border-2 border-dashed border-gray-200 rounded-lg"
          >
            <div
              v-for="item in sourceItems"
              :key="item.id"
              :data-id="item.id"
              class="swappable-item bg-gradient-to-r from-purple-100 to-pink-100 border border-purple-200 rounded-lg p-4 cursor-move hover:shadow-md transition-all duration-200"
            >
              <div class="flex items-center">
                <div class="w-3 h-3 bg-purple-500 rounded-full mr-3"></div>
                <div>
                  <h5 class="font-medium text-gray-800">{{ item.name }}</h5>
                  <p class="text-sm text-gray-500">{{ item.type }}</p>
                </div>
              </div>
            </div>
          </div>
        </div>
        <!-- 目标容器 -->
        <div class="bg-white rounded-xl shadow-lg p-6">
          <h4 class="text-xl font-semibold text-gray-700 mb-4 text-center">
            🎯 设计画布
          </h4>
          <div
            ref="targetContainer"
            class="space-y-3 min-h-[400px] p-4 border-2 border-dashed border-blue-200 rounded-lg bg-blue-50"
          >
            <div
              v-for="item in targetItems"
              :key="item.id"
              :data-id="item.id"
              class="swappable-item bg-gradient-to-r from-blue-100 to-indigo-100 border border-blue-200 rounded-lg p-4 cursor-move hover:shadow-md transition-all duration-200"
            >
              <div class="flex items-center">
                <div class="w-3 h-3 bg-blue-500 rounded-full mr-3"></div>
                <div>
                  <h5 class="font-medium text-gray-800">{{ item.name }}</h5>
                  <p class="text-sm text-gray-500">{{ item.type }}</p>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { Swappable } from "@shopify/draggable";
const sourceContainer = ref(null);
const targetContainer = ref(null);
const sourceItems = ref([
  { id: 1, name: "按钮组件", type: "UI组件" },
  { id: 2, name: "输入框", type: "UI组件" },
  { id: 3, name: "图片组件", type: "媒体组件" },
]);
const targetItems = ref([{ id: 4, name: "标题组件", type: "UI组件" }]);
let swappableInstance = null;
onMounted(() => {
  nextTick(() => {
    const containers = [sourceContainer.value, targetContainer.value];
    swappableInstance = new Swappable(containers, {
      draggable: ".swappable-item",
      mirror: {
        appendTo: "body",
        constrainDimensions: true,
      },
    });
    swappableInstance.on("swappable:stop", (evt) => {
      updateContainersData();
    });
  });
});
function updateContainersData() {
  // 更新源容器数据
  const sourceElements =
    sourceContainer.value.querySelectorAll(".swappable-item");
  const newSourceItems = Array.from(sourceElements)
    .map((el) => {
      const id = parseInt(el.dataset.id);
      return [...sourceItems.value, ...targetItems.value].find(
        (item) => item.id === id
      );
    })
    .filter(Boolean);
  // 更新目标容器数据
  const targetElements =
    targetContainer.value.querySelectorAll(".swappable-item");
  const newTargetItems = Array.from(targetElements)
    .map((el) => {
      const id = parseInt(el.dataset.id);
      return [...sourceItems.value, ...targetItems.value].find(
        (item) => item.id === id
      );
    })
    .filter(Boolean);
  sourceItems.value = newSourceItems;
  targetItems.value = newTargetItems;
}
</script>
<style scoped>
.draggable-mirror {
  opacity: 0.8;
  transform: rotate(3deg) scale(1.05);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.swappable--is-dragging {
  opacity: 0.5;
}
.draggable-container--over {
  background-color: #f0f9ff;
  border-color: #0ea5e9;
}
</style>
核心类和插件
主要类
| 类名 | 描述 | 用途 | 
|---|---|---|
| Draggable | 基础拖拽类 | 实现基本的拖拽功能 | 
| Sortable | 可排序类 | 在容器内重新排序元素 | 
| Swappable | 可交换类 | 在多个容器间交换元素 | 
| Droppable | 可放置类 | 实现拖放到指定区域 | 
常用插件
| 插件 | 描述 | 功能 | 
|---|---|---|
| Mirror | 镜像插件 | 显示拖拽时的镜像元素 | 
| Focusable | 焦点插件 | 键盘导航支持 | 
| Announcement | 公告插件 | 屏幕阅读器支持 | 
| ScrollSensitive | 滚动敏感插件 | 拖拽时自动滚动 | 
常用配置选项
| 选项 | 类型 | 默认值 | 描述 | 
|---|---|---|---|
| draggable | String | .draggable-source--is-draggable | 可拖拽元素选择器 | 
| handle | String | null | 拖拽手柄选择器 | 
| delay | Number | 0 | 拖拽延迟时间(毫秒) | 
| distance | Number | 0 | 触发拖拽的最小距离 | 
| mirror | Object | {} | 镜像配置选项 | 
| scrollable | Object | {} | 滚动配置选项 | 
常用事件
| 事件 | 描述 | 触发时机 | 
|---|---|---|
| drag:start | 开始拖拽 | 拖拽开始时 | 
| drag:move | 拖拽移动 | 拖拽过程中 | 
| drag:stop | 拖拽结束 | 拖拽结束时 | 
| sortable:start | 开始排序 | 排序开始时 | 
| sortable:stop | 排序结束 | 排序结束时 | 
| swappable:start | 开始交换 | 交换开始时 | 
| swappable:stop | 交换结束 | 交换结束时 | 
最佳实践
- 性能优化: 使用事件委托,避免为每个元素绑定事件
- 数据同步: 及时更新 Vue 的响应式数据状态
- 用户体验: 提供清晰的视觉反馈和动画效果
- 可访问性: 启用键盘导航和屏幕阅读器支持
- 移动端适配: 测试触摸设备的拖拽体验
- 错误处理: 添加适当的错误处理和回滚机制
总结
Draggable 是一个功能强大且高度可定制的拖拽库,提供了完整的拖拽解决方案。通过其模块化的设计和丰富的插件系统,可以轻松实现各种复杂的拖拽交互需求。与 Vue 3 结合使用时,能够创建出流畅且用户友好的拖拽界面。
 原文链接:https://code.ifrontend.net/archives/902,转载请注明出处。		    			
		             
	
评论0