关于svg图层添加元素拉伸效果实现
需求: 给定一个svg,根据用户手动去框选矩形面积来确定坐标,传递参数使后端在dwg源文件上达到圈定效果 依赖库的第三方库: @svgdotjs/svg.js、@svg-resize.js、@svg-select
效果如图
单机图源出现可框选效果,并可以从8个方向拉伸rect 元素
安装版本
{
...
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1",
...
},
...
}
注意:svg.select.js 和 svg.resize.js 来选择和自由缩放已绘制的SVG元素,作者说该插件并未完全支持svg.js,所以我们需要手动去源码里面修改一下 找到 node_modules > svg.select.js > dist > svg-select..js && svg-resize.js文件,在首行加入
import * as SVG from '@svgdotjs/svg.js'
使用过程中发现 svg3.0 不支持 set函数,所以我们需要在svg-select.js源码里重写添加一下
export default class Set {
constructor(members) {
if (members instanceof Set) {
this.members = members.members.slice()
} else {
Array.isArray(members) ? (this.members = members) : this.clear()
}
}
// Add element to set
add() {
var i,
il,
elements = [].slice.call(arguments)
for (i = 0, il = elements.length; i < il; i++) this.members.push(elements[i])
return this
}
// Remove element from set
remove(element) {
var i = this.index(element)
// remove given child
if (i > -1) this.members.splice(i, 1)
return this
}
// Iterate over all members
each(block) {
for (var i = 0, il = this.members.length; i < il; i++) block.apply(this.members[i], [ i, this.members ])
return this
}
// Restore to defaults
clear() {
// initialize store
this.members = []
return this
}
// Get the length of a set
length() {
return this.members.length
}
// Checks if a given element is present in set
has(element) {
return this.index(element) >= 0
}
// retuns index of given element in set
index(element) {
return this.members.indexOf(element)
}
// Get member at given index
get(i) {
return this.members[i]
}
// Get first member
first() {
return this.get(0)
}
// Get last member
last() {
return this.get(this.members.length - 1)
}
// Default value
valueOf() {
return this.members
}
// Get the bounding box of all members included or empty box if set has no items
bbox() {
// return an empty box of there are no members
if (this.members.length == 0) return new RBox()
// get the first rbox and update the target bbox
var rbox = this.members[0].rbox(this.members[0].doc())
this.each(function() {
// user rbox for correct position and visual representation
rbox = rbox.merge(this.rbox(this.doc()))
})
return rbox
}
}
extend(Element, {
set(members) {
return new Set(members)
}
})
试验一下
const canvas = SVG().addTo(svg);
let rect = canvas.rect(20, 20).move(100,100).attr({ fill:"white", stroke: "red", 'stroke-width': 1, 'fill-opacity': 0, 'stroke-opacity': 1 });
rect.selectize().resize(); // 调用
这样就创建了一个宽高都为20,坐标在x:100,y:100的矩形可拉伸元素
结合我们开头提到的需求集成一下
点击元素码后添加生成可框选的rect元素,这里SVG.size()目的是为了保证外层viewbox变化时内层svg不会出现不见得情况,得指定一下内层嵌套svg得宽高,和外层保持一致,这样效果就大功告成了,之后根据需求转换一下单位调用接口
addRect(svg, pointBox, hexCode) {
if (this.positionSet.has(hexCode)) {
// 已存在 唯一hexCode 无需再添加rect
console.log(11);
} else {
// 需要用size属性指定要添加的svg的大小,
const canvas = SVG().size('999.772mm', '999.772mm').addTo(svg);
let rect = canvas.rect(pointBox.width, pointBox.height).move(pointBox.x, pointBox.y).attr({ fill: "white", stroke: "red", 'stroke-width': 1, 'fill-opacity': 0, 'stroke-opacity': 1 });
rect.selectize().resize();
this.positionSet.add(hexCode)
}
},
关于dwg文件坐标和svg坐标不一致解决方案
例子: 在dwg中同一个点的坐标是(5000,4000),转成svg后同一个点在svg中的坐标是(300,200),注意:每份dwg的原点坐标不是固定的,比如这份的坐标原点是(2000,2300),所以不能直接用除法去换算。
解决方案:后端可以生成svg时两个给定的坐标点,如A点和B点,在生成元素时把在dwg中的坐标信息挂载到属性上,得到的元素就是
<svg>
...
<path code="pointA" positionX="5000" positionY="5000" ...> <path/>
<path code="pointB" positionX="4000" positionY="4000" ...> <path/>
</svg>
svg坐标计算
svg中path元素是根据一个个坐标点画出来的,如d="M112 116 155 177 ...",这些坐标的特点就是,左上角的点一定是X最小,Y最小,通过算法把最小坐标提取出来,就能得到标注点在svg上的坐标了,如下图则是dArr[0],dArr[1],同理可得B点的svg坐标
if (path.getAttribute('code') == 'pointA') {
const dArr = path.getAttribute('d').replace(/[a-zA-Z]/g, ' ').replace(/(^\s*)|(\s*$)/g, "").split(' ');
aPosition = {
x: dArr[0],
y: dArr[1],
positionX: parseFloat(path.getAttribute('positionx')),
positionY: parseFloat(path.getAttribute('positiony')),
}
}
取得单位比例
在这里计算出的值是个定值 57.234114 小数点后面忘记了,但一定是定值
const proportion = (aPosition.positionX - cPosition.positionX) / (aPosition.x - cPosition.x)
套公式
接下来就很好计算了, 现在给定一个C点,svg坐标假定是20,20,现在A点的svg坐标是30,30,A点的dwg坐标是2000,2000
// 则C点的dwg坐标
dwgC = {
x:A.dwgX + proportion * (C.svgX - A.svgX)
y:A.dwgY + proportion * (C.svgY - A.svgY)
}
// 代入值得到
x = 1430
y = 1430