7.组件连线(贝塞尔曲线)--从零起步实现基于Html5的WEB设计器Jquery插件(含源码)...
上節(jié)講到如何創(chuàng)建組件,清除設(shè)計(jì)器視圖,以及設(shè)計(jì)視圖的持久化和恢復(fù),本節(jié)將重點(diǎn)講如何實(shí)現(xiàn)組件間的連線,前面章節(jié)有提到為了方便從持久化文件中恢復(fù),組件和連線是分別存放的:nodes和lines對象,兩個組件實(shí)現(xiàn)連線主要也還是通過鼠標(biāo)拖動事件實(shí)現(xiàn),但前提是有一個連接點(diǎn)的概念,即我們要從組件上、下、左、右四個錨點(diǎn)中開始拖動,在拖動過程中繪制跟隨線,拖到目標(biāo)組件上時(shí)出現(xiàn)錨點(diǎn),在錨點(diǎn)上釋放鼠標(biāo),在兩個錨點(diǎn)間繪制連線,并將連線加到lines數(shù)組中。
下圖是要實(shí)現(xiàn)的錨點(diǎn)圖樣例:
?
錨點(diǎn)為紅色邊框,整個組件可以作為一個錨點(diǎn),同時(shí)四個x也可作為特定方位的錨點(diǎn),錨點(diǎn)出現(xiàn)時(shí)鼠標(biāo)為十字形狀,代表允許按下鼠標(biāo)拖動連線了。大家注意,我在打開按鈕的邊上增加了一個checkbox連線,用來指示是在連線狀態(tài)與否,取消這個勾選,是不會出現(xiàn)連線錨點(diǎn)的,選擇、拖動組件只有在非連線狀態(tài)下進(jìn)行,兩者互斥。
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方法用于測式當(dāng)前鼠標(biāo)位置是顯示哪個錨點(diǎn):整個組件/上/下/左/右,并移動對應(yīng)的錨點(diǎn)紅色矩形。render畫了一個紅色矩形和四個連接點(diǎn)x。
在VisualDesigner中增加lining屬性指示是否畫線狀態(tài),
在Component中增加activeConnector指示當(dāng)前活動的連接器錨點(diǎn)
在Component.init方法中增加了鼠標(biāo)進(jìn)入,退出后的連接點(diǎn)創(chuàng)建和刪除,如下代碼片斷:
Component.prototype.init = function (options) {if (options == undefined)options = {};this.properties = $.extend(options, Component.DEFAULTS);this.group = new paper.Group();this.designer = undefined; //當(dāng)前設(shè)計(jì)器,createElement時(shí)賦值var me = this;var drag = false;this.activateConnector = null; //活動的連線指示符this.group.onClick = function (event) {if (!me.designer.lining) //非畫線狀態(tài)才允許選中me.group.children[0].selected = !me.group.children[0].selected;}this.group.onMouseDown = function (event) {if (!me.designer.lining) //非畫線狀態(tài)才允許拖動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) //非畫線狀態(tài)才允許拖動 {if (me.activateConnector) //在拖動元素時(shí)如果有連線指示器則清除。 {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) //還沒有創(chuàng)建連接指示框,且當(dāng)前為連線狀態(tài) {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) { //當(dāng)前為連線狀態(tài),且移出了組件范圍 ,擦除連線指示框 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) { //當(dāng)前為連線狀態(tài),且在組件范圍 ,檢測四個邊線連線指示框 me.activateConnector.hiTest(event)}}return this;}這里說一個小插曲,因?yàn)橐诮M件的代碼里訪問設(shè)計(jì)器的成員(如是否畫線狀態(tài)visualDesigner.lining),我在Component里增加了一個designer對象來保存當(dāng)前設(shè)計(jì)器,并在代碼中訪問,可保存設(shè)計(jì)視圖時(shí)出現(xiàn)JSON對象序例化時(shí)出現(xiàn)遞歸的異常 ,因?yàn)樾蛄谢痭odes組件對象數(shù)組時(shí),每一個組件里有VisualDesigner對象而VisualDesigner對象里又有nodes對象的數(shù)組,首先想到的是特定的屬性不要序列化,查資料后發(fā)現(xiàn)JSON.stringify里有第二個參數(shù),可以為可序列化屬性名稱的白名單數(shù)組,也可以為函數(shù),此外因?yàn)閷傩悦Q并不完全確定,所以用函數(shù):
VisualDesigner.prototype.getContent = function () {debugger;return JSON.stringify({ "nodes": this.nodes, "lines": this.lines },function (k, v) {if (k == "designer") {return undefined;}return v;});}?依據(jù)面向?qū)ο蟮木幊谭椒▎我宦氊?zé)原則,增加了一個類(lineManager),專門用來管理連線的過程管理,在連線時(shí)要保持住前一個結(jié)點(diǎn),在拖動結(jié)束時(shí)畫出線,代碼如下:
function LineManager(designer) {this.designer = designer;this.line = null;//當(dāng)前跟隨線this.start = null;//當(dāng)前正在畫線的起點(diǎn)元素this.startPos=null;var tool=new paper.Tool();//設(shè)計(jì)器元素之外的移動也要顯示跟隨線,var me=this;tool.onMouseMove=function(event){me.draging(event.point);}tool.onMouseUp=function(event){//設(shè)計(jì)器元素之外的釋放不生成連線,清除已有開始結(jié)點(diǎn)等信息,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); //獲取當(dāng)前鼠標(biāo)位置處連接點(diǎn)的中央坐標(biāo)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); //獲取當(dāng)前鼠標(biāo)位置處連接點(diǎn)的中央坐標(biāo)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; //清除畫線狀態(tài),等待重新畫線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)}}}同時(shí)增加了曲線的類(貝塞爾曲線),
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
直接運(yùn)行查看
(本文為原創(chuàng),在引用代碼和文字時(shí)請注明出處)
轉(zhuǎn)載于:https://www.cnblogs.com/coolalam/p/9644645.html
總結(jié)
以上是生活随笔為你收集整理的7.组件连线(贝塞尔曲线)--从零起步实现基于Html5的WEB设计器Jquery插件(含源码)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [51Nod 1218] 最长递增子序列
- 下一篇: 梦到自己把项链扯断了好不好