前端使用 Konva 实现可视化设计器(10)- 对齐线
请大家动动小手,给我一个免费的 Star 吧~
大家如果发现了 Bug,欢迎来提 Issue 哟~
不知不觉来到第 10 章了,感觉接近尾声了。。。
对齐线
先看效果:
这里交互有两个部分:
1、节点之间的对齐线
2、对齐磁贴
多选的情况下,效果是一样的:
主要逻辑会放在控制“选择”的代码文件里:
src\Render\handlers\SelectionHandlers.ts
这里需要一些辅助都定义:
interface SortItem {
id?: number // 有 id 就是其他节点,否则就是 选择目标
value: number // 左、垂直中、右的 x 坐标值; 上、水平中、下的 y 坐标值;
}
type SortItemPair = [SortItem, SortItem]
尝试画个图说明一下上面的含义:
这里以纵向(基于 x 坐标值)为例:
这里的 x1~x9,就是 SortItem,横向(基于 y 坐标值)同理,特别地,如果是正在拖动的目标节点,会把该节点的 _id 记录在 SortItem 以示区分。
会存在一个处理,把一个方向上的所有 x 坐标进行从小到大的排序,然后一双一双的遍历,需要符合以下条件“必须分别属于相邻的两个节点”的 SortItem 对,也就是 SortItemPair。
在查找所有 SortItemPair 的同时,只会更新并记录节点距离最短的那些 SortItemPair(可能会存在多个)。
核心逻辑代码:
// 磁吸逻辑
attract = (newPos: Konva.Vector2d) => {
// 对齐线清除
this.alignLinesClear()
// 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
let pairX: SortItemPair | null = null
let pairY: SortItemPair | null = null
// 对齐线 磁吸逻辑
if (this.render.config.attractNode) {
// 横向所有需要判断对齐的 x 坐标
const sortX: Array<SortItem> = []
// 纵向向所有需要判断对齐的 y 坐标
const sortY: Array<SortItem> = []
// 选择目标所有的对齐 x
sortX.push(
{
value: this.render.toStageValue(newPos.x - stageState.x) // 左
},
{
value: this.render.toStageValue(newPos.x - stageState.x + width / 2) // 垂直中
},
{
value: this.render.toStageValue(newPos.x - stageState.x + width) // 右
}
)
// 选择目标所有的对齐 y
sortY.push(
{
value: this.render.toStageValue(newPos.y - stageState.y) // 上
},
{
value: this.render.toStageValue(newPos.y - stageState.y + height / 2) // 水平中
},
{
value: this.render.toStageValue(newPos.y - stageState.y + height) // 下
}
)
// 拖动目标
const targetIds = this.render.selectionTool.selectingNodes.map((o) => o._id)
// 除拖动目标的其他
const otherNodes = this.render.layer.getChildren((node) => !targetIds.includes(node._id))
// 其他节点所有的 x / y 坐标
for (const node of otherNodes) {
// x
sortX.push(
{
id: node._id,
value: node.x() // 左
},
{
id: node._id,
value: node.x() + node.width() / 2 // 垂直中
},
{
id: node._id,
value: node.x() + node.width() // 右
}
)
// y
sortY.push(
{
id: node._id,
value: node.y() // 上
},
{
id: node._id,
value: node.y() + node.height() / 2 // 水平中
},
{
id: node._id,
value: node.y() + node.height() // 下
}
)
}
// 排序
sortX.sort((a, b) => a.value - b.value)
sortY.sort((a, b) => a.value - b.value)
// x 最短距离
let XMin = Infinity
// x 最短距离的【对】(多个)
let pairXMin: Array<SortItemPair> = []
// y 最短距离
let YMin = Infinity
// y 最短距离的【对】(多个)
let pairYMin: Array<SortItemPair> = []
// 一对对比较距离,记录最短距离的【对】
// 必须是 选择目标 与 其他节点 成【对】
// 可能有多个这样的【对】
for (let i = 0; i < sortX.length - 1; i++) {
// 相邻两个点,必须为 目标节点 + 非目标节点
if (
(sortX[i].id === void 0 && sortX[i + 1].id !== void 0) ||
(sortX[i].id !== void 0 && sortX[i + 1].id === void 0)
) {
// 相邻两个点的 x 距离
const offset = Math.abs(sortX[i].value - sortX[i + 1].value)
if (offset < XMin) {
// 更新 x 最短距离 记录
XMin = offset
// 更新 x 最短距离的【对】 记录
pairXMin = [[sortX[i], sortX[i + 1]]]
} else if (offset === XMin) {
// 存在多个 x 最短距离
pairXMin.push([sortX[i], sortX[i + 1]])
}
}
}
for (let i = 0; i < sortY.length - 1; i++) {
// 相邻两个点,必须为 目标节点 + 非目标节点
if (
(sortY[i].id === void 0 && sortY[i + 1].id !== void 0) ||
(sortY[i].id !== void 0 && sortY[i + 1].id === void 0)
) {
// 相邻两个点的 y 距离
const offset = Math.abs(sortY[i].value - sortY[i + 1].value)
if (offset < YMin) {
// 更新 y 最短距离 记录
YMin = offset
// 更新 y 最短距离的【对】 记录
pairYMin = [[sortY[i], sortY[i + 1]]]
} else if (offset === YMin) {
// 存在多个 y 最短距离
pairYMin.push([sortY[i], sortY[i + 1]])
}
}
}
// 取第一【对】,用于判断距离是否在阈值内
if (pairXMin[0]) {
if (Math.abs(pairXMin[0][0].value - pairXMin[0][1].value) < this.render.bgSize / 2) {
pairX = pairXMin[0]
}
}
if (pairYMin[0]) {
if (Math.abs(pairYMin[0][0].value - pairYMin[0][1].value) < this.render.bgSize / 2) {
pairY = pairYMin[0]
}
}
// 优先对齐节点
// 存在 1或多个 x 最短距离 满足阈值
if (pairX?.length === 2) {
for (const pair of pairXMin) {
// 【对】里的那个非目标节点
const other = pair.find((o) => o.id !== void 0)
if (other) {
// x 对齐线
const line = new Konva.Line({
points: _.flatten([
[other.value, this.render.toStageValue(-stageState.y)],
[other.value, this.render.toStageValue(this.render.stage.height() - stageState.y)]
]),
stroke: 'blue',
strokeWidth: this.render.toStageValue(1),
dash: [4, 4],
listening: false
})
this.alignLines.push(line)
this.render.layerCover.add(line)
}
}
// 磁贴第一个【对】
const target = pairX.find((o) => o.id === void 0)
const other = pairX.find((o) => o.id !== void 0)
if (target && other) {
// 磁铁坐标值
newPosX = newPosX - this.render.toBoardValue(target.value - other.value)
isAttract = true
}
}
// 存在 1或多个 y 最短距离 满足阈值
if (pairY?.length === 2) {
for (const pair of pairYMin) {
// 【对】里的那个非目标节点
const other = pair.find((o) => o.id !== void 0)
if (other) {
// y 对齐线
const line = new Konva.Line({
points: _.flatten([
[this.render.toStageValue(-stageState.x), other.value],
[this.render.toStageValue(this.render.stage.width() - stageState.x), other.value]
]),
stroke: 'blue',
strokeWidth: this.render.toStageValue(1),
dash: [4, 4],
listening: false
})
this.alignLines.push(line)
this.render.layerCover.add(line)
}
}
// 磁贴第一个【对】
const target = pairY.find((o) => o.id === void 0)
const other = pairY.find((o) => o.id !== void 0)
if (target && other) {
// 磁铁坐标值
newPosY = newPosY - this.render.toBoardValue(target.value - other.value)
isAttract = true
}
}
}
虽然代码比较冗长,不过逻辑相对还是比较清晰,找到满足条件(小于阈值,足够近,这里阈值为背景网格的一半大小)的 SortItemPair,就可以根据记录的坐标值大小,定义并绘制相应的线条(添加到 layerCover 中),记录在某个变量中:
// 对齐线
alignLines: Konva.Line[] = []
// 对齐线清除
alignLinesClear() {
for (const line of this.alignLines) {
line.remove()
}
this.alignLines = []
}
在适合的时候,执行 alignLinesClear 清空失效的对齐线即可。
接下来,计划实现下面这些功能:
- 连接线
- 等等。。。
More Stars please!勾勾手指~
前端使用 Konva 实现可视化设计器(10)- 对齐线的更多相关文章
- 惊闻企业Web应用生成平台 活字格 V4.0 免费了,不单可视化设计器免费,服务器也免费!
官网消息: 针对活字格开发者,新版本完全免费!您可下载活字格 Web 应用生成平台 V4.0 Updated 1,方便的创建各类 Web 应用系统,任意部署,永不过期. 我之前学习过活字格,也曾经向用 ...
- (原创)【B4A】一步一步入门02:可视化界面设计器、控件的使用
一.前言 上篇 (原创)[B4A]一步一步入门01:简介.开发环境搭建.HelloWorld 中我们创建了默认的项目,现在我们来看一下B4A项目的构成,以及如何所见即所得的设计界面,并添加和使用自带的 ...
- Windows Phone 十二、设计器同步
在设计阶段为页面添加数据源 Blend或者VS的可视化设计器会跑我们的代码,然后来显示出来,当我们Build之后,设计器会进入页面的构造函数,调用InitializeComponent();方法来将U ...
- WinForms项目升级.Net Core 3.0之后,没有WinForm设计器?
目录 .NET Conf 2019 Window Forms 设计器 .NET Conf 2019 2019 9.23-9.25召开了 .NET Conf 2019 大会,大会宣布了 .Net Cor ...
- ActiveReports 9 新功能:可视化查询设计器(VQD)介绍
在最新发布的ActiveReports 9报表控件中添加了多项新功能,以帮助你在更短的时间里创建外观绚丽.功能强大的报表系统,本文将重点介绍可视化数据查询设计器,无需手动编写任何SQL语句,主要内容如 ...
- VS2015 android 设计器不能可视化问题解决。
近期安装了VS2015,体验了一下android 的开发,按模板创建执行了个,试下效果非常不错.也能够可视化设计.但昨天再次打开或创建一个android程序后,设计界面直接不能显示,显示错误:(可能是 ...
- 2013 duilib入门简明教程 -- 界面设计器 DuiDesigner (10)
上一个教程讲解了怎么布局最大化.最小化.关闭按钮,但是如果手动去计算这三个按钮的位置和大小的话,非常的不直观,也很不方便. 所以这一章准备介绍duilib的UI设计器,由于这个设计器很 ...
- duilib入门简明教程 -- 界面设计器 DuiDesigner (10)
上一个教程讲解了怎么布局最大化.最小化.关闭按钮,但是如果手动去计算这三个按钮的位置和大小的话,非常的不直观,也很不方便. 所以这一章准备介绍duilib的UI设计器,由于这个设计器很不 ...
- duilib入门简明教程 -- 界面设计器 DuiDesigner (10) (转)
原文转自:http://www.cnblogs.com/Alberl/p/3343838.html 上一个教程讲解了怎么布局最大化.最小化.关闭按钮,但是如果手动去计算这三个按钮的位置和大小的 ...
- Windows 10开发基础——VS2015 Update1新建UWP项目,XAML设计器无法加载的解决
这次,我们来解决一个问题...在使用Visual Studio 2015 Update 1的时候,新建一个UWP的项目,XAML设计器就会崩,具体异常信息如下图: 解决方法如下:下面圈出的那个路径就按 ...
随机推荐
- 01 jQuery初使用
01 jQuery初使用 jQuery是一个曾经火遍大江南北的一个Javascript的第三方库. jQuery的理念: write less do more. 其含义就是让前端程序员从繁琐的js代码 ...
- Git 删除 .gitignore 生成之前上传的文件
清除缓存 git rm -r --cached . git add . 提交记录 git commit -m "chore: 清除缓存." git push
- RabbitMQ 05 直连模式-Spring Boot操作
Spring Boot集成RabbitMQ是现在主流的操作RabbitMQ的方式. 官方文档:https://docs.spring.io/spring-amqp/docs/current/refer ...
- Windows wsl2支持systemd
背景 很多Linux发行版都是使用systemd来管理程序进程,但是在WSL中默认是用init来管理进程的. 为了符合长久的使用习惯,且省去不必要的学习成本,就在WSL的发行版(我这里安装的是Ubun ...
- 在python中通过面向对象方式,实现烤地瓜案例
例子:烤地瓜,不同时间,反馈不同状态,并给不同状态地瓜加入不同味道 烤地瓜时间 0-3分钟,生的 4-7分钟,半生不熟的 8-12分钟,熟了 12分钟以上,已烤熟,糊了 用户可以按自己的意思添加调料 ...
- 4天带你上手HarmonyOS ArkUI开发
本次HarmonyOS ArkUI入门训练营课程--健康生活实战篇,手把手教大家如何制作一个合理膳食的APP前端Demo! 课程实战样例通过ArkUI声明式UI开发框架实现,只需用几行简单直观的声明式 ...
- Centos8安装docker-ce
一.安装步骤 1.安装yum-utils yum install -y yum-utils 2.配置阿里源 yum-config-manager --add-repo http://mirrors.a ...
- 【转】CentOS安装VMware Tools
[转]CentOS安装VMware Tools VMware 是非常好的虚拟机软件.如果系统安装了VMware Tools以后对虚拟机的性能会提升很多的.下面是如果在CentOS系统内安装VMware ...
- js 检查对象是否没有字段
前言 我以前遇到的一个问题. var object={}; if(object=={}) { } 在这里我遇到了问题,一直是lese状态. 后来想想其实也是自己犯了一个很严重的问题,这两个不是同一个对 ...
- 安装两个jdk Windows系统
安装两个jdk Windows系统 本来装的是Java 8,现在有一个新的项目要到手上,需要的环境是Java 7,想着还有别的要用,所以就装两个在电脑上 jdk的下载直接去官网就行,不知道怎么下就看这 ...