一、选中多个模块组合与拆分组

1、组合

组合是要把选中的模块组合成一个组

选中的模块:

card.getActiveObject()

成组:

// 成组 、重新渲染
card.getActiveObject().toGroup()
card.renderAll()
2、拆分组合
// 获取选中的组合模块,进行组合拆分、重新渲染
card.getActiveObject().toActiveSelection();
card.renderAll()

二、loadSVGFromString 加载 SVG 导致子元素left和top值不对

问题:通过 loadSVGFromString 加载 SVG ,渲染完成之后回导致 里面的子模块 lefttop 的值不正确

原因:这个是因为,通过 loadSVGFromString 加载的 SVG 回被组成一个组合,组合里面的子元素按照组合的基点来算 lefttop 值,而组合的基点所在位置是中心点,不是我们通常的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 虚化网格透明背景

fabricjs pattern_加载

问题:在 canvas 初始化时需要加载 网格透明背景,让用户感觉这个时一个透明的图层,从而可以在上面操作

1、可以加载一个 rect,特定一个自定义类型,给这个 rect 填充一个 100 * 100 的网格图

比如我有一个100*100的网格图

fabricjs pattern_fabric.js_02

// 初始化一个 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

fabricjs pattern_加载_03

原因:生成的 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 的值
思路:
  1. 保存操作记录:
  1. deleLen <= 0 即没有需要删除的操作记录
  1. 定义jsonconst json = window.card.toDatalessJSON()
  2. saveOperateList 中 push 数据:window.saveOperateList.push(json)
  3. operIndex的值往上加一:state.operIndex += 1
  1. deleLen > 0 的时候,即有删除操作记录的数据的时候
  1. 遍历deleteOperateList数组,给saveOperateList[item]每一个对象加一个del属性(需要删除掉的值)
  2. filter``saveOperateList数组,把有 del 属性的过滤掉
  3. deleteOperateList赋值为[]
  4. saveOperateList添加新的数据
  5. 设置 operIndex 的值
  1. 设置saveLendeleLen 的值
  1. 撤销/上一步操作:
  1. 如果operIndex > 0
  1. 加载上一次保存的数据:window.card.loadFromJSON(window.saveOperateList[state.operIndex - 1]).renderAll()
  2. 如果deleteOperateList包含当前operIndex - 1则不进任何操作,否则把operIndex pushdeleteOperateList
  3. 再把operIndex - 1
  1. 如果operIndex <= 0 不进行任何操作,说明此时不能进行撤销(上一步)操作
  2. 设置saveLendeleLen 的值
  1. 恢复/下一步操作:
  1. 如果operIndex + 1 > saveOperateList.length则不进行任何操作,说明此时不能进行恢复操作,直接return即可
  2. 加载 operIndex + 1 的数据:window.card.loadFromJSON(window.saveOperateList[state.operIndex + 1]).renderAll()
  3. 如果 deleteOperateList包含operIndex + 1的值,从deleteOperateList中删除这个值;如果不包含,则不删除
  4. 设置operIndex 的值
  5. 设置saveLendeleLen 的值

此时即完成了一个 保存数据、撤销、恢复整个流程

由于设置的 operIndex 为 -1 ,所以需要在 card 初始化时进行保存初始化的 json 数据,在初始化完成之后进行调用一次 OPERATE_OPERATE_DATA函数

后面每次进行操作的时候调用 OPERATE_OPERATE_DATA,每次点击撤销的时候调用PREV_STEP_OPERATE,点击恢复的时候调用NEXT_STEP_OPERATE