一、选中多个模块组合与拆分组
1、组合
组合是要把选中的模块组合成一个组
选中的模块:
card.getActiveObject()
成组:
// 成组 、重新渲染
card.getActiveObject().toGroup()
card.renderAll()
2、拆分组合
// 获取选中的组合模块,进行组合拆分、重新渲染
card.getActiveObject().toActiveSelection();
card.renderAll()
二、loadSVGFromString 加载 SVG 导致子元素left和top值不对
问题:通过 loadSVGFromString
加载 SVG
,渲染完成之后回导致 里面的子模块 left
和 top
的值不正确
原因:这个是因为,通过 loadSVGFromString
加载的 SVG
回被组成一个组合,组合里面的子元素按照组合的基点来算 left
和 top
值,而组合的基点所在位置是中心点,不是我们通常的canvas的顶点位置
所以可以先进行组合、在进行组合拆分
fabric.loadSVGFromString(svgContext, (objects) => {
// 先进行 组合成组
const group1 = new fabric.Group(objects, {
left: 0,
top: 0
})
// 把组合 add 进 card
card.add(group1)
// 把组合设置为选中
card.setActiveObject(group1)
// 把选中的组合 进行拆分组
card.getActiveObject().toActiveSelection();
// 把拆分开的每一个模块进行取消选中状态
card.discardActiveObject()
// 重新渲染
card.renderAll()
})
三、加载 fabric.js 的 canvas 虚化网格透明背景
问题:在 canvas 初始化时需要加载 网格透明背景,让用户感觉这个时一个透明的图层,从而可以在上面操作
1、可以加载一个 rect,特定一个自定义类型,给这个 rect 填充一个 100 * 100 的网格图
比如我有一个100*100的网格图
// 初始化一个 rect、设置宽、高、left、top、自定义类型、以及不可选中(最重要,这个背景不可选)
const shapeBg = new fabric.Rect({
width: card.width,
height: card.height,
left: 0,
top: 0,
fill: 'rgba(0, 0, 0, 0)',
myFabricType: 'bg',
selectable: false // 禁止选中
})
card.add(shapeBg)
const url = '网格图的地址'
// 加载网格图
fabric.util.loadImage(url, function(img) {
// 对 rect 进行图片填充,设置 repeat 为 重复
shapeBg.set('fill', new fabric.Pattern({
source: img,
repeat: 'repeat'
}))
// 渲染
card.renderAll();
});
此时 card 渲染就会有一个网格的背景了
这个rect一定要在渲染其他的模块之前进行加载,最好时创建card的时候就进行加载这个rect,不然的话会有层级的问题
2、把这个虚化的网格写成一个 svg,根据 loadSVGFromString 进行导入
导入的 svg
也是需要设置 selectable: false
四、fabric.js loadSVGFromString Uncaught TypeError: Cannot set property ‘crossOrigin’ of undefined
在 fabric.js
的项目中,使用 canvas.toSVG()
生成的 SVG
代码,在通过 loadSVGFromString
来进行渲染,报错
错误内容
fabric.js:4477 Uncaught TypeError: Cannot set property 'crossOrigin' of undefined
at Object.fabric.parseSVGDocument (fabric.js:4477)
at Object.loadSVGFromString (fabric.js:4856)
at testFabric4.html:225
原因:生成的 SVG
内容头部引入的 http:
链接,改成 https:
即可
svgContext = svgContext.replace(/http:\/\//g, 'https://')
fabric.loadSVGFromString(svgContext, () => {
// ...
})
五、fabric.js 撤销、恢复和保存每一步操作
const state = {
saveLen: 0,
deleLen: 0,
operIndex: -1
}
window.saveOperateList = []
window.deleteOperateList = []
const getters = {
}
const mutations = {
// 操作保存的数据
OPERATE_OPERATE_DATA (state) {
const json = window.card.toDatalessJSON()
if (state.deleLen > 0) {
window.deleteOperateList.some(item => {
window.saveOperateList[item].del = true
})
window.saveOperateList = window.saveOperateList.filter(item => {
return !item.del
})
window.deleteOperateList = []
window.saveOperateList.push(json)
state.operIndex = window.saveOperateList.length - 1
} else {
window.saveOperateList.push(json)
state.operIndex += 1
}
state.saveLen = window.saveOperateList.length
state.deleLen = window.deleteOperateList.length
},
// 上一步操作
PREV_STEP_OPERATE (state) {
if (state.operIndex > 0) {
window.card.loadFromJSON(window.saveOperateList[state.operIndex - 1]).renderAll()
if (window.deleteOperateList.includes(state.operIndex - 1)) {
} else {
window.deleteOperateList.push(state.operIndex)
state.operIndex -= 1
}
}
state.saveLen = window.saveOperateList.length
state.deleLen = window.deleteOperateList.length
},
// 下一步操作
NEXT_STEP_OPERATE (state) {
if (state.operIndex + 1 >= window.saveOperateList.length) {
return
}
window.card.loadFromJSON(window.saveOperateList[state.operIndex + 1]).renderAll()
if (window.deleteOperateList.includes(state.operIndex + 1)) {
const index = window.deleteOperateList.indexOf(state.operIndex + 1)
window.deleteOperateList.splice(index, 1)
} else {
}
state.operIndex = state.operIndex + 1
state.saveLen = window.saveOperateList.length
state.deleLen = window.deleteOperateList.length
}
}
const actions = {
}
export default { state, getters, mutations, actions }
字段解释:
-
saveLen
:保存每一步的数据saveOperateList
的长度 -
deleLen
:需要删除每一步的数据deleteOperateList
的长度 -
operIndex
:操作的 Index 的值 -
window.saveOperateList
:保存的数据,存的值为每一步的 json 数据 -
window.deleteOperateList
:需要删除每一步的数据列表,存的值为saveOperateList
的每一步的index
的值
思路:
- 保存操作记录:
deleLen <= 0
即没有需要删除的操作记录
- 定义
json
:const json = window.card.toDatalessJSON()
- 往
saveOperateList
中 push 数据:window.saveOperateList.push(json)
-
operIndex
的值往上加一:state.operIndex += 1
deleLen > 0
的时候,即有删除操作记录的数据的时候
- 遍历
deleteOperateList
数组,给saveOperateList[item]
每一个对象加一个del
属性(需要删除掉的值) -
filter``saveOperateList
数组,把有del
属性的过滤掉 -
deleteOperateList
赋值为[]
-
saveOperateList
添加新的数据 - 设置
operIndex
的值
- 设置
saveLen
和deleLen
的值
- 撤销/上一步操作:
- 如果
operIndex > 0
- 加载上一次保存的数据:
window.card.loadFromJSON(window.saveOperateList[state.operIndex - 1]).renderAll()
- 如果
deleteOperateList
包含当前operIndex - 1
则不进任何操作,否则把operIndex
push
进deleteOperateList
中 - 再把
operIndex - 1
- 如果
operIndex <= 0
不进行任何操作,说明此时不能进行撤销(上一步)操作 - 设置
saveLen
和deleLen
的值
- 恢复/下一步操作:
- 如果
operIndex + 1 > saveOperateList.length
则不进行任何操作,说明此时不能进行恢复操作,直接return
即可 - 加载
operIndex + 1
的数据:window.card.loadFromJSON(window.saveOperateList[state.operIndex + 1]).renderAll()
- 如果
deleteOperateList
包含operIndex + 1
的值,从deleteOperateList
中删除这个值;如果不包含,则不删除 - 设置
operIndex
的值 - 设置
saveLen
和deleLen
的值
此时即完成了一个 保存数据、撤销、恢复整个流程
由于设置的 operIndex
为 -1 ,所以需要在 card
初始化时进行保存初始化的 json
数据,在初始化完成之后进行调用一次 OPERATE_OPERATE_DATA
函数
后面每次进行操作的时候调用 OPERATE_OPERATE_DATA
,每次点击撤销的时候调用PREV_STEP_OPERATE
,点击恢复的时候调用NEXT_STEP_OPERATE