js 获取鼠标在画布的位置_云凤蝶如何打造媲美 sketch 的自由画布

a2050b6fb559a093eeb7ae93e0abc669.png

在 Design Tools 中,组件间的对齐与吸附功能是否好用是决定其画布是否可以高效进行产品设计的关键因素。云凤蝶作为一款快速制作高品质中后台应用的 hpaPaaS 平台,同样拥有自由拖拽的可视化画布。那么在云凤蝶的自由画布中,对齐规则是怎样的?实现这些规则的策略是怎样的?规则和策略是否足够完备?最终效果如何?能否媲美 Sketch 等设计软件?这篇文章就来对这些问题进行一一解答。

几个术语

在开始前,先简单介绍几个术语及其在画布中出现时的样式。

48276e15a3d8be128d6edcc647bc158a.png
构成组件的 6 条线

可以看到,一个组件在画布中可以由 6 条线 (vt / vm / vb | hl / hm / hr) 来表示,组件移动过程中的对齐其实就是组件的 6 条线到其它组件线的集合中寻找临近线,找到后考虑 吸附 + 对齐 的过程。

8a1fd958a6c2bb37633cb97acefa0b51.png
对齐线

79b077c09acad8918e62c9716d9cc378.png
间距线

347c66ca394aeb67df465c9fb5d38ab6.png
间距块

以上是组件在移动过程中对齐时出现的几种状态,其中出现 3 个名词,3 种辅助样式:

  • 红色实线代表吸附线
  • 蓝色实线代表距离线
  • 粉色块代表间距块

通用规则

在介绍云凤蝶画布中的对齐规则之前,我们先来直观地看下当下几款不错的设计产品在对齐方面是如何做的。这里主要以 Sketch 和 Figma 这两款设计工具为例,其它如 FramerX、墨刀等也可自行参考,效果大致相同,只是它们在实现上的优化程度略有高低。

Sketch

bb9708ad25bb8caea85a999357ef7ccb.gif
Sketch 对齐

af6aa9894262dfc2954b6d0007d2ed44.gif
Sketch 对齐

在 Sketch 中,以带边框组件的边框中线为基准,在移动过程中不断去找到距离最近的线去对齐。其中

  • 在首次接近某条线时,有一个吸附的过程。如在移动 1px 找到吸附线时,会让组件实际移动 6px,达到吸附效果
  • 当组件在吸附线上时,再次移动时,考虑是否能找到相邻的线,如果可以找到,则移动到下条吸附线上;如果未找到时,则鼠标移动某个距离时,触发移动,有种卡点的感觉,用以辅助对齐;
  • 另外还有比较细节的地方,比如当某一个方向移动吸附到相邻的线后(右图),如果相反方向移动,则非常轻松,而如果继续同一方向继续移动,则需要移动比较大的距离,以此实现了更好的吸附对齐效果。
  • ...

Figma

c8c9f0c8c2937c39a1d240f0862eac4b.gif
Figma 对齐

13bdbeb3919d92202b82627aa80815bd.gif
Figma 对齐

在 Figma 中,功能大致相同,与 Sketch 不同的是组件是以外边框为对齐线,且在组件移动过程中没有距离线的出现的。

提前剧透:这方面云凤蝶会有独特的策略。

对比

以上用最简单的组件移动示例分别展示了 Sketch 和 Figma 这两款设计产品在组件移动时的一个不断对齐的过程,核心主要涉及到

  • 对齐(与哪些组件边框线对齐)
  • 吸附(多少距离内时吸附)
  • 距离(与哪些组件边框线考虑展示距离)
  • 画线(画哪几条线、从哪里出线)

至于它们是如何实现,目前是不了解的,未查找到其完整的策略及算法。但是通过细致的使用,我们可以体验到这些产品中触发不同功能的时机、功能的优化以及顺畅度等等。另外,这些设计工具中还有有很多其它的辅助功能,如精心设计的快捷键、间距块的辅助、可视区域内的防干扰对齐等等,这里没有进行展示,下面在介绍云凤蝶自由画布中的辅助功能时会有介绍。

Sketch 和 Figma 在设计领域已经算是比较上乘的产品了,通过了解它们,我们可以看到当前设计产品中画布较好的的使用体验是怎样的,以此借鉴来优化我们的画布体验。

云凤蝶规则

好了,前置内容铺垫结束,到了云凤蝶画布这里。云凤蝶也有可自由拖拽的画布,还有更丰富的资产支持添加更多样的组件。不过,我们除了支持上述 Design Tools 中两条线的预期偏差对齐吸附,还遵循 Ant Design 设计规范中的 Gutter=(8n)px 的原则,所以云凤蝶画布中组件在对齐吸附时,还需要在几个特殊的无形线(8px / 16px / 24px)处做间距卡点吸附

关于布局、网格单位、栅格等规范,可以参考 Ant Design 官网中 布局 一节进行详细了解。

接下来让我们详细来看下云凤蝶中的规则以及当前已经完成的功能和效果。

对齐吸附

2eb371577ab9a761117dbdea40ec9d03.gif
对齐吸附

当组件朝某一方向移动时,以上图中横向左移动为例,它的 hl / hm / hr 会不断的去查找与这 3 条线相邻最近的线。其中

  • 当没有找到相邻线时,组件跟随鼠标移动;
  • 当初次找到时,组件便移动一个较大距离吸附过去;
  • 当在吸附线上再次移动时,继续查找相邻线,看是否有下一条吸附线
    • 如果有,则移动到下一条吸附线上;
    • 如果没有,则在鼠标移动一定距离后,组件离开;

以上便是组件对齐吸附的时机及规则,这是可自由拖拽画布中最基础的一个吸附对齐能力。

间距吸附

832b06963aef5e5f27c7c55ea9927c67.gif
间距吸附

上文中提到过,在云凤蝶中搭建页面时,组件的摆放要遵循 Ant Design 中 (8n)px 的原则,所以在云凤蝶的自由画布中也要实现组件间距满足要求时的吸附能力。

resize 吸附

2f13258bcfbe83c08ea113ae68fa390f.gif
resize 吸附

resize 吸附的使用场景是在调整组件宽高时,想要与目标组件的某条边线对齐。由上图也可以看到,对于组件的边框对齐,我们是做了内外的一个适配,站在用户搭建使用的角度来,给出对齐提示。

间距块吸附

5396850fd8706bfb8d0b247a4432f414.gif
间距块吸附

间距块的对齐吸附更多的是给到用户一种提示,即横/纵方向上几个组件间满足同等间距。

吸附剪枝

9e7c7e56867f1673892dc4bbd110b4b0.gif
可视区域变化

46a209bd79b95553794d7decdf51bd42.gif
可视区域内组件变化

吸附剪枝主要是解画布中组件太多时组件需要对齐的目标组件太多的问题,该规则依赖于“用户只关心当前画布中组件的对齐”这一假设,减少对齐时的干扰。上动图中主要展示跟随可视区域变化,其内组件数量变化时的剪枝。

另一种吸附剪枝是在组件快速移动时,也要避免一直找线,干扰正常移动,如下图中所示。

8337537b46c27b6dee0349f92aafa8c5.gif
快速移动时的剪枝

快捷键

这里简单举两个与辅助线相关功能的快捷键 command / option | alt。其实看到后面的实现后就会发现,只要有了这套线的逻辑,想实现类似的功能是比较容易的。

f5d275ff6fc30306584fa67f98de66ca.gif
command 键标识组件相对位置

b3c79834120f30cd541f173569c5cce2.gif
option/alt 键标识任意两个组件相对位置

1ddaaf6c8f418090583e5733d1d03adc.gif
command 键移动组件时无吸附对齐

画线原则

baa8bf1014fecdba0aa38e262cc5fc12.png
对齐

一个组件在对齐时,通常情况下出现的线的条数最多是

  • 3 + 3 条 红色的对齐线
  • 2 条 蓝色的距离线(蓝色线永远从移动的组件中线出发)

对于 Label

  • 横向线,label 在上
  • 纵向线,label 在左

规则原因

这里简单解释下,为什么要在画布中实现吸附对齐能力。

首先,可以回归到吸附对齐的作用 -- 弥补用户在精确操作鼠标上的不足。考虑以下场景:当用户试图把一个组件精确摆放到另一个组件旁边时,目标区域只有 1px,当在移动组件时,如何准确摆放到想要的位置?如果没有吸附对齐的能力,要想实现精确摆放是非常难操作的,要么容易重合,要么容易分离。此时,如果有吸附对齐的能力,就有种让“目标区域”变大的效果,当拖拽组件与目标组件两者仅仅相差某个指定范围的像素时,即认为是进入了吸附对齐的范围内,直接把移动组件吸附到目标位置上。与此同时,当组件离开时也会有一个“吸附引力”,即也有一个同等的离开距离,以形成卡点效果。这样,整个对齐操作就比较有控制力了。

同时,这种能力可以让画布中组件的摆放更加高效。依赖于 Ant Design 的 Gutter 设计原则,当我们在画布中进行页面搭建时,可以比较轻松的摆放到更符合设计原则的位置上去,更快速地搭建页面,从而达到提效的目标。

实现策略

当我们总结出自由画布中组件对齐吸附的能力之后,就可以考虑如何实现它。涉及到对齐吸附,一定就涉及到”移动组件“和”目标组件“,下面就从组件移动的角度来大致介绍实现以上规则的一种思路。

存储

在上文中提到,一个组件可以看做是由 6 条线构成的,组件移动过程中的吸附就是这些线之间的对齐关系。所以,首先要做的就是对组件进行线的存储。下面是一种可行的存储数据结构

一条线有自身的 pos、type及其所归属的 Box

// 线的数据结构
interface Line {pos: number;type: LineType;box?: Box | null;
}

一个组件有 6 条线,且冗余存储其 node 及组件实例信息 instance

// 组件的数据结构
interface Box {id: string;vt: Line;vm: Line;vb: Line;hl: Line;hm: Line;hr: Line;node: HTMLElement;instance: ComponentInstance;
}

线的存储可以考虑使用二叉树,普通二叉树可以参考 typescript-collections/src/lib/BSTree.ts,而横纵线可以构造两颗二叉树

// 线的集合
interface Lines {vLines: BSTree;hLines: BSTree;
}
如果你有其它好的思路,可以一起探讨,比如桶

移动

组件的移动,可以考虑两种实现方式

  • H5 Drag And Drop
  • 拖放鼠标事件

其中,H5 DnD 不是移动 HTML 元素,而是将数据对象从一个位置移动到另一个位置。要移动 HTML 元素,必须使用 MouseEvents:

  • mousedown: 选中元素
  • mousemove: 移动元素
  • mouseup: 释放元素
至于为什么会有对 DnD 的误解,可以参考 《Drag & Drop vs. MouseEvents - A misunderstanding》

为了实现更好地拖拽移动效果,我们采用 MouseEvents 的实现方式。决定了如何拖拽以后,接下来就来考虑,如何实现组件的移动。下图展示了鼠标选中组件时的移动位置关系,其中, A 为鼠标 mousedown 时的选中点,B 为鼠标移动后的 mouseup 的释放点,moveX 则表示鼠标的移动距离,也即组件即将横向移动的距离。

15ae7cbf6bdb9754a739979983c5a388.png
鼠标在组件上的移动

找线

线的查找与线的存储数据结构密切相关。在存储时,我们采用的是二叉树,所以找线的过程就变成了在二叉树中查找与某个位置具有某种大小关系的线,具体的找线算法这里不表,只能说根据业务规则尽量找到最优的算法。找线逻辑是整个策略中的关键一环,通过合理的策略找出离当前组件最近的线,只有这样才能保证后面的吸附是正确的。

找线策略在上文对齐吸附中有提到,这里不再赘述。需要说明的是,“离当前组件最近”是需要重点考虑的,因为在横/纵向查找的过程中,3 条线都有可能遇到最近的线,而且还有 Ant Design Gutter 8xpx 原则在这里,所以这里需要综合考虑。

吸附

当通过前置步骤找到了要吸附的线,我们就可以考虑是否将组件进行移动吸附了,这里主要还是要结合找到的”最近的线”。不过需要注意的是,我们不能在找到线时就移动过去,是因为组件移动距离与鼠标移动偏差不一致时可能会带来的抖动。

此外,对于组件在吸附状态下的移动,也是需要做二次找线的,因为当此时可能直接离开当前的引力区域,也可能是移动到下一条吸附线上。

吸附同样是非常关键的一环,处理不好将直接影响到画布的使用体验,处理了上述两方面,理论上就达到一个不错的效果。

分类

在文章开头,给出了几种不同辅助功能的线,在目前的对齐策略中,有以下几种

enum PairType {distance = 'distance', // 距离线alignment = 'alignment', // 对齐线spacing = 'spacing', // 间距线area = 'area', // 间距块
}

无论是对齐线、距离线、间距线还是间距块,都是通过二叉树中一对 Line 构成的 LinePair 画出的

export interface LinePair {source: Line; // 需要对齐的边target: Line; // 可以被对齐的边type: PairType; // 这对线的关系delta: number; // 这对线的偏差duplicate?: Line[]; // 目标对齐线可能包含多条位置相同的边框线或者中间线
}

根据以上数据结构,我们可以对找到的线进行一个分类,标识出一对线的关系及偏差。

画线

在以上步骤中拿到分好类的线,就可以进行画线操作了。通过线的分类及规范的数据结构,我们可以保证各类型线的画线逻辑的纯粹性。然后针对不同类型的线,在画布中用不同的层来实时展示即可。

至于如何实现更高效的画线以及用何种技术手段来实现,则可以仁者见仁了。

总结

本文主要总结了当前云凤蝶自由画布中支持的对齐、吸附、间距、resize、画线等辅助功能以及实现这些功能的整体规则和策略,这些都是自由画布中最基础的能力,也是推导出画布中吸附对齐涉及取值的理论基础。

梳理下来可以看到,要想在自由画布中实现对齐的能力并不难,麻烦的是如何将规则和策略理清楚、提炼和沉淀,从全局的视角和可扩展的角度来实现。最近云凤蝶自由画布中对齐等辅助能力已经根据这些原则进行了 2.0 的升级,从整体使用体验上,组件摆放这件事情已经变得比较容易。

未来

这里罗列几点当前云凤蝶画布中相较于 Design Tools 中还可以优化的地方:

  • 标尺的对齐与吸附(标尺可以看做是只有一条 h / v 方向 height / width 为画布大小的线)
  • 间距块的查找纳入对齐吸附的整体查找策略中(当前是与对齐线、辅助线的查找分开的)
  • 无极缩放,在更小的区域也能优雅地实现想要的操作
  • 揣测用户意图,更“智能”快速的摆放策略

看到上述几点,感觉也不是很难啊,为啥还没做?

是的,主要是因为...

等你来,一起做!

参考

  • Ant Design 布局https://ant.design/docs/spec/layout-cn
  • typescript-collectionshttps://github.com/basarat/typescript-collections/blob/release/src/lib/BSTree.ts
  • H5 Drag And Drophttps://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
  • MouseEventhttps://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
  • Drag'n'Drop with mouse eventshttps://javascript.info/mouse-drag-and-drop
  • Drag & Drop vs. MouseEvents - A misunderstandinghttps://steffenjahr.de/2016/02/07/drag-drop-vs-mouse-events-a-missunderstanding/
  • Sketchhttps://www.sketch.com/
  • Figmahttps://www.figma.com/

未来已来,时不我待!

云凤蝶招聘前端、Java、PD、设计岗位,未来等你共创!

如果你感兴趣,欢迎联系 chenyu@antfin.com 或 shuai.shao@antfin.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/433515.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GCC 使用摘记

1. 几个子程序和说明cc1C 的实际编译程序cc1plusC 的实际编译程序collect2在不使用 GNU连接程序的系统上,用来产生特定的全局初始化代码crt0.o为每个系统定制的初始化和结束代码libgcc默认连接的 gcc 例程库libstdc默认连接的 g 例程库 2. 几个重要的和 GCC 协同工作…

不愿意和别人打交道_如果你的交际能力很差,不喜欢与人打交道,这3种职业最适合你...

现在给个飞速发展的社会,在我们日常生活中交际表达能力非常的重要,但是还是有一部分人比较内向,不善于表达自己的内心,也不喜欢和别人接触,那么这一类人从事哪些行业比较合适呢?下面我就跟大家讨论一下。性…

python组合函数_Python---函数---参数组合

# 参数组合# 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数# 这5中都可以组合使用# 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数# 比如定义一个函数,包含上述若干…

Linux下TI omap芯片 MUX 配置分析(以AM335X芯片为例)

在移植内核的时候,通常会遇到引脚复用(MUX)的配置问题。在现在的Linux内核中,对于TI的ARM芯片,早已经有了比较通用的MUX配置框架。这对于许多TI的芯片都是通用的,这次看AM335X的代码顺手写一下分析&#xf…

python生成验证码_python之验证码生成(gvcode与captcha)

今天向大家总结一下python在做项目时用到的验证码生成工具:gvcode与captchagvcode全称:graphic-verification-code安装:pip install gvcode使用:import gvcodes, v gvcode.generate() #序列解包s.show() #显示生成的验证码图片pr…

设计模式 学习笔记(2)单一职责原则、开放封闭原则、依赖倒转原则

(3)单一职责原则 单一职责原则(SRP),就一个类而言,应该仅有一个引起它变化的原因。例如,我们在写一个窗体应用程序,一般都会生成一个Form这样的类,于是我们就把各种各样的…

是网关吗_智能家居网关功能这么多,你都知道吗?

在科技发达的今天,我们的生活也开始趋向于智能化,智能家居已经迎来了新时代。电动窗帘、扫地机器人、电视、空调等电器都能智联wifi,可是使用的时候得一个个去控制,数量多的话懒癌们肯定嫌麻烦的。所以很多聪明的人都选择安装智能…

eventfd以及epoll原理分析

这两天公司代码中用到了epoll。然后在跟同事闲扯的过程中发现了Linux中有eventfd。两者虽然名字看起来差不多,但是相关性倒是不多。为了弄明白这两个东西到底在内核上是怎么实现的,这两天将内核这两个部分的相关代码看了下,也终于明白了这两个…

一个数据包大小是多少k_算法交流: 6046 数据包的调度机制 【2.6基本算法之动态规划】...

【题目描述】 6046 数据包的调度机制 By OIer14wa随着 Internet的迅猛发展,多媒体技术和电子商务应用日益广泛,Internet上的服务质量(QoS,Qualityof Service)问题已越来越受到重视。网络中采用的数据包调度机制与网络的服务质量 QoS 有着密切的关系。研究表明传统的基于队列的调…

iOS vs. Android,应用设计该如何对症下药?

摘要:从iOS到Android,两大平台应用设计有何不同?又都存在什么样的问题?Android定制性太高,该如何进行UI设计?在CMDN CLUB第28期活动中,咕咚网高级产品经理王磊,从iOS、Android谈起&a…

postman数据保存在哪里_Postman 历史记录导出的解决方案

Postman 可以说是我在 CTF 中使用最多的工具了。它确实非常好用,但我并没有完全掌握它的使用之道,因此大量的历史请求堆在一起,显得环境无比混乱。 虽说是有想要改变的想法,但这些历史记录还是非常重要的,一时间难以割舍。于是便开始寻找导出的方案。 indexedDB 我们知道,…

cs8900a网卡驱动--寄存器

1. CS8900内部有一个4k的RAM用于访问其内部寄存器,称为PacketPage。 2. LineCTL 网卡状态设置 从上图看到,此寄存器的6,7位用于设置网卡的收发使能。8,9位用于设置网卡状态。是10BASE-T还是 AUI。下面这图更详细介绍了8&…

python输入序列语句_Python基础教程(一) - 序列:字符串、列表和元组

这一章我们主要研究这样一些类型,他们的成员是有序排列的,并且可以通过下标偏移量访问的,这类Python类型统称为序列,包括字符串、列表和元组。序列类型操作符成员关系操作符(in、not in):成员关系操作符是用来判断一个…

由于找不到openni2_Kinect开发教程八:OpenNI2显示深度、彩色及融合图像

在《Kinect开发教程二:OpenNI读取深度图像与彩色图像并显示》中,小斤介绍了OpenNI读取深度与彩色图像数据的方法,并且借助OpenCV进行显示。OpenNI2在接口上与OpenNI有了较大变化,具体更新可以查看《OpenNI Migration Guide》。从获…

目录服务用户OSX: ARD的基于目录服务用户权限

改章节笔者在上海游玩的时候突然想到的...今天就有想写几篇关于目录服务用户的笔记,所以回家到以后就奋笔疾书的写出来发布了 从Apple Remote Desktop 3.3开始, 加入了对目录服务用户/用户组的支持, 也就是说可以利用目录用户/用户组, 来定义该用户/用户组的ARD权限…

__builtin_expect详解

在GTK2.0源码中有很多这样的宏:G_LIKELY和G_UNLIKELY。比如下面这段代码: if (G_LIKELY (acat 1)) /* allocate through magazine layer */ { ThreadMemory *tmem thread_memory_from_self(); guint ix SLAB_INDEX (allocat…

python3界面实例_程序人生——python3下tkinter的界面示例

# written by wangluojisuanimport tkinterfrom tkinter import messageboxglobal main_formglobal lbl_nameglobal entry_nameglobal entry_text_varglobal chk_varglobal chkglobal text_areadef window_quit():if tkinter.messagebox.askyesno("提示", "退出…

jQuery图表插件 JS Charts

JS Charts 是一款免费的基于javascript的轻量级插件,用JS Charts 绘制图表是很轻松地事,因为你只需要关心客户端的脚本。 Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...Loading...转载于:https://www.cnb…

python官方文档怎么用_python帮助文档怎么使用

在python命令行中输入help(),然后再次输入time,可以获得很详细的模块文档;或者输入time.localtime,可以获得简略的函数参数显示;或者输入range,可以获得很详细的类的文档。方法一在python命令行输入以下内容…

ACCEPT

ACCEPT 章节:Linux 程序员手册 (2)更新:2010-09-10到 易美翻译 翻译名字 accept - 通过套接口接受一个连接 概要 #include Esys/types.h> /* 参看 “注意小节” */ #include Esys/socket.h>int accept(int sockfd, struct sockaddr *addr, sockl…