上节讲到如何创建组件,清除设计器视图,以及设计视图的持久化和恢复,本节将重点讲如何实现组件间的连线,前面章节有提到为了方便从持久化文件中恢复,组件和连线是分别存放的: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

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

  1. 8.图片组件和动画效果--从零起步实现基于Html5的WEB设计器Jquery插件(含源码)...

    前面示例我建立了三种形状的组件,圆.矩形.椭圆,本节我将再扩展两种类型:图片和动画,并通过这个过程来论证前面OOP的编程是如何简化扩展工作的: 首先要在工具条里增加这两个组件,以便可以拖动: < ...

  2. 1. 概述--从零起步实现基于Html5的WEB设计器Jquery插件

    当一个软件为了达到一定的业务扩展性时(产品上线后限定范围内的用户需求无需变更代码,通过简单的配置可满足用户的要求),必然要求软件是可配置的. 笔者过去开发过很多配置型的管理软件,从可配置的表单设计(F ...

  3. 老男孩GO语言线下培训班1期整套教程(完整18天含源码)

    老男孩GO语言线下培训班1期整套教程(完整18天含源码) 老男孩教育-Go语言第一期(共18天 含课程源码) 课程详细目录: ├─L001-Go语言-mp4 │      01 Go开发1期 day1 ...

  4. SwiftUI 界面大全之文本折叠书签动画组件3D(中文教程含源码)

    实战需求 SwiftUI 界面大全之文本折叠书签动画组件3D(中文教程含源码) 本文价值与收获 看完本文后,您将能够作出下面的界面 基础知识 效果本身其实很简单,包括三件事: 图像的旋转 图像的垂直移 ...

  5. Flutter 组件之 Flutter高级自定义TabBar(教程含源码)

    实战需求 Flutter 组件之 Flutter高级自定义TabBar(教程含源码) 本文价值与收获 看完本文后,您将能够作出下面的界面 实战代码 import 'package:flutter/ma ...

  6. 零基础教你Unity制作像素鸟游戏 【文末源码】

    爆肝三天终于写完了,一文教你从零开启Unity制作像素鸟游戏 前言 一,新建目录 二,制作材质 三,场景搭建 四,创建地图 五,制作管道 六,创建主角 七,小鸟动起来 八,游戏状态控制 九,摄像机跟随 ...

  7. 基于微软企业库的AOP组件(含源码)

    软件开发,离不开对日志的操作.日志可以帮助我们查找和检测问题,比较传统的日志是在方法执行前或后,手动调用日志代码保存.但自从AOP出现后,我们就可以避免这种繁琐但又必须要实现的方式.本文是在微软企业库 ...

  8. 微信小程序 - 实现车牌输入功能,自定义车牌号输入法组件(键盘弹出后输入车牌号,可自定义各地区及界面样式)超详细注释组件插件示例源码

    效果图 本文实现了 输入车牌号码专用键盘组件,高效简洁无 BUG, 你可以直接复制组件源码,干净整洁的代码轻松移植到自己的项目中. 如下图所示,本文提供这样一个组件供你复制,快速完成功能. 组件源码 ...

  9. 【C#+SQL Server+打印组件】实现电商快递单打印系统 五:快递单打印模块设计(附源码和资源)

    需要源码和资源请点赞关注收藏后评论区留言私信~~~ 一.快递单打印模块概述 由于一个用户可能使用多种类型的快递单,所以在快递单打印窗体中提供了自由选择快递单种类的功能,在确定使用某一种快递单后,程序将 ...

最新文章

  1. DBA_Oracle基本体系内存和进程结构(概念)
  2. Apache与Tomcat整合
  3. 数据管理 - 每天5分钟玩转 Docker 容器技术(147)
  4. Arcgis10安装说明
  5. devops对大会的看法_哪条建议对您在DevOps的职业生涯影响最大?
  6. MySQL -- 创建用户并提升用户权限
  7. 从0-1背包问题到动态规划
  8. 数据库设计 表和表之间的三种关系
  9. 计算机重启 ie 被改,IE浏览器首页被篡改怎么办 如何重置IE还原到最初的安装状态...
  10. 百度换肤,表单全选案例
  11. 重归理性 国内SOA平台期待价值提升
  12. Markdown编辑器——Editor.md的使用
  13. 泡沫一般是执行什么标准呢?
  14. 三菱FX5U常见问题解析
  15. 记一次git冲突解决
  16. @Enumerated的使用
  17. mysql CONCAT和DATE_ADD函数的使用
  18. 课堂教学评价的主要内容
  19. 利用samba漏洞入侵linux主机(samba低版本漏洞利用)
  20. glut 配置,解决“gl/glut.h”: No such file or directory

热门文章

  1. 杜教筛--51nod1239 欧拉函数之和
  2. Educational Codeforces Round 25 C. Multi-judge Solving
  3. UVA 11825 状态压缩DP+子集思想
  4. IPHONE 开发 7 -- Object C 02 字符串NSString 与 char* ,字符串的遍历,字符串的比较,截取与大小写改变,搜索字符串与替换字符串...
  5. Windows10家庭版安装Docker Desktop(非Docker Toolbox)
  6. iview日期控件,双向绑定日期格式
  7. centos7安装oracle12c 三
  8. hadoop hive 2.1.1 将Hive启动为服务
  9. postman提取返回值
  10. 关于大学生玩网络游戏的调查问卷