SVG.js+扩展工具(svg.select.js+svg.resize.js+svg.draggable.js)联合开发画图工具
先介绍各个js负责功能模块
svg.js —— 图形绘制、图形层次结构
svg.select.js(svg.select.css) —— 图形选择功能+图形选择样式
svg.resize.js —— 图形拉伸、收缩和旋转
svg.draggable.js ——图形拖动功能
请注意svg的版本问题,由于svg.select.js与3.0以上版本的svg.js暂时不兼容,一旦使用svg.select.js里的selectize方法便会抛出this.parents.set…错误,所以本文采用的svg版本是2.7
画板结构设计
首先对网页页面的功能区域进行划分:
左侧:作为工具选择栏,基础绘图功能:直线、折线、矩形、圆形、文字和图片
上侧:作为菜单栏
中部:作为绘图区域(draw-workarea)
右侧:作为图形属性显示窗口(properties)
下侧:操作提示框
文件准备,引入js文件
1 2 3 4 5 6 7 8 9 10 11 | // 先导入select.js的样式文件 <link href="${base}static/css/svg.select.min.css" rel="stylesheet" type="text/css"/> // 引用js文件 <script src="jquery-1.11.1.min.js"></script> <script src="svg.min.js"></script> <script src="svg.select.min.js"></script> <script src="svg.resize.min.js"></script> <script src="svg.draggable.js"></script> // 自定义js文件 <script src="main.js"></script> |
前端框架easyUI搭建页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <div class="easyui-layout" fit="true"> <div data-options="region:'north'" style="height:32px"> <div style="line-height:30px;padding:0 10px"> <a href="javascript:void(0)" class="easyui-linkbutton" id="pickProject" data-options="iconCls:'icon-box-project',plain:true" >选项1</a> </div> </div> <!-- 左侧工具栏区域--> <div data-options="region:'west'" style="width:160px;"> <div class="box-menu"> <dl id="menu1"> <dt>栏目1</dt> </dl> <dl id="menu2"> <dt>栏目2</dt> </dl> <dl id="menu3"> <dt>栏目3</dt> </dl> </div> </div> <!-- 画布工作区域--> <div data-options="region:'center'" style="background:#eeeeee"> <div class="box-workarea" id="box-workarea"> </div> </div> <!-- 消息区域--> <div data-options="region:'south'" style="height:26px;"> <div id="currentProjectMessage"></div> <div class="work-message" id="workMessage"><span>暂无消息</span></div> </div> </div> |
main.js
在js文件中,创建svg将其命名为svgroot,高和宽我们都设置为100%
1 | var svgRoot = SVG('box-workarea').size('100%','100%').id('svgroot') |
在3.0版本svg的创建
1 | var svgRoot = SVG('').addTo('box-workarea').size('100%','100%')id('svgroot') |
在svg.js中提供了检测浏览器是否支持svg的方法
1 2 3 4 5 | if (SVG.supported) { var svgRoot = SVG('box-workarea').size('100%','100%').id('svgroot') } else { alert('您的浏览器版本不支持SVG,请更换浏览器或者升级版本!') } |
创建完svg后,要创建一层grouproot层用来包裹所有元素,而图形之间连线用group_polyline包裹,每个图形都具有引脚的属性,用作触发连线的点,依次类推;接着用js定义图形对象
1 2 3 4 5 6 7 8 9 10 11 12 | var groupRoot = svg.group().id('groupRoot').size('3000px','1500px').attr('style','background-color:white') // all var groupPolyLine = groupRoot.group('group_polyline') // 连线层 var selectDrawObjList = []; // 用来存放选中的图形集合 var changeLineX = false; // 连线的起线变换是否位于X轴 var changeLiney = false; // 连线的起线变换是否位于Y轴 var component = new Component(); //创建图形操作对象 var originScale = 1; // 原始比例 var gridNumber = 10; //栅格数(移动步长) var isSelectBox = false;// 矩形拉选 var isMulSelect = false;// 是否处于多选状态 var isMoveMulSelect = false; // 是否开启批量移动 var currentMousePointer = {}; // 当前点击的点坐标 |
构造网格线,10为单位
1 2 3 4 5 6 7 8 9 | var outpattern = defs.pattern(10, 10, function(add) { add.path('M 10 0 L 0 0 0 10').fill('none').stroke({color:'#eee',width:0.5}) add.pattern(10,10,function(add){ add.rect().attr({width:10,height:10,fill:'url(#smallGrid)'}) add.path('M 100 0 L 0 0 0 100').fill('none').stroke({color:'#eee',width:1}) }).attr({'id':'grid'}) }).attr({'id':'smallGrid'}) var gridRect = svgRootGroup.rect().size('3000px','1500px').fill('url(#grid)').id('gridrect') // 网格 svgRootGroup.add(gridRect) |
连线移动产生的十字线的效果
1 2 3 | var polylineGroup = svgRootGroup.group().id('polylinegroup') // 连线组对象 var crossLine_X = svgRoot.path('M 0 0').attr({'id':'cross_line_x','stroke-dasharray':'2,2', 'fill':'none','stroke':'red', 'stroke-width':1}) var crossLine_Y = svgRoot.path('M 0 0').attr({'id':'cross_line_y','stroke-dasharray':'2,2', 'fill':'none','stroke':'red', 'stroke-width':1}) |
矩形选择框
1 | var selectRectBox = svgRoot.rect('0,0').attr({'id':'select_rect_box','stroke-dasharray':'3,3','fill':'none','stroke':'#0000ff','stroke-width':1}).hide() |
画板响应事件
画板的响应事件应该对应有:
mousedown、mouseup(移动结束或连线结束)、mousewheel(放大和缩小)、mousemove(移动)、keyup(鼠标按键)、mouseenter(移动画板时触发)、mouseleave(离开画板时触发)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | //画布键盘相应事件 $(document).on('keyup',function (e) { switch (e.keyCode) { case 46:;break; // 删除 case 82:;break; //旋转 //...复制等等 } }) svgRoot.on('mousedown',function(e){ // 判断是否鼠标左键 if(1 == e.which){ // 选中图形逻辑等等 if(){ } // 若无选中图形则开启矩形选择 if (!isSelectBox && isComplatePath) { isSelectBox = true isMulSelect = false isMoveMulSelect = false selectRectBox.attr({'x':e.offsetX,'y':e.offsetY,'width':0,'height':0}) currentMousePointer.x = e.offsetX currentMousePointer.y = e.offsetY selectRectBox.show() return; } } }) svgRoot.on('mouseup',function(e){ if(1 == e.which){ // 取消矩形选择 selectRectBox.hide() } }) // 鼠标滚轮发大和缩小 svgRoot.on('mousewheel',function(e){ var target = $(e.target).context var delta = e.originalEvent.wheelDelta var scale = originScale; originScale = originScale * 10 if(delta > 0 && originScale < 50) originScale += 2 else if (delta < 0 && originScale > 2 && originScale <= 50) originScale -= 2 originScale = originScale / 10 var t = svgRoot.transform(); svgRoot.scale(originScale,(e.offsetX - (t.e + svgRoot.node.clientWidth/2))/scale,(e.offsetY - (t.f + svgRoot.node.clientHeight/2))/scale) }) // 根据自己需求书写 svgRoot.on('mouseenter',function(e){ }) // 根据自己需求书写 svgRoot.on('mouseleave',function(e){ }) svgRoot.on('mousemove',function(e){ // 矩形选择 if (e.which == 1) { if (isSelectBox) { var x = currentMousePointer.x var y = currentMousePointer.y var d = {} d.x = Math.abs(x- e.offsetX) d.y = Math.abs(y- e.offsetY) if (x <= e.offsetX) { selectRectBox.attr('x',x); }else { selectRectBox.attr('x',e.offsetX); } if (y <= e.offsetY) { selectRectBox.attr('y',y); }else { selectRectBox.attr('y',e.offsetY); } selectRectBox.size(d.x,d.y) } // 调用自身的移动方法 if (isMulSelect && isMoveMulSelect) { selectDrawObjList.forEach(function(obj){ obj.group.dmove(d.x,d.y) }) } } }) |
图形对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | function DrawObj(){ this.id=''; // 图形id this.displayName=''; // 图形显示名称 this.value=''; // 数据化操作 this.foots=[]; // 引脚集合 this.location='' // 图形左上角坐标 this.svgcontent='' //svg描述字符串,如'<rect>xxxx</rect>,<line></line>' // 定义绘画方法 this.draw = function(){ // 每个图形创建一个group var group = grouproot.group() var groupfoot = group.group() // 包裹在图形group里 var objStrList = this.svgcontent.split(',') objStrList.forEach(function(v){ // svg.js有个通过字符串导入svg对象的方法 var obj = svgroot.svg(v) group.add(obj) }) // 添加引脚,用圆形来代替引脚 this.foots.forEach(function(foot){ var circle = svgroot.circle().attr({ id:foot.id, cx:foot.x, cy:foot.y, r:3, fill:'#ffffff00' //设置透明色将引脚隐藏 }) // 定义移动到引脚上时时间 circle.on('mouseover',function(e){ // 显示引脚 circle.fill('red') }) // 定义点击引脚创建连线事件 circle.on('mousedown',function(e){ // 创建连线事件 createPolyLine() }) // 定义离开引脚事件 circle.on('mouseout',function(e){ circle.fill('#ffffff00') }) }) // 定义group选中方法和resize方法,若是一维图形如直线、折线等,则selectize({deepSelect:true})可添加参数描述 group.selectize().resize(); // 定义图形拖动,这里添加一个移动时发生的事件 group.draggable().on('dragmove', e => { }) } // 删除图形 this.remove = function(){ if (!_this.isLock) { _this.group.remove() } } |
连线对象
1 2 3 4 5 6 7 8 9 | function SVGPolyline(){ this.startFoot = null; // 起点引脚对象 this.endFoot = null; // 终点引脚对象 this.points=[]; //连线点集合 // 移动图形时,改变连接处点 this.moveDrawObj(polyline){ } } |
图形操作对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | function Component(){ this.id=''; this.drawObjList=[]; // 取消全部选中器件 this.unSelectAll = function(){ selectDrawObjList.forEach(function(v){ v.unSelected() }) } // 添加图形 this.addDrawObj=function(obj){ } // 移除图形 this.removeDrawObj=function(obj){ obj.remove() } // 重置画板 this.resetDrawObj=function(){ } } function createPolyLine(){ var polyLine = svgroot.polyLine() ... } |
以上代码非完整,请勿全部复制粘贴,仅提供大体的结构思路,欢迎指正和讨论。楼主已经做出了一个版本了(正常使用),这是之前构思的初稿,基本思路没有问题。