额。。这是我自己创造的名词,内嵌工作台画布
要实现什么效果
如图(http://fabricjs.com/kitchensink):
首先想元素脱出画布外一部分也能选中,如图上面就没法选择超出框外的内容。我想到的是如何在画布中间加个画布。周围隐藏掉。
实现思路
- 首先创建一个足够大的画布
- 设置一个padding
- 通过padding 计算中间白色区域四个角的坐标
- 在周围画上四个矩形盖住(也可以画一个大矩形,把中间的挖掉,由于各种需求和测试问题没有采用这种方式)
- 每次添加新的元素在上面的时候都保持周边四个区域在最上面
其他思路
- 使用clipPath,画一个通道给中间区域。
- 画两个画布一个再上面一个在下面 其实这些方法可能都能实现,但是问题的关键不是实现,而是如何兼容之后的诸多行为,比如zoom,loadJson,redo, undo
代码
1.先定义一个想要的外边距padding,然后计算内部工作台处于外部画布的四个点的位置。
// 获取中间画布的四个点的位置
function setXY(state, obj = {}) {
// 最外边整个画布的宽 高
const w = obj.currentWidth || state.currentWidth
const h = obj.currentHeight || state.currentHeight
// 内部工作台画布的宽pannelW 高pannelH
if (
state.currentWidth / state.currentHeight >
state.pannelW / state.pannelH
) {
const height = h - state.padding
// 缩放画布,因为pannelW为实际宽度,比如一张A4纸显示在屏幕上,需要缩放一下,zoom后获取到屏幕的宽度
state.zoom = height / state.pannelH
this.commit('zoomCanvas')
const x2 = 0
const y2 = 0
const y0 = state.padding / state.zoom / 2 + y2
const y1 = state.pannelH + y0
const x0 = w / state.zoom / 2 - state.pannelW / 2 + x2
const x1 = state.pannelW + x0
state.arrX = [x0, x1, x2, w / state.zoom + x2]
state.arrY = [y0, y1, y2, h / state.zoom + y2]
} else {
const width = w - state.padding
state.zoom = width / state.pannelW
this.commit('zoomCanvas')
const x2 = 0
const y2 = 0
const x0 = state.padding / state.zoom / 2 + x2
const x1 = state.pannelW + x0
const y0 = h / state.zoom / 2 - state.pannelH / 2 + y2
const y1 = state.pannelH + y0
state.arrX = [x0, x1, x2, w / state.zoom]
state.arrY = [y0, y1, y2, h / state.zoom]
}
}
2.创建四周的遮罩,给中间的区域,画个背景
function createMask(state) {
const arrX = state.arrX
const arrY = state.arrY
const pathOption = {
selectable: false,
fill: '#ebeced',
hoverCursor: 'default',
evented: false,
excludeFromExport: true,
hasControls: false,
perPixelTargetFind: false,
strokeWidth: 0,
stroke: null
}
const rect1 = new fabric.Rect({
width: arrX[0] - arrX[2],
height: arrY[3] - arrY[2]
})
const rect2 = new fabric.Rect({
width: arrX[3] - arrX[0] + 2,
height: arrY[0] - arrY[2]
})
const rect3 = new fabric.Rect({
width: arrX[3] - arrX[1],
height: arrY[1] - arrY[0] + 2
})
const rect4 = new fabric.Rect({
width: arrX[3] - arrX[0],
height: arrY[3] - arrY[1]
})
rect1.set({
left: arrX[2],
top: arrY[2],
name: 'mask1',
...pathOption
})
rect2.set({
left: arrX[0] - 1,
top: arrY[2],
name: 'mask2',
...pathOption
})
rect3.set({
left: arrX[1],
top: arrY[0] - 1,
name: 'mask3',
...pathOption
})
rect4.set({
left: arrX[0],
top: arrY[1],
name: 'mask4',
...pathOption
})
state.maskPath = new fabric.Group([rect1, rect2, rect3, rect4], {
selectable: false,
excludeFromExport: true,
lockMovementX: true,
lockMovementY: true,
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
lockUniScaling: true,
hoverCursor: 'auto',
name: 'grid',
left: arrX[2],
top: arrY[2],
type: 'sMask',
evented: false
})
// 创建中间画布的背景
state.backgroundPanel = new fabric.Rect({
left: arrX[0],
top: arrY[0],
selectable: false,
evented: false,
width: state.pannelW,
height: state.pannelH,
strokeWidth: 0,
fill: !state.activePage ? '#ffffff' : 'rgba(0, 0, 0, 0)',
objectCaching: false,
hoverCursor: 'default',
excludeFromExport: true,
hasControls: false,
type: 'sBg',
perPixelTargetFind: false
})
state.canvas.remove(...state.canvas.getObjects('sMask'))
state.canvas.remove(...state.canvas.getObjects('sBg'))
state.canvas.add(state.maskPath)
state.canvas.add(state.backgroundPanel)
state.canvas.sendToBack(state.backgroundPanel)
state.canvas.renderAll()
}
3.刷新画布,或者加载数据的时候
jsonDemo.objects.forEach((item) => {
item.left += state.arrX[0]
item.top += state.arrY[0]
})
4.保存json
function saveJson(state) {
// state.toJSONProperties 为要保存的额外属性
const jsonStr = JSON.stringify(state.canvas.toJSON(state.toJSONProperties))
const jsonObj = JSON.parse(jsonStr)
jsonObj.objects.forEach((v) => {
v.left = v.left - state.arrX[0]
v.top = v.top - state.arrY[0]
})
}