关于第三章提到的 selectingNodesArea,在后续的实现中已经精简掉了。

而 transformer 的 dragBoundFunc 中的逻辑,也直接移动 transformer 的 dragmove 事件中处理。

请大家动动小手,给我一个免费的 Star 吧~

这一章花了比较多的时间调试,创作不易~

github源码

gitee源码

示例地址

磁贴效果

放大缩小点磁贴网格效果



官方提供的便捷的 api 可以实现该效果,就是 transformer 的 anchorDragBoundFunc,官方实例,在此基础上,根据当前设计进行实现。

    // 变换中
anchorDragBoundFunc: (oldPos: Konva.Vector2d, newPos: Konva.Vector2d) => {
// 磁贴逻辑 if (this.render.config.attractResize) {
// transformer 锚点按钮
const anchor = this.render.transformer.getActiveAnchor() // 非旋转(就是放大缩小时)
if (anchor && anchor !== 'rotater') {
// stage 状态
const stageState = this.render.getStageState() const logicX = this.render.toStageValue(newPos.x - stageState.x) // x坐标
const logicNumX = Math.round(logicX / this.render.bgSize) // x单元格个数
const logicClosestX = logicNumX * this.render.bgSize // x磁贴目标坐标
const logicDiffX = Math.abs(logicX - logicClosestX) // x磁贴偏移量
const snappedX = /-(left|right)$/.test(anchor) && logicDiffX < 5 // x磁贴阈值 const logicY = this.render.toStageValue(newPos.y - stageState.y) // y坐标
const logicNumY = Math.round(logicY / this.render.bgSize) // y单元格个数
const logicClosestY = logicNumY * this.render.bgSize // y磁贴目标坐标
const logicDiffY = Math.abs(logicY - logicClosestY) // y磁贴偏移量
const snappedY = /^(top|bottom)-/.test(anchor) && logicDiffY < 5 // y磁贴阈值 if (snappedX && !snappedY) {
// x磁贴
return {
x: this.render.toBoardValue(logicClosestX) + stageState.x,
y: oldPos.y
}
} else if (snappedY && !snappedX) {
// y磁贴
return {
x: oldPos.x,
y: this.render.toBoardValue(logicClosestY) + stageState.y
}
} else if (snappedX && snappedY) {
// xy磁贴
return {
x: this.render.toBoardValue(logicClosestX) + stageState.x,
y: this.render.toBoardValue(logicClosestY) + stageState.y
}
}
}
} // 不磁贴
return newPos
}

主要的逻辑:根据最新的坐标,找到最接近的网格,达到设计的阈值就按官方 api 的定义,返回修正过的坐标(视觉上),所以返回之前,把计算好的“逻辑坐标”用 toBoardValue 恢复成“视觉坐标”。

移动磁贴网格效果



这个功能实现起来比较麻烦,官方是没有像类似 anchorDragBoundFunc 这样的 api,需要在 transformer 的 dragmove 介入修改。官方有个对齐线示例,也是“磁贴”相关的,证明在 transformer 的 dragmove 入手是合理的。主要差异是,示例是针对单个节点控制的,本设计是要控制在 transformer 中的多个节点的。

主要流程:

  • 通过 transformer 的 dragmove 获得拖动期间的坐标
  • 计算离四周网格的距离和偏移量
  • 横向、纵向分别找到达到接近阈值,且距离最近的那个网格坐标(偏移量最小)
  • 把选中的所有节点进行坐标修正

核心逻辑:

  // 磁吸逻辑
attract = (newPos: Konva.Vector2d) => {
// stage 状态
const stageState = this.render.getStageState() const width = this.render.transformer.width()
const height = this.render.transformer.height() let newPosX = newPos.x
let newPosY = newPos.y let isAttract = false if (this.render.config.attractBg) {
const logicLeftX = this.render.toStageValue(newPos.x - stageState.x) // x坐标
const logicNumLeftX = Math.round(logicLeftX / this.render.bgSize) // x单元格个数
const logicClosestLeftX = logicNumLeftX * this.render.bgSize // x磁贴目标坐标
const logicDiffLeftX = Math.abs(logicLeftX - logicClosestLeftX) // x磁贴偏移量 const logicRightX = this.render.toStageValue(newPos.x + width - stageState.x) // x坐标
const logicNumRightX = Math.round(logicRightX / this.render.bgSize) // x单元格个数
const logicClosestRightX = logicNumRightX * this.render.bgSize // x磁贴目标坐标
const logicDiffRightX = Math.abs(logicRightX - logicClosestRightX) // x磁贴偏移量 const logicTopY = this.render.toStageValue(newPos.y - stageState.y) // y坐标
const logicNumTopY = Math.round(logicTopY / this.render.bgSize) // y单元格个数
const logicClosestTopY = logicNumTopY * this.render.bgSize // y磁贴目标坐标
const logicDiffTopY = Math.abs(logicTopY - logicClosestTopY) // y磁贴偏移量 const logicBottomY = this.render.toStageValue(newPos.y + height - stageState.y) // y坐标
const logicNumBottomY = Math.round(logicBottomY / this.render.bgSize) // y单元格个数
const logicClosestBottomY = logicNumBottomY * this.render.bgSize // y磁贴目标坐标
const logicDiffBottomY = Math.abs(logicBottomY - logicClosestBottomY) // y磁贴偏移量 // 距离近优先 for (const diff of [
{ type: 'leftX', value: logicDiffLeftX },
{ type: 'rightX', value: logicDiffRightX }
].sort((a, b) => a.value - b.value)) {
if (diff.value < 5) {
if (diff.type === 'leftX') {
newPosX = this.render.toBoardValue(logicClosestLeftX) + stageState.x
} else if (diff.type === 'rightX') {
newPosX = this.render.toBoardValue(logicClosestRightX) + stageState.x - width
}
isAttract = true
break
}
} for (const diff of [
{ type: 'topY', value: logicDiffTopY },
{ type: 'bottomY', value: logicDiffBottomY }
].sort((a, b) => a.value - b.value)) {
if (diff.value < 5) {
if (diff.type === 'topY') {
newPosY = this.render.toBoardValue(logicClosestTopY) + stageState.y
} else if (diff.type === 'bottomY') {
newPosY = this.render.toBoardValue(logicClosestBottomY) + stageState.y - height
}
isAttract = true
break
}
}
} return {
pos: {
x: newPosX,
y: newPosY
},
isAttract
}
}

这段逻辑及其相关事件的改动,不下 5 次,才勉强达到预期的效果。

接下来,计划实现下面这些功能:

  • 实时预览窗
  • 导出、导入
  • 节点层次单个、批量调整
  • 键盘复制、粘贴
  • 对齐效果
  • 等等。。。

是不是值得更多的 Star 呢?勾勾手指~

源码

gitee源码

示例地址

前端使用 Konva 实现可视化设计器(5)的更多相关文章

  1. 惊闻企业Web应用生成平台 活字格 V4.0 免费了,不单可视化设计器免费,服务器也免费!

    官网消息: 针对活字格开发者,新版本完全免费!您可下载活字格 Web 应用生成平台 V4.0 Updated 1,方便的创建各类 Web 应用系统,任意部署,永不过期. 我之前学习过活字格,也曾经向用 ...

  2. (原创)【B4A】一步一步入门02:可视化界面设计器、控件的使用

    一.前言 上篇 (原创)[B4A]一步一步入门01:简介.开发环境搭建.HelloWorld 中我们创建了默认的项目,现在我们来看一下B4A项目的构成,以及如何所见即所得的设计界面,并添加和使用自带的 ...

  3. Windows Phone 十二、设计器同步

    在设计阶段为页面添加数据源 Blend或者VS的可视化设计器会跑我们的代码,然后来显示出来,当我们Build之后,设计器会进入页面的构造函数,调用InitializeComponent();方法来将U ...

  4. WinForms项目升级.Net Core 3.0之后,没有WinForm设计器?

    目录 .NET Conf 2019 Window Forms 设计器 .NET Conf 2019 2019 9.23-9.25召开了 .NET Conf 2019 大会,大会宣布了 .Net Cor ...

  5. ActiveReports 9 新功能:可视化查询设计器(VQD)介绍

    在最新发布的ActiveReports 9报表控件中添加了多项新功能,以帮助你在更短的时间里创建外观绚丽.功能强大的报表系统,本文将重点介绍可视化数据查询设计器,无需手动编写任何SQL语句,主要内容如 ...

  6. VS2015 android 设计器不能可视化问题解决。

    近期安装了VS2015,体验了一下android 的开发,按模板创建执行了个,试下效果非常不错.也能够可视化设计.但昨天再次打开或创建一个android程序后,设计界面直接不能显示,显示错误:(可能是 ...

  7. 可视化流程设计——流程设计器演示(基于Silverlight)

    上一篇文章<通用流程设计>对鄙人写的通用流程做了一定的介绍,并奉上了相关源码.但一个好的流程设计必少不了流程设计器的支持,本文将针对<通用流程设计>中的流程的设计器做一个简单的 ...

  8. Type Script 在流程设计器的落地实践

    流程设计器项目介绍 从事过BPM行业的大佬必然对流程建模工具非常熟悉,做为WFMC三大体系结构模型中的核心模块,它是工作流的能力模型,其他模块都围绕工作流定义来构建. 成熟的建模工具通过可视化的操作界 ...

  9. .net erp(办公oa)开发平台架构概要说明之表单设计器

    背景:搭建一个适合公司erp业务的开发平台.   架构概要图: 表单设计开发部署示例图    表单设计开发部署示例说明1)每个开发人员可以自己部署表单设计至本地一份(当然也可以共用一套开发环境,但是如 ...

  10. 解析大型.NET ERP系统核心组件 查询设计器 报表设计器 窗体设计器 工作流设计器 任务计划设计器

    企业管理软件包含一些公共的组件,这些基础的组件在每个新项目立项阶段就必须考虑.核心的稳定不变功能,方便系统开发与维护,也为系统二次开发提供了诸多便利.比如通用权限管理系统,通用附件管理,通用查询等组件 ...

随机推荐

  1. MySql变量说明

    1 #变量 2 /* 3 系统变量: 4 全局变量 5 会话变量 6 7 自定义变量: 8 用户变量 9 局部变量 10 11 */ 12 #一.系统变量 13 /* 14 说明:变量由系统定义,不是 ...

  2. H3C-IP路由器

    定义 路由器负责将数据报文在IP网段之间进行转发 路由器负责将数据报文在IP网段之间进行转发 路由是指导路由器如何进行数据转发的路径信息 作用 路由器负责将数据报文在逻辑网段间进行转发 路由器是指导路 ...

  3. 【预训练语言模型】使用Transformers库进行GPT2预训练

    基于 HuggingFace的Transformer库,在Colab或Kaggle进行预训练. 本教程提供:英文数据集wikitext-2和代码数据集的预训练. 注:可以自行上传数据集进行训练 目的: ...

  4. 脑电测量ADS1299芯片调试总结

    问题一:读出来ID不对? 笔者经过查阅官网资料和测试,发现这个一般是上电或者启动次序不对引起的. 特别是上电次序不同会导致这类问题. 问题二:内部时钟和外部时钟的选择是什么? 就拿内部时钟来说吧,首先 ...

  5. 关于Java执行Cmd命令出现的死锁问题解决

    原文:关于Java执行Cmd命令出现的死锁问题解决 - Stars-One的杂货小窝 问题 之前研究了Java通过执行cmd命令从而触发Android打包的思路,但是发现Android打包成功之后,后 ...

  6. day26--Java集合09

    Java集合09 18.TreeSet 元素无序:插入顺序和输出顺序不一致 可以按照一定的规则进行排序,具体排序方式取决于构造方法: TreeSet () :根据其元素的自然排序进行排序 TreeSe ...

  7. mysql迁移到pqsql笔记

    在将MySQL迁移到PostgreSQL的过程中,遇到了一些问题,下面是一些简单的解决方案. 使用命令,初始化数据库,并设置postgres的密码 bin\initdb -E UTF-8 -A md5 ...

  8. [STM32]STM32双机串口通信

    [STM32]STM32双机串口通信 上一篇的通信方案在发送端高强度通信下寄了,发现是函数HAL_UART_Transmit()的锅,一个函数居然能跑0.3s左右...于是打算选用DMA收发数据,但是 ...

  9. CodeForces Hello 2024 个人题解(A~C)

    A. Wallet Exchange 时间限制: 1秒 内存限制: 256兆 输入: 标准输入 输出: 标准输出 Alice and Bob are bored, so they decide to ...

  10. [深度学习] 计算机视觉低代码工具Supervision库使用指北

    Supervision库是一款出色的Python计算机视觉低代码工具,其设计初衷在于为用户提供一个便捷且高效的接口,用以处理数据集以及直观地展示检测结果.Supervision库的官方开源仓库地址为: ...