在日常开发中,我们可能会遇到需要用户通过拖拽调整布局大小的场景,例如分割面板、拖拽调整弹窗大小等功能。本文将介绍如何使用 Vue 自定义指令实现一个支持拖拽缩放的容器,并详细说明该指令的功能和使用方法。

指令功能概述

该指令通过拖拽分隔条,调整目标元素的宽度或高度,具备以下功能:

  1. 支持宽度或高度缩放:可以通过传递参数决定调整方向。

  2. 支持尺寸限制:可以设置最小尺寸(minSize)和最大尺寸(maxSize)。

  3. 步长调整:支持每次调整的像素步长,提供更精细或更灵敏的拖拽体验。

  4. 回调事件:支持在调整尺寸时触发父组件的回调,方便同步更新。

指令实现

以下是该指令的完整实现代码:

// src/directives/resize.ts
import type { Directive, DirectiveBinding } from "vue"; // 使用 type 引入

const resize: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    const direction = binding.arg || "height"; // 默认按高缩放
    const step = binding.value?.step || 1; // 缩放的步长
    const sensitivity = binding.value?.sensitivity || 1; // 缩放灵敏度
    const minSize = binding.value?.minSize || 50; // 最小尺寸
    const maxSize = binding.value?.maxSize || Infinity; // 最大尺寸

    const parent = el.parentElement;
    if (!parent) {
      console.warn("resize directive requires the element to have a parent");
      return;
    }

    // 创建分隔条
    const divider = document.createElement("div");
    divider.style.position = "absolute";
    divider.style.background = "#ccc";
    divider.style.cursor = direction === "width" ? "col-resize" : "row-resize";
    divider.style[direction === "width" ? "right" : "bottom"] = "0";
    divider.style[direction === "width" ? "width" : "height"] = "5px";
    divider.style[direction === "width" ? "height" : "width"] = "100%";
    divider.style.zIndex = "100";
    el.appendChild(divider);

    // 事件处理
    let startPos = 0;
    let startSize = 0;

    const onMouseDown = (event: MouseEvent) => {
      startPos = direction === "width" ? event.clientX : event.clientY;
      startSize = direction === "width" ? el.offsetWidth : el.offsetHeight;

      document.addEventListener("mousemove", onMouseMove);
      document.addEventListener("mouseup", onMouseUp);
      event.preventDefault();
    };

    const onMouseMove = (event: MouseEvent) => {
      const currentPos = direction === "width" ? event.clientX : event.clientY;
      const diff = (currentPos - startPos) * sensitivity;
      let newSize = startSize + Math.round(diff / step) * step;

      newSize = Math.max(minSize, Math.min(maxSize, newSize));
      el.style[direction] = `${newSize}px`;

      // 触发父组件绑定的回调
      if (binding.value?.onResize) {
        binding.value.onResize(newSize);
      }
    };

    const onMouseUp = () => {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
    };

    divider.addEventListener("mousedown", onMouseDown);
  },
};

export default resize;

使用方法

1. 注册指令

在项目中全局注册指令:

// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import resize from "./directives/resize";

const app = createApp(App);
app.directive("resize", resize);
app.mount("#app");

2. 模板中使用指令

使用 v-resize 指令可以轻松实现可拖拽缩放的容器。以下是一个示例:

<template>
  <div class="resize-container" v-resize:height="{
    step: 10,
    sensitivity: 1,
    minSize: 100,
    maxSize: 500,
    onResize: handleResize
  }">
    <p>拖拽调整高度</p>
  </div>
</template>

<script setup>
function handleResize(newSize) {
  console.log("当前尺寸:", newSize);
}
</script>

<style>
.resize-container {
  width: 100%;
  height: 300px;
  position: relative;
  background-color: #f0f0f0;
}
</style>

3. 参数说明

指令参数

参数名称

类型

默认值

描述

arg

string

"height"

决定缩放方向,可选值为 "width""height"

step

number

1

每次拖拽缩放的步长

sensitivity

number

1

缩放灵敏度,值越大拖拽越灵敏

minSize

number

50

最小尺寸(像素)

maxSize

number

Infinity

最大尺寸(像素)

onResize

function

拖拽时触发的回调函数,接收当前尺寸为参数

适用场景

  1. 分割布局:例如左侧导航栏和右侧内容区域的宽度调整。

  2. 弹窗调整:拖拽调整弹窗的宽高。

  3. 动态面板:如代码编辑器与预览窗口的大小调整。

总结

通过本文提供的自定义指令,开发者可以轻松实现拖拽缩放功能,且具备灵活的定制化选项(如步长、灵敏度、尺寸限制等)。这种实现方式不仅减少了 DOM 操作,还能无缝地集成到 Vue 项目中,为用户提供更好的交互体验。