7.组件连线(贝塞尔曲线)--从零起步实现基于Html5的WEB设计器Jquery插件(含源码)...

上节讲到如何创建组件,清除设计器视图,以及设计视图的持久化和恢复,本节将重点讲如何实现组件间的连线,前面章节有提到为了方便从持久化文件中恢复,组件和连线是分别存放的:nodes和lines对象,两个组件实现连线主要也还是通过鼠标拖动事件实现,但前提是有一个连接点的概念,即我们要从组件上、下、左、右四个锚点中开始拖动,在拖动过程中绘制跟随线,拖到目标组件上时出现锚点,在锚点上释放鼠标,在两个锚点间绘制连线,并将连线加到lines数组中。

下图是要实现的锚点图样例:

 

锚点为红色边框,整个组件可以作为一个锚点,同时四个x也可作为特定方位的锚点,锚点出现时鼠标为十字形状,代表允许按下鼠标拖动连线了。大家注意,我在打开按钮的边上增加了一个checkbox连线,用来指示是在连线状态与否,取消这个勾选,是不会出现连线锚点的,选择、拖动组件只有在非连线状态下进行,两者互斥。

    function Connector(node) {this.node = node;this.group = null;}Connector.prototype = {destroy: function () {this.group.remove();},hiTest: function (event) {var bounds = this.node.getBound();if (event.point.x >= bounds.x - 5 && event.point.x <= bounds.x + 5 && event.point.y >= bounds.y + bounds.height / 2 - 5 && event.point.y <= bounds.y + bounds.height / 2 + 5){//在左连线指示器框中this.group.children[0].bounds.x = bounds.x - 5;this.group.children[0].bounds.y = bounds.y + bounds.height / 2 - 5;this.group.children[0].bounds.width = 10;this.group.children[0].bounds.height = 10;}else if (event.point.x >= bounds.x + bounds.width - 5 && event.point.x <= bounds.x + bounds.width  + 5 && event.point.y >= bounds.y + bounds.height / 2 - 5 && event.point.y <= bounds.y + bounds.height / 2 + 5) {//在右连线指示器框中this.group.children[0].bounds.x = bounds.x + bounds.width  - 5;this.group.children[0].bounds.y = bounds.y + bounds.height / 2 - 5;this.group.children[0].bounds.width = 10;this.group.children[0].bounds.height = 10;}else if (event.point.x >= bounds.x + bounds.width / 2 - 5 && event.point.x <= bounds.x + bounds.width / 2  + 5 && event.point.y >= bounds.y  - 5 && event.point.y <= bounds.y  + 5) {//在上连线指示器框中this.group.children[0].bounds.x = bounds.x + bounds.width / 2  - 5;this.group.children[0].bounds.y = bounds.y  - 5;this.group.children[0].bounds.width = 10;this.group.children[0].bounds.height = 10;}else if (event.point.x >= bounds.x + bounds.width / 2 - 5 && event.point.x <= bounds.x + bounds.width / 2  + 5 && event.point.y >= bounds.y + bounds.height - 5 && event.point.y <= bounds.y + bounds.height+ 5) {//在下连线指示器框中this.group.children[0].bounds.x = bounds.x + bounds.width / 2  - 5;this.group.children[0].bounds.y = bounds.y + bounds.height  - 5;this.group.children[0].bounds.width = 10;this.group.children[0].bounds.height = 10;}else{this.group.children[0].bounds.x = bounds.x this.group.children[0].bounds.y = bounds.y;this.group.children[0].bounds.width = bounds.width;this.group.children[0].bounds.height = bounds.height;}},render: function () {var me = this;var color = 'white';this.group = new paper.Group();var rect = new paper.Path.Rectangle({point: [this.node.getBound().x, this.node.getBound().y],size: [this.node.getBound().width, this.node.getBound().height],strokeColor: 'red',strokeWidth: 2})rect.onMouseDown = function (event) {debugger;};this.group.addChild(rect);var bounds = this.node.getBound();var topCross1 = new paper.Path.Line({ from: [bounds.x + bounds.width / 2 - 2.5, bounds.y - 2.5], to: [bounds.x + bounds.width / 2 + 2.5, bounds.y + 2.5], strokeColor: 'blue' });this.group.addChild(topCross1);var topCross2 = new paper.Path.Line({ from: [bounds.x + bounds.width / 2 - 2.5, bounds.y + 2.5],to: [bounds.x + bounds.width / 2 + 2.5, bounds.y - 2.5], strokeColor: 'blue' });this.group.addChild(topCross2);var rightCross1 = new paper.Path.Line({ from: [bounds.x + bounds.width - 2.5, bounds.y + bounds.height / 2 - 2.5], to: [bounds.x + bounds.width + 2.5, bounds.y + bounds.height / 2 + 2.5], strokeColor: 'blue' });this.group.addChild(rightCross1);var rightCross2 = new paper.Path.Line({ from: [bounds.x + bounds.width - 2.5, bounds.y + bounds.height / 2 + 2.5], to: [bounds.x + bounds.width + 2.5, bounds.y + bounds.height / 2 - 2.5], strokeColor: 'blue' });this.group.addChild(rightCross2);var leftCross1 = new paper.Path.Line({ from: [bounds.x - 2.5, bounds.y + bounds.height / 2 - 2.5], to: [bounds.x + 2.5, bounds.y + bounds.height / 2 + 2.5], strokeColor: 'blue' });this.group.addChild(leftCross1);var leftCross2 = new paper.Path.Line({ from: [bounds.x - 2.5, bounds.y + bounds.height / 2 + 2.5], to: [bounds.x + 2.5, bounds.y + bounds.height / 2 - 2.5], strokeColor: 'blue' });this.group.addChild(leftCross2);var bottomCross1 = new paper.Path.Line({ from: [bounds.x + bounds.width / 2 - 2.5, bounds.y + bounds.height - 2.5], to: [bounds.x + bounds.width / 2 + 2.5, bounds.y + bounds.height + 2.5], strokeColor: 'blue' });this.group.addChild(bottomCross1);var bottomCross2 = new paper.Path.Line({ from: [bounds.x + bounds.width / 2 - 2.5, bounds.y + bounds.height + 2.5], to: [bounds.x + bounds.width / 2 + 2.5, bounds.y + bounds.height  - 2.5], strokeColor: 'blue' });this.group.addChild(bottomCross2);this.group.bringToFront();var drag = false;return this;}};

上面代码hiTest方法用于测式当前鼠标位置是显示哪个锚点:整个组件/上/下/左/右,并移动对应的锚点红色矩形。render画了一个红色矩形和四个连接点x。

在VisualDesigner中增加lining属性指示是否画线状态,

在Component中增加activeConnector指示当前活动的连接器锚点

在Component.init方法中增加了鼠标进入,退出后的连接点创建和删除,如下代码片断:

    Component.prototype.init = function (options) {if (options == undefined)options = {};this.properties = $.extend(options, Component.DEFAULTS);this.group = new paper.Group();this.designer = undefined; //当前设计器,createElement时赋值var me = this;var drag = false;this.activateConnector = null; //活动的连线指示符this.group.onClick = function (event) {if (!me.designer.lining) //非画线状态才允许选中me.group.children[0].selected = !me.group.children[0].selected;}this.group.onMouseDown = function (event) {if (!me.designer.lining) //非画线状态才允许拖动drag = (event.event.button == 0);else {drawing = true;}}this.group.onMouseUp = function () {drag = false;document.body.style.cursor = 'default';}this.group.onMouseDrag = function (event) {if (drag && !me.designer.lining) //非画线状态才允许拖动
            {if (me.activateConnector) //在拖动元素时如果有连线指示器则清除。
                {me.activateConnector.destroy();me.activateConnector = null;}me.properties.x += event.delta.x;me.properties.y += event.delta.y;this.translate(event.delta.x, event.delta.y);document.body.style.cursor = 'move';}}this.group.onMouseEnter = function (event) {if (!me.activateConnector && me.designer.lining) //还没有创建连接指示框,且当前为连线状态
            {me.designer.selectAll(false);//取消选中所有元素,if anyme.activateConnector = new Connector(me).render();document.body.style.cursor = 'crosshair';}}this.group.onMouseLeave = function (event) {if (me.designer.lining && me.activateConnector) { //当前为连线状态,且移出了组件范围 ,擦除连线指示框
                me.activateConnector.destroy();me.activateConnector = null;console.log("delete in group")document.body.style.cursor = 'default';}}this.group.onMouseMove = function (event) {if (me.designer.lining && me.activateConnector) { //当前为连线状态,且在组件范围 ,检测四个边线连线指示框
                me.activateConnector.hiTest(event)}}return this;}

这里说一个小插曲,因为要在组件的代码里访问设计器的成员(如是否画线状态visualDesigner.lining),我在Component里增加了一个designer对象来保存当前设计器,并在代码中访问,可保存设计视图时出现JSON对象序例化时出现递归的异常 ,因为序列化nodes组件对象数组时,每一个组件里有VisualDesigner对象而VisualDesigner对象里又有nodes对象的数组,首先想到的是特定的属性不要序列化,查资料后发现JSON.stringify里有第二个参数,可以为可序列化属性名称的白名单数组,也可以为函数,此外因为属性名称并不完全确定,所以用函数:

    VisualDesigner.prototype.getContent = function () {debugger;return JSON.stringify({ "nodes": this.nodes, "lines": this.lines },function (k, v) {if (k == "designer") {return undefined;}return v;});}

 依据面向对象的编程方法单一职责原则,增加了一个类(lineManager),专门用来管理连线的过程管理,在连线时要保持住前一个结点,在拖动结束时画出线,代码如下:

    function LineManager(designer) {this.designer = designer;this.line = null;//当前跟随线this.start = null;//当前正在画线的起点元素this.startPos=null;var tool=new paper.Tool();//设计器元素之外的移动也要显示跟随线,var me=this;tool.onMouseMove=function(event){me.draging(event.point);}tool.onMouseUp=function(event){//设计器元素之外的释放不生成连线,清除已有开始结点等信息,if (me.line)me.line.remove();me.start=null;me.startPos=null;me.line=null;}}LineManager.prototype = {dragStart: function (co,pos) {this.start = co;var xy = co.node.getConnectorCenter(pos); //获取当前鼠标位置处连接点的中央坐标this.startPos=xy;this.line = new paper.Path.Line({from: [xy.x, xy.y],to: [xy.x, xy.y],strokeWidth: 2,strokeColor: 'red'});},draging: function (pos) {if (this.line !== null ) {var txy = this.calcLine(this.startPos.x, this.startPos.y, pos.x, pos.y);this.line.set({ pathData: 'M' + this.startPos.x + ',' + this.startPos.y + ' L' + txy.x + ',' + txy.y });}},dragEnd:function(co,pos){var xy = co.node.getConnectorCenter(pos); //获取当前鼠标位置处连接点的中央坐标if (this.line !== null  ) {if (this.start.node.properties.id!=co.node.properties.id){this.designer.createLine("曲线",{targetType:co.node.getConnectorDirection(this.startPos,pos),source:this.start.node.properties.id,target:co.node.properties.id,sxy:this.startPos,txy:xy});}this.line.remove();}this.start=null; //清除画线状态,等待重新画线this.startPos=null;},calcLine: function (x1, y1, x2, y2) {var vx = x2 - x1;var vy = y2 - y1;var d = Math.sqrt(vx * vx + vy * vy);vx /= d;vy /= d;d = Math.max(0, d - 5);return {'x': Math.round(x1 + vx * d),'y': Math.round(y1 + vy * d)}}}

同时增加了曲线的类(贝塞尔曲线),

function BezierLine() {}BezierLine.prototype = $.extend({}, Component.prototype);BezierLine.prototype = $.extend(BezierLine.prototype, {render: function (options) {this.properties.typeName = "曲线";this.properties.strokeWidth = 2;this.properties.strokeColor = 'red';this.properties=$.extend(this.properties,options)this.properties.x = Math.min(this.properties.sxy.x, this.properties.txy.x);this.properties.y = Math.min(this.properties.sxy.y, this.properties.txy.y);this.properties.width = Math.abs(this.properties.txy.x - this.properties.sxy.x);this.properties.height = Math.abs(this.properties.txy.y - this.properties.sxy.y);var wire = new paper.Path(this.calcPath(this.properties.targetType, this.properties.sxy.x, this.properties.sxy.y, this.properties.txy.x, this.properties.txy.y));wire.strokeWidth = this.properties.strokeWidth;wire.strokeColor=this.properties.strokeColor;wire.sendToBack();this.group=new paper.Group();this.group.addChild(wire);//this.group.translate(this.properties.x, this.properties.y);return this;},calcPath:function(type, x1, y1, x2, y2){var path= "";if(type =="left" || type == "right")path= 'M ' + x1 + ', ' + y1 + 'C ' +(x1 + (x2 - x1) / 2) + ', ' + y1 + ' ' +(x2 - (x2 - x1) / 2) + ', ' + y2 + ' ' +x2 + ', ' + y2;else if (type=="up" || type == "down")path='M' + x1 + ', ' + y1 + 'C ' +x1 + ', ' + (y1 + (y2 - y1) / 2) + ' ' +x2 + ', ' + (y2 - (y2 - y1) / 2) + ' ' +x2 + ', ' + y2;return path;}});

最后效果图如下:

 

 

源代码:sample.1.5.rar

直接运行查看

(本文为原创,在引用代码和文字时请注明出处)

转载于:https://www.cnblogs.com/coolalam/p/9644645.html

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

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

相关文章

定位排查工作流的计算结果数据量不符合预期的方法

近期有发现一些用户在咨询&#xff0c;为什么数据从数据源出来后&#xff0c;经过了一些计算&#xff0c;结果不符合预期了。最常见的是说&#xff0c;为什么我的数据在Mysql里有xx条&#xff0c;怎么到MaxCompute里算了下结果变了。因为这是两个不同的系统&#xff0c;我们又没…

canvas 插件_基于canvas的JavaScript 二维码生成工具——QRCanvas

介绍在我们日常的开发中&#xff0c;特别是在现代的社会环境下&#xff0c;二维码的应用可谓是丰富多彩&#xff0c;各种各样让人眼花缭乱的二维码&#xff0c;可见二维码已经渗透进我们生活的方方面面&#xff0c;也可以说目二维码确确实实方便了我们的生活。因为作为开发人员…

消息队列NetMQ 原理分析2-IO线程和完成端口

目录 前言介绍目的IO线程初始化IO线程Proactor启动Procator线程轮询处理socketIOObject总结前言 介绍 [NetMQ](https://github.com/zeromq/netmq.git)是ZeroMQ的C#移植版本,它是对标准socket接口的扩展。它提供了一种异步消息队列,多消息模式,消息过滤&#xff08;订阅&#xf…

VC连接mysql数据库错误:libmysql.lib : fatal error LNK1113: invalid machine 解决方法

VC连接MySQL的配置过程在上一篇博文中&#xff0c;不过当你设置好&#xff0c;以为万事大吉的时候&#xff0c;运行却出现这个错误&#xff1a;libmysql.lib : fatal error LNK1113: invalid machine type。 无效的机器类型&#xff0c;真的是很让人捉急。 发生这个错误的原因是…

linux 内存泄漏 定位,一种内存泄露检查和定位的方法

一个系统后台服务进程&#xff0c;可能包括多个线程&#xff0c;在生成环境下要求系统程序能够稳定长时间稳定运行而不宕机。其中一个基本的前提就是需要保证系统程序不存在内存泄露。那么&#xff0c;该如何判读系统程序是否存在内存泄露呢&#xff1f;如果存在&#xff0c;又…

ifconfig命令找不到_02. Linux命令之查看网络连接

1. 查看网络连接数和端口使用 netstat 命令查看网络连接情况netstat -anp参数&#xff1a;-a 显示所有选项-t (tcp)仅显示tcp相关选项-u (udp)仅显示udp相关选项-n 拒绝显示别名&#xff0c;能显示数字的全部转化成数字。-p 显示建立相关链接的程序名关键列解释:Proto 表示协议…

python学习之模块(pip),列表生成式,模块操作mysql,excel

python基础 生成式 列表生成式  格式 [表达式 for 表达式 in 迭代对象 (可加判断)] 原&#xff1a; 1 res1 [] 2 for i in range(1,5): 3   res1.append(i) 4 print(res1) 改&#xff1a; 1 res2 [i for i in range(1,5)] 2 print(res2) 字典生成式  格式 {key:value f…

linux驱动read函数 copytouser,Linux驱动编程 step-by-step (五)主要的文件操作方法实现...

主要的文件操作方法实现文件操作函数有很多的操作接口&#xff0c;驱动编程需要实现这些接口&#xff0c;在用户编程时候系统调用时候会调用到这些操作structfile_operations {...loff_t (*llseek) (structfile *, loff_t,int);ssize_t (*read) (structfile *,char__user *,siz…

基于光线追踪的渲染中景深(Depth of field)效果的实现

图形学离线渲染中常用的透视摄像机模型时根据小孔成像的原理建立的&#xff0c;其实现通常是从向成像平面上发射ray&#xff0c;并把trace这条ray的结果作为成像平面上对应交点的采样结果。即&#xff1a; 图片来自《Fundamentals of Computer Graphics》 现实中的镜头拍摄的图…

带你制作百词斩单词表读写插件

上篇博文简单的介绍了一下Chrome插件&#xff0c;今天就与大家分享一下我做的这款有实际意义的插件吧。 做这款插件主要是用百词斩站点进行单词学习时&#xff0c;遇到的一点点闹心事儿。在单词表中不能听发音。也不能练习拼写。所以才忍无可忍的做了这么一款插件。自我感觉还是…

iphone各机型参数对比_带你了解新款iPhone 12系列四款机型

2020年10月14日凌晨1&#xff1a;00&#xff0c;苹果召开新品发布会&#xff0c;发布了新款iPhone 12系列手机&#xff0c;“果粉”们期待已久的iPhone 12终于来了。iPhone 12系列手机共有四款机型&#xff0c;分别是iPhone 12 mini、iPhone 12、iPhone 12 Pro、iPhone 12 Pro …

高并发第一弹:准备阶段 了解高并发

高并发第一弹:准备阶段 了解高并发 首先需要知道什么并发, 什么是高并发. 并发: 关于并发的学习&#xff0c;可以从JDK提供的并发包为核心开始&#xff0c;许多其他的类和封装都是对其进行扩展或者补充&#xff0c;我们来看一下Java并发包(java.util.concurrent包&#xff0c;简…

matlab立体坐标定位_【半导光电】基于光电探测器的激光章动定位算法(二)

今日光电有人说&#xff0c;20世纪是电的世纪&#xff0c;21世纪是光的世纪&#xff1b;知光解电&#xff0c;再小的个体都可以被赋能。欢迎来到今日光电&#xff01;----与智者为伍 为创新赋能----1. 章动定位算法实验前&#xff0c;首先需要对光路进行调节&#xff0c;保证经…

Android:支持多选的本地相册

前段时间在做一个动态发布功能&#xff0c;需要用到图片上传。一开始直接调用的系统相册和相机&#xff0c;由于系统相机不支持多选&#xff0c;就花点时间做了个本地相册&#xff0c;在此开源下。 先上截图&#xff0c;依次为选择相册界面、相册详情界面、查看图片大图界面 相…

心灵与大脑

2019独角兽企业重金招聘Python工程师标准>>> http://blog.sina.com.cn/s/blog_6f034fc30102f2tg.html 转载于:https://my.oschina.net/chirnson/blog/832011

python入门心得_记初学python的一些心得

人生苦短&#xff0c;我用python&#xff01; 其实我自学python也很长一段时间了&#xff0c;但总是去更换学习资料&#xff0c;搞的现在学的不是很好&#xff0c;因为没更换次资料都要从头开始学起&#xff0c;那么分享下我的学习战况吧&#xff0c;不是很好&#xff0c;还将就…

16.U-boot的工作流程分析-2440

16.U-boot的工作流程分析-2440 分析的流程&#xff1a; 程序入口 第一阶段程序分析 第二阶段程序分析 2440开发板&#xff1a; 1.uboot的入口&#xff1a; 要看uboot工程的入口&#xff0c;首先打开顶层目录的Makefile&#xff1a; Uboot所支持的开发板&#xff0c;在顶层的Ma…

如何使用Redis做MySQL的缓存

应用Redis实现数据的读写&#xff0c;同时利用队列处理器定时将数据写入mysql。 同时要注意避免冲突&#xff0c;在redis启动时去mysql读取所有表键值存入redis中&#xff0c;往redis写数据时&#xff0c;对redis主键自增并进行读取&#xff0c;若mysql更新失败&#xff0c;则需…

psychopy 与脑电打码 eeg

2019独角兽企业重金招聘Python工程师标准>>> 实验程序就不放了&#xff0c;这里主要放如何向串口发送打码的代码 实际上&#xff0c;给脑电打码的本质就是向串口发送一个字符&#xff0c;脑电的程序会自动在收到该字符的同时在脑电数据上进行标记。以下代码打开了一…

mysql -- 索引的使用

普通索引&#xff1a;用于提升查询速度唯一索引&#xff1a;用于提升查询速度&#xff0c;还要求字段值不得重复主键索引&#xff1a;唯一性且不为空的索引全文索引&#xff1a;用于大量文本搜索中建立的索引虽然索引有好处&#xff0c;但是凡是都有俩面性&#xff0c;提高效率…