所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

零依赖拖拽神器:SortableJS 在 Vue 3 中的最佳实践

简介

SortableJS 是一个功能强大、轻量级的 JavaScript 拖拽排序库,支持现代浏览器和触摸设备。它提供了丰富的配置选项和事件处理,可以轻松实现列表项的拖拽排序功能。

主要特性

  • 🚀 轻量级: 压缩后仅约 30KB
  • 📱 触摸支持: 完美支持移动设备触摸操作
  • 🎯 无依赖: 不依赖任何第三方库
  • 🔧 高度可配置: 丰富的配置选项和事件回调
  • 🌐 跨浏览器: 支持所有现代浏览器
  • 可访问性: 支持键盘操作和屏幕阅读器

安装

# npm
npm install sortablejs

# yarn
yarn add sortablejs

# pnpm
pnpm add sortablejs

Vue 3.js 基础使用示例

1. 基本拖拽排序

<template>
  <div
    class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-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>
      <ul ref="basicList" class="space-y-3">
        <li
          v-for="item in basicItems"
          :key="item.id"
          class="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 p-4 cursor-move border border-gray-200 hover:border-blue-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>
            <span class="text-gray-700 font-medium">{{ item.name }}</span>
          </div>
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from "vue";
import Sortable from "sortablejs";

const basicList = ref(null);
const basicItems = ref([
  { id: 1, name: "项目 1" },
  { id: 2, name: "项目 2" },
  { id: 3, name: "项目 3" },
  { id: 4, name: "项目 4" },
]);

onMounted(() => {
  nextTick(() => {
    new Sortable(basicList.value, {
      animation: 150,
      ghostClass: "ghost",
      chosenClass: "chosen",
      dragClass: "drag",
      onEnd: (evt) => {
        // 更新数据顺序
        const item = basicItems.value.splice(evt.oldIndex, 1)[0];
        basicItems.value.splice(evt.newIndex, 0, item);
      },
    });
  });
});
</script>

<style scoped>
.ghost {
  opacity: 0.5;
  background-color: #dbeafe;
  border-color: #93c5fd;
}

.chosen {
  transform: scale(1.05);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  border-color: #60a5fa;
}

.drag {
  transform: rotate(2deg);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
</style>

2. 多列表拖拽

<template>
  <div class="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 p-6">
    <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-3 gap-6">
        <!-- 待办事项列 -->
        <div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
          <h4
            class="text-lg font-semibold text-gray-700 text-center mb-4 pb-2 border-b border-gray-200"
          >
            📝 待办事项
          </h4>
          <ul
            ref="todoList"
            class="space-y-3 min-h-[300px]"
            data-group="shared"
          >
            <li
              v-for="item in todoItems"
              :key="item.id"
              class="bg-gray-50 border border-gray-200 rounded-lg p-3 cursor-move hover:shadow-md transition-all duration-200 hover:bg-gray-100"
            >
              <div class="flex items-center">
                <div class="w-3 h-3 bg-gray-400 rounded-full mr-3"></div>
                <span class="text-gray-700">{{ item.text }}</span>
              </div>
            </li>
          </ul>
        </div>

        <!-- 进行中列 -->
        <div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
          <h4
            class="text-lg font-semibold text-gray-700 text-center mb-4 pb-2 border-b border-gray-200"
          >
            🚀 进行中
          </h4>
          <ul
            ref="doingList"
            class="space-y-3 min-h-[300px]"
            data-group="shared"
          >
            <li
              v-for="item in doingItems"
              :key="item.id"
              class="bg-orange-50 border border-orange-200 border-l-4 border-l-orange-400 rounded-lg p-3 cursor-move hover:shadow-md transition-all duration-200 hover:bg-orange-100"
            >
              <div class="flex items-center">
                <div class="w-3 h-3 bg-orange-400 rounded-full mr-3"></div>
                <span class="text-gray-700">{{ item.text }}</span>
              </div>
            </li>
          </ul>
        </div>

        <!-- 已完成列 -->
        <div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
          <h4
            class="text-lg font-semibold text-gray-700 text-center mb-4 pb-2 border-b border-gray-200"
          >
            ✅ 已完成
          </h4>
          <ul
            ref="doneList"
            class="space-y-3 min-h-[300px]"
            data-group="shared"
          >
            <li
              v-for="item in doneItems"
              :key="item.id"
              class="bg-green-50 border border-green-200 border-l-4 border-l-green-500 rounded-lg p-3 cursor-move hover:shadow-md transition-all duration-200 hover:bg-green-100 opacity-80"
            >
              <div class="flex items-center">
                <div class="w-3 h-3 bg-green-500 rounded-full mr-3"></div>
                <span class="text-gray-700 line-through">{{ item.text }}</span>
              </div>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from "vue";
import Sortable from "sortablejs";

const todoList = ref(null);
const doingList = ref(null);
const doneList = ref(null);

const todoItems = ref([
  { id: 1, text: "学习 Vue 3" },
  { id: 2, text: "完成项目文档" },
]);

const doingItems = ref([{ id: 3, text: "开发新功能" }]);

const doneItems = ref([
  { id: 4, text: "修复 Bug" },
  { id: 5, text: "代码审查" },
]);

onMounted(() => {
  nextTick(() => {
    const sortableOptions = {
      group: "shared",
      animation: 150,
      ghostClass: "ghost",
      chosenClass: "chosen",
      dragClass: "drag",
      onEnd: (evt) => {
        const { from, to, oldIndex, newIndex } = evt;

        // 获取对应的数据数组
        const fromList = getListByElement(from);
        const toList = getListByElement(to);

        if (fromList && toList) {
          const item = fromList.value.splice(oldIndex, 1)[0];
          toList.value.splice(newIndex, 0, item);
        }
      },
    };

    new Sortable(todoList.value, sortableOptions);
    new Sortable(doingList.value, sortableOptions);
    new Sortable(doneList.value, sortableOptions);
  });
});

function getListByElement(element) {
  if (element === todoList.value) return todoItems;
  if (element === doingList.value) return doingItems;
  if (element === doneList.value) return doneItems;
  return null;
}
</script>

<style scoped>
.ghost {
  opacity: 0.5;
  background-color: #dbeafe;
  border-color: #93c5fd;
  transform: rotate(2deg);
}

.chosen {
  transform: scale(1.02);
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  z-index: 10;
}

.drag {
  transform: rotate(3deg) scale(1.05);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  z-index: 20;
}
</style>

3. 带手柄的拖拽

<template>
  <div
    class="min-h-screen bg-gradient-to-br from-purple-50 to-pink-50 py-12 px-4"
  >
    <div class="max-w-2xl mx-auto">
      <h3 class="text-3xl font-bold text-gray-800 text-center mb-8">
        🎯 带手柄的拖拽列表
      </h3>
      <ul ref="handleList" class="space-y-4">
        <li
          v-for="item in handleItems"
          :key="item.id"
          class="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-200 overflow-hidden group"
        >
          <div class="flex items-center p-5">
            <!-- 拖拽手柄 -->
            <div
              class="drag-handle flex-shrink-0 mr-4 p-2 rounded-lg bg-gray-50 hover:bg-gray-100 transition-colors duration-200 cursor-move"
            >
              <svg
                class="w-5 h-5 text-gray-400 group-hover:text-gray-600"
                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>

            <!-- 任务内容 -->
            <div class="flex-1 min-w-0">
              <h4 class="text-lg font-semibold text-gray-800 truncate">
                {{ item.title }}
              </h4>
              <p class="text-sm text-gray-500 mt-1">拖拽手柄来重新排序</p>
            </div>

            <!-- 删除按钮 -->
            <button
              @click="deleteItem(item.id)"
              class="flex-shrink-0 ml-4 px-5 py-3 bg-red-500 hover:bg-red-600 text-white text-base font-medium rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 flex items-center whitespace-nowrap"
            >
              <svg
                class="w-4 h-4 mr-2"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
                ></path>
              </svg>
              删除
            </button>
          </div>
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from "vue";
import Sortable from "sortablejs";

const handleList = ref(null);
const handleItems = ref([
  { id: 1, title: "重要任务 1" },
  { id: 2, title: "重要任务 2" },
  { id: 3, title: "重要任务 3" },
  { id: 4, title: "重要任务 4" },
]);

onMounted(() => {
  nextTick(() => {
    new Sortable(handleList.value, {
      handle: ".drag-handle",
      animation: 200,
      ghostClass: "ghost",
      chosenClass: "chosen",
      dragClass: "drag",
      onEnd: (evt) => {
        const item = handleItems.value.splice(evt.oldIndex, 1)[0];
        handleItems.value.splice(evt.newIndex, 0, item);
      },
    });
  });
});

function deleteItem(id) {
  const index = handleItems.value.findIndex((item) => item.id === id);
  if (index > -1) {
    handleItems.value.splice(index, 1);
  }
}
</script>

<style scoped>
.ghost {
  opacity: 0.5;
  background-color: #f3e8ff;
  border-color: #c084fc;
  transform: rotate(1deg);
}

.chosen {
  transform: scale(1.02);
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  z-index: 10;
}

.drag {
  transform: rotate(2deg) scale(1.05);
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  z-index: 20;
}
</style>

常用配置选项

选项类型默认值描述
groupString/Objectnull分组名称,用于多列表拖拽
sortBooleantrue是否允许排序
disabledBooleanfalse是否禁用拖拽
animationNumber0动画持续时间(毫秒)
handleStringnull拖拽手柄选择器
filterStringnull过滤不可拖拽的元素
ghostClassStringsortable-ghost拖拽时的幽灵元素样式类
chosenClassStringsortable-chosen选中元素的样式类
dragClassStringsortable-drag拖拽元素的样式类

常用事件

事件描述参数
onStart开始拖拽时触发evt
onEnd拖拽结束时触发evt
onAdd元素添加到列表时触发evt
onUpdate列表内元素顺序更新时触发evt
onRemove元素从列表移除时触发evt
onSort任何排序变化时触发evt

最佳实践

  1. 性能优化: 对于大量数据,考虑使用虚拟滚动
  2. 数据同步: 及时更新 Vue 的响应式数据
  3. 用户体验: 添加适当的动画和视觉反馈
  4. 移动端适配: 测试触摸设备的拖拽体验
  5. 可访问性: 提供键盘操作支持

总结

SortableJS 是一个功能强大且易于使用的拖拽排序库,与 Vue 3 结合使用可以快速实现各种拖拽排序需求。通过合理的配置和事件处理,可以创建出用户体验良好的交互界面。

资源下载
下载价格免费
注意:本网站资源属于虚拟产品,不支持退款。请谨慎购买! 购买后资源无法下载,请联系客服QQ:844475003,微信号:th844475003。
原文链接:https://code.ifrontend.net/archives/900,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?