分享好友 前端技术首页 频道列表

Vue+jsPlumb实现连线效果(支持滑动连线和点击连线)

vue  2023-03-08 21:440

前言

最近在做一个互动题板管理项目,主要负责开发互动题板的连线题,由于时间紧凑,一番search之后决定使用jsPlumb来做,本身jsPlumb做的是可以滑动连线的,奈何产品要同时兼容点击,我想做过拖拽的前端小伙伴知道,拖拽和点击两者是有冲突问题; 拖拽比点击多了个move的操作,所有我们可以通过鼠标按下和抬起的位置来区分是否点击或者是拖拽,

思路:

① 记录鼠标按下mousedown和鼠标抬起mouseup的时候当前的pageX和pageY,

② 通过开方将两个位置坐标进行对比,当值等于0或者小于10的时候证明当前是点击事件,反之则是拖拽事件

实现

下载依赖:

npm install jsplumb --save`

代码

<template>
  <div id="container">
    <div style="display: flex;margin-bottom: 50px">
      <div v-for="(el, index) in up" :key="'up'+index" class="border"
           :id="'up-'+index"
           @mousedown="mouseDown($event,'up-'+index)" >
        {{el.txt}}
      </div>
    </div>
    <div style="display: flex">
      <div v-for="(el, index) in below" :key="'below'+index" class="border"
           :id="'below-'+index"
           @mousedown="mouseDown($event,'below-'+index)">
        {{el.txt}}
      </div>
    </div>
  </div>
</template>

<script>
import { jsPlumb } from "jsplumb";
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data () {
    return {
      instance: null,
      up: [
        { txt: "up1"},
        { txt: "up2"},
        { txt: "up3"},
        { txt: "up4"},
      ],
      below: [
        { txt: "below1"},
        { txt: "below2"},
        { txt: "below3"},
        { txt: "below4"},
      ],
      curItem: "",
      pos: {
        pageX: 0,
        pageY: 0,
      },
      clickItem: []
    }
  },
  beforeDestroy () {
    document.removeEventListener("mouseup", this.mock);
  },
  mounted() {
    const _this = this;
    this.$nextTick(() => {
      jsPlumb.ready(function () {
        // 初始化jsPlumb 创建jsPlumb实例
        _this.init();
        // 设置可以为连线起点和连线终点的元素
        _this.setContainer();
        // 在连线事件中 只允许连接相邻的列表 不能跨列表连接
        _this.setRule();
        jsPlumb.fire("jsPlumbDemoLoaded", _this.instance);
      });
    });
    document.addEventListener("mouseup", this.mock);
  },
  methods: {
    init () {
      this.instance = jsPlumb.getInstance({
        Container: "container",
        Connector: "Straight",
        ConnectionsDetachable: false,
        DeleteEndpointsOnDetach: false,
        Detachable: false,
        PaintStyle: {
          strokeWidth: 5,
          stroke: "#ffffff",
          dashstyle: "5 0.8",
          outlineStroke: "transparent",
          outlineWidth: 15
        },
        HoverPaintStyle: {
          strokeWidth: 5,
          stroke: "#368FFF",
          dashstyle: "5 0.8"
        },
        Endpoint: ["Dot", { radius: 5 }],
        EndpointStyle: { fill: "transparent" }
      });
    },
    setContainer () {
      this.instance.batch(() => {
        for (let i = 0; i < this.up.length; i++) {
          this.initLeaf(`up-${i}`);
        }
        for (let j = 0; j < this.below.length; j++) {
          this.initLeaf(`below-${j}`);
        }
      });
    },
    setRule () {
      this.instance.bind("connection", () => {
        this.clickItem = [];
      });
    },
    initLeaf (id) {
      // anchor: ["Left", "Right"] 左右
      const elem = document.getElementById(id);
      this.instance.makeSource(elem,  {
        anchor: ["Top", "Bottom"],
        allowLoopback: false,
        maxConnections: -1
      });
      this.instance.makeTarget(elem, {
        anchor: ["Top", "Bottom"]
      });
    },
    mouseDown (e, index) {
      console.log("eee", e);
      this.curItem = index;
      this.pos = {
        pageX: e.pageX,
        pageY: e.pageY
      };
    },
    mock (e) {
      console.log("ee000e", e);
      // 模拟点击
      if (
          Math.abs(e.pageX - this.pos.pageX) <= 10 &&
          Math.abs(e.pageY - this.pos.pageY) <= 10
      ) {
        if (this.clickItem.length > 0) {
          this.clickItem.push(this.curItem);
          this.instance.connect({
            source: this.clickItem[0],
            target: this.clickItem[1]
          });
        } else {
          this.clickItem.push(this.curItem);
        }
      } else {
        this.clickItem = [];
      }
    },
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#container {
  height: 100%;
  background-color: greenyellow;
}
.border {
  width: 120px;
  height: 50px;
  line-height: 50px;
  border-radius: 8px;
  border: 1px dashed black;
  margin: 20px;
}
</style>

实现效果

Vue+jsPlumb实现连线效果(支持滑动连线和点击连线)

实现其实很简单,主要看document.addEventListener("mouseup", this.mock); 和 mouseDown方法。

原文地址:https://segmentfault.com/a/1190000043366813

查看更多关于【vue】的文章

展开全文
相关推荐
反对 0
举报 0
评论 0
图文资讯
热门推荐
优选好物
更多热点专题
更多推荐文章
vue3+TS 自定义指令:长按触发绑定的函数
vue3+TS 自定义指令:长按触发绑定的函数而然间看到一个在vue2中写的长按触发事件的自定义指定,想着能不能把他copy到我的vue3项目中呢。编写自定义指令时遇到的几个难点1.自定义指令的类型在ts中写任何东西都要考虑到类型的问题,自定义指令的类型问题依然存

0评论2023-03-08326

基于ZR.VUE 前端的改造,页面刷新报错
 问题描述:前后端分离开发,分开部署. 页面刷新 直接报404 错误的解决办法提示:  先在 .env.development 中 配置 VUE_APP_BASE_API , 将 '/' 替换为 后端地址 'http://localhost:8888/'如果是对应的发布的正式环境,也要修改  .env.production 的VUE_APP_

0评论2023-03-08677

Vue3 企业级优雅实战 - 组件库框架 - 9 实现组件库 cli - 上
上文搭建了组件库 cli 的基础架子,实现了创建组件时的用户交互,但遗留了 cli/src/command/create-component.ts 中的 createNewComponent 函数,该函数要实现的功能就是上文开篇提到的 —— 创建一个组件的完整步骤。本文咱们就依次实现那些步骤。(友情提示

0评论2023-03-08947

vue-3 this概念
一、this概念官方是这样说的:在 setup()内部,this 不会是该活跃实例的引用因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同这在和其它选项式 API 一起使用 setup() 时可能会导致混淆啥意思呢

0评论2023-03-08538

Vue3+TypeScript 项目中,配置 ESLint 和 Prettier
接上篇:从0搭建vite-vue3-ts项目框架:配置less+svg+pinia+vant+axios文档同步项目gitee:https://gitee.com/lixin_ajax/vue3-vite-ts-pinia-vant-less.git 一、Eslint:用于检测代码安装eslint相关依赖yarn add eslint eslint-plugin-vue @typescript-esli

0评论2023-03-08825

Vue中四种操作dom方法保姆级讲解 vue中如何操作dom
目录前言一、通过ref拿到dom的引用适用场景示例代码二、通过父容器的ref遍历拿到dom引用适用场景示例代码三、通过子组件emit传递ref适用场景示例代码四、通过:ref将dom引用放到数组中适用场景示例代码前言最近主管提出了许多优化用户体验的要求,其中很多涉及

0评论2023-03-08944

Vue状态管理工具Vuex工作原理解析 vuex五种状态
目录一、什么是vuex二、vuex的工作方式三、vuex的使用场景四、工作流程五、vuex的核心API六、应用七、vuex的工作流程一、什么是vuexVuex是vue项目的状态管理器(状态管理工具)。vue项目的状态是通过vue实例(组件)绑定的变量来体现。所以也可以说vuex是用来管理

0评论2023-03-08859

更多推荐