fabric 组合group 增加边框
本身是没有边框属性支持的,所以想要边框就得手写:
大体逻辑:
1、组合时,额外增加 rect 模拟边框样式,保存最初组合时 left top width height 属性
2、拆分时,需计算元素 top left(缩放等属性,因为group 移除元素时使用了 removeWithUpdate,故省却计算)
3、更新边框属性时,需删除对应边框(通常为组合最后一个元素),再根据当前 left top 和 历史 width height 计算出边框值添加到组合中
代码:
import { fabric } from "fabric";
import { KeyCode } from "./key-code";
/**
* 组合
* 使用添加 边框方法,模拟 group 的边框
* 组合时,额外增加 rect 模拟边框样式,保存最初组合时 left top width height 属性
* 拆分时,需计算元素 top left(缩放等属性,因为group 移除元素时使用了 removeWithUpdate,故省却计算)
* 更新边框属性时,需删除对应边框(通常为组合最后一个元素),再根据当前 left top 和 历史 width height 计算出边框值添加到组合中
*/
export class GroupBoxTest {
constructor(canvas) {
// 创建多个 rect 对象
let rect = new fabric.Rect(GroupBoxTest.defaultRect('red', 100));
let rect1 = new fabric.Rect(GroupBoxTest.defaultRect('yellow', 250));
let rect2 = new fabric.Rect(GroupBoxTest.defaultRect('blue', 400));
canvas.add(rect, rect1, rect2);
// 绑定键盘事件
this.bindKeyBoard(canvas);
}
/**
* 绑定键盘事件
* @param canvas
*/
bindKeyBoard(canvas) {
$(document).on('keydown', (e) => {
const key = e.originalEvent.keyCode;
let objs = null;
switch (key) {
case KeyCode.Q: // 打印显示
console.log(canvas.getObjects());
break;
case KeyCode.W: // 组合
// 获取选中元素
objs = canvas.getActiveObjects();
// 增加判断只能组合未组合图形
let hasGroup = false;
objs.forEach((item) => {
if (item.id.match(/group/)) {
hasGroup = true;
return false;
}
});
if (hasGroup) {
return false;
}
// 清除活动对象,活动对象已成组,会导致位置紊乱
canvas.discardActiveObject();
// 再进行组合
let group = new fabric.Group(objs, {
id: GroupBoxTest.getId('group'),
lockRotation: true, // 组合元素不给旋转
});
// 给组合增加 矩形背景,用于设置 模拟组容器边框等属性
let groupInfo = {
width: group.width - 1,
height: group.height - 1,
left: group.get('left'),
top: group.get('top')
};
let borderInfo = new fabric.Rect(GroupBoxTest.defaultBorder(groupInfo));
group.addWithUpdate(new fabric.Rect(borderInfo));
canvas.add(group);
// 删掉之前对象
let ids = objs.map((o) => {
return o.id;
});
canvas.getObjects().forEach((item) => {
if (ids.includes(item.id)) {
canvas.remove(item);
}
});
canvas.setActiveObject(group).renderAll();
break;
case KeyCode.E: // 拆分
objs = canvas.getActiveObjects();
if (objs.length === 1 && objs[0].id.match(/group/)) {
// 组标志,同时拆分1 个组
canvas.discardActiveObject().renderAll();
let group = objs[0];
let childrenLen = group.size();
// 删除边框
group.removeWithUpdate(childrenLen - 1);
childrenLen -= 1;
// 处理内部元素
while (childrenLen > 0) {
let child = group.item(childrenLen - 1);
this.deleteGroup(child);
child.setCoords();
canvas.add(child);
childrenLen--;
}
canvas.remove(group).renderAll();
canvas.discardActiveObject();
}
break;
case KeyCode.R: // 更新表框属性
objs = canvas.getActiveObjects();
if (objs.length === 1 && objs[0].id.match(/group/)) {
let group = objs[0];
let childrenLen = group.size();
group.removeWithUpdate(group.item(childrenLen - 1));
let colorA = ['purple', 'grey', 'green', 'lightblue', 'orange', 'red'];
let r = Math.round(Math.random() * 20);
let sw = Math.round(Math.random() * 10);
let c = Math.round(Math.random() * 5);
let borderInfo = {
rx: r,
ry: r,
width: group.width - 1,
height: group.height - 1,
left: group.left,
top: group.top,
stroke: colorA[c],
strokeWidth: sw,
};
group.addWithUpdate(new fabric.Rect(GroupBoxTest.defaultBorder(borderInfo)));
canvas.renderAll();
}
break;
}
});
}
/**
* 删除组信息,并计算元素相对信息数据
* @param ele
*/
deleteGroup(ele) {
ele.top = ele.top + ele.group.top + ele.group.height * 0.5;
ele.left = ele.left + ele.group.left + ele.group.width * 0.5;
ele.width = ele.width * ele.scaleX;
ele.height = ele.height * ele.scaleY;
ele.scaleX = 1;
ele.scaleY = 1;
delete ele.group;
}
/**
* 获取 id = type_time
* @param type
* @returns {string}
*/
static getId(type) {
return type + '_' + new Date().getTime();
}
/**
* 获取默认测试矩形
* @param color
* @param left
* @returns {{top: number, left, width: number, id: string, fill, height: number}}
*/
static defaultRect(color, left) {
return {
fill: color,
height: 100,
left: left,
top: 100,
width: 100,
id: GroupBoxTest.getId('rect'),
}
}
/**
* 获取模拟边框默认样式
* @param info
* @returns {{strokeWidth: number, top: number, left: number, rx: number, ry: number, width: *, fill: string, stroke: string, height: *}}
*/
static defaultBorder(info) {
let rx = info.rx || 0;
let ry = info.ry || 0;
let strokeWidth = info.strokeWidth || 1;
let stroke = info.stroke || '#000';
return {
ry: rx,
rx: ry,
width: info.width + rx + strokeWidth,
height: info.height + ry + strokeWidth,
left: info.left - rx * 0.5 - strokeWidth,
top: info.top - ry * 0.5 - strokeWidth,
fill: 'transparent',
stroke: stroke,
strokeWidth: strokeWidth,
}
}
}

浙公网安备 33010602011771号