额。。这是我自己创造的名词,内嵌工作台画布
要实现什么效果
如图():
首先想元素脱出画布外一部分也能选中,如图上面就没法选择超出框外的内容。我想到的是如何在画布中间加个画布。周围隐藏掉。
实现思路
- 首先创建一个足够大的画布
- 设置一个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] })}