仿真demo的画布实现

功能介绍

布局

  1. 左侧元件区,5个svg图标
  2. 中间svg画布,用户将元件拖入画布,创作自己的网
  3. 右侧按钮区,跳转使用(不是技术重点,不做介绍)

技术难点

  1. 元件由dom拖入到svg dom如何实现
  2. 在svg画布中需要进行拖拽和编辑,旋转操作,元件的编号需要跟着元件一起走
  3. 自动给每个拖入的元件按种类编号
  4. 每个元件有自己的属性,需要给每个元件id和属性值

库准备

  1. svg.js 2.7.0版本
  2. svg.select.js 选元件时的框
  3. svg.resize.js 用于select之后的编辑
  4. svg.draggy.js 在画布中拖拽元件,在svg.resize.js的引入包中
  5. svg.draggable.js 只支持3.0以上版本,所以此版本无法使用(svg.select.js在3.0以上版本不能使用)
  6. 移动端使用 touch.js 库识别手势操作

svg元件拖拽进入画布

概述

在元件区,每个svg是一个dom对象,使用img引入,利用touch.js,绑定touchstart事件,监听img相对于屏幕的位置,
在touchend的时候,使用屏幕位置减去画布的起始位置,就是svg应该存放在画布中的相对于画布的位置。画布的起始位置即画布dom元素的相对于屏幕位置坐标。

详细过程

  1. 在拖动过程中,img随着鼠标移动位置,在开始的一瞬间,在元件库相同位置创建一个相同的img;拖动结束时,img回到元件库中的初始位置,删除新建的img;如果结束位置在画布中,同时在画布的相对位置新建一个svg图形;否则不添加svg图形。
  2. 新建的svg图形为了方便之后的选择和改变大小以及拖拽,在ps或ai导出的时候,需要作为一个合并路径,也就是代码为一个path。
  3. 生成每个新的图形同时,每个svg需要根据类别编号,编号作为文字展示在图形的上方;为了之后的拖拽,文字和图形在一个group中。
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
let dx, dy;
let aGroup = draw.group()
// 发电机元件开始拖动
touch.on('#alternator', 'touchstart', function(ev){
ev.preventDefault();
dx = dx || 0;
dy = dy || 0;
let offx = dx + "px";
let offy = dy + "px";
$("<img src='assets/svg/icon1.svg' class='icon-item-pic' id='alternator-copy'></img>").appendTo($("#alternator-parent"))
.css('position','absolute').css('top',offx).css('left',offy)
});
// 发电机元件拖动时
touch.on('#alternator', 'drag', function(ev){
let offx = dx + ev.x + "px";
let offy = dy + ev.y + "px";
this.style.webkitTransform = "translate3d(" + offx + "," + offy + ",0)";
});
// 发电机元件拖动完成
touch.on('#alternator', 'dragend', function(ev){
// console.log("当前x值为:" + ev.x + ", 当前y值为:" + ev.y +".");
// 元件进入画布则创建,否则不创建
// 当前位置屏幕坐标
let dtop = ev.y+$('#alternator-copy').offset().top
let dleft = ev.x+$('#alternator-copy').offset().left
console.log(dtop,dleft)
if(dleft>=drawx && dleft<drawx+draww && dtop>drawy && dtop<drawy+drawh){
// 在画布中该位置创建一个该元件
let offx = dleft-drawx;
let offy = dtop-drawy;
let path = draw.path(`M26.5,0.261C12.032,0.261,0.261,12.032,0.261,26.5c0,14.469,11.771,26.239,26.239,26.239
c14.469,0,26.239-11.771,26.239-26.239C52.739,12.032,40.969,0.261,26.5,0.261z M26.5,47.659c-11.667,0-21.159-9.492-21.159-21.159
S14.833,5.341,26.5,5.341S47.659,14.833,47.659,26.5S38.167,47.659,26.5,47.659z M42.958,24.523l-5.095,0.422c0.125,0.5,0.174,1.564,0.173,1.563
c0.003,0.005,0.131,0.496-0.029,2.016c-0.108,1.021-0.61,1.955-1.382,2.563c-0.865,0.684-2.064,0.945-3.463,0.766
c-2.297-0.299-3.559-1.373-4.518-6.675c-0.584-3.22-2.47-5.866-5.175-7.26c-2.564-1.322-5.522-1.379-8.113-0.156
c-4.844,2.284-5.468,7.998-5.073,11.017l4.96-0.639c-0.006-0.046-0.518-4.554,2.245-5.855c1.174-0.553,2.519-0.525,3.692,0.079
c1.332,0.686,2.235,2.002,2.544,3.706c0.923,5.105,2.627,9.941,8.793,10.743c0.528,0.068,1.029,0.1,1.508,0.1
c2.676,0,4.545-0.989,5.697-1.899c1.822-1.438,3.02-3.618,3.266-5.972C43.202,26.329,43.113,25.898,42.958,24.523z`)
path.fill({ color: '#fff', opacity: 1 })
path.x(offx-10).y(offy+10)
path.scale(1.3, 1.3)
path.touchstart(function() {
event.preventDefault()
this.selectize().resize({snapToGrid:1, snapToAngle:1})
//遍历其他元素,全部取消选择
})
path.touchmove(function() {
event.preventDefault()
this.selectize(false)
})
path.touchcancel(function() {
event.preventDefault()
this.selectize(false)
})
let text = draw.text(`G ${a_index}`).fill({ color: '#fff', opacity: 1 })
text.x(offx).y(offy-30)
this.style.webkitTransform = "translate3d(" + 0 + "," + 0 + ",0)"
$("#alternator-copy").remove()
let classname = `a-group${a_index}`
let group = aGroup.group().addClass(classname)
group.add(path)
group.add(text)
group.draggy()
a_index ++
}else{
// 删除子元素,this回到初始位置
this.style.webkitTransform = "translate3d(" + 0 + "," + 0 + ",0)"
$("#alternator-copy").remove()
}
})