在vue中使用fabric

基本用法

vue@3.2.47 + fabric@5.3.0

常用功能

添加元素

// 只能生成一种类型 如Rect
function addRect() {
    const rect = new fabric.Rect({
        // options
    })
    canvas.add(rect)
    canvas.requestRenderAll()
}

// 可批量生成各种类型
function addObjectbyType(type, data) {
    const klass = fabric.util.getKlass(type, '')
    klass.fromObject(data, (obj) => {
        canvas.add(obj)
        canvas.requestRenderAll()
    })
}

添加svg

// svg地址加载
function loadSVGFromURL(url) {
    fabric.loadSVGFromURL(url, (objects) => {
        const group = new fabric.Group(objects, {
            // options
        })
        canvas.add(group)
        canvas.requestRenderAll()
    })
}

// svg字符串加载
function loadSVGFromString(str) {
    fabric.loadSVGFromString(str, (objects) => {
        const group = new fabric.Group(objects, {
            // options
        })
        canvas.add(group)
        canvas.requestRenderAll()
    })
}

添加图片

function addImage(url) {
    fabric.Image.fromURL(url, (image) => {
        const {width} = image
        const initialScale = 200 / width
        image.set({
            scaleX: initialScale,
            scaleY: initialScale,
            // options
        })
        canvas.add(image)
        canvas.setActiveObject(image)
    })
}

画布数据导入、导出

// 导入
function dataImport(data) {
    canvas.loadFromJSON(data, () => {
        // callback
    })
}

// 导出
function dataExport() {
    return canvas.toJSON()
}

画布保存为图片

// 图片形式
function saveAsImage() {
    const dataUrl = canvas.toDataURL('image/jpeg')
    const link = document.createElement('a')
    link.download = '画布.png'
    link.href = dataUrl
    link.click()
}

元素编组、取消编组

// 编组
function group() {
    const activeObject = canvas.getActiveObject()
    if (!activeObject) {
        return
    }
    if (activeObject.type !== 'activeSelection') {
        return
    }
    activeObject.toGroup()
    canvas.requestRenderAll()
}

// 取消编组
function unGroup() {
    const activeObject = canvas.getActiveObject()
    if (!activeObject) {
        return
    }
    if (activeObject.type !== 'group') {
        return
    }
    activeObject.toActiveSelection()
    canvas.requestRenderAll()
}

调整元素层级

// 移至顶层
function bringToFront() {
    const activeObject = canvas.getActiveObject()
    if (!activeObject) {
        return
    }
    activeObject.bringToFront()
}

// 移至底层
function sendToBack() {
    const activeObject = canvas.getActiveObject()
    if (!activeObject) {
        return
    }
    const index = canvas.freeLayerIndexMinLimit || 0
    activeObject.sendToBack()
}

// 往上一层
function bringForward() {
    const activeObject = canvas.getActiveObject()
    if (!activeObject) {
        return
    }
    activeObject.bringForward()
}

// 往下一层
function sendBackwards() {
    const activeObject = canvas.getActiveObject()
    if (!activeObject) {
        return
    }
    activeObject.sendBackwards()
}

// 指定层级
function moveTo(index) {
    const activeObject = canvas.getActiveObject()
    if (!activeObject) {
        return
    }
    activeObject.moveTo(index)
}

视图自适应

import {fabric} from 'fabric'

export const useZoomToFit = (canvas) => {
    const objects = canvas.getObjects()
    let minX, minY, maxX, maxY
    if (objects.length > 0) {
        let rect = objects[0].getBoundingRect()
        minX = rect.left
        minY = rect.top
        maxX = rect.left + rect.width
        maxY = rect.top + rect.height
        for (let i = 1; i < objects.length; i++) {
            rect = objects[i].getBoundingRect()
            minX = Math.min(minX, rect.left)
            minY = Math.min(minY, rect.top)
            maxX = Math.max(maxX, rect.left + rect.width)
            maxY = Math.max(maxY, rect.top + rect.height)
        }
    }

    // 计算平移坐标
    const panX = (maxX - minX - canvas.width) / 2 + minX
    const panY = (maxY - minY - canvas.height) / 2 + minY
    // 开始平移
    canvas.absolutePan({
        x: panX,
        y: panY
    })

    // 计算缩放比例
    const zoom = Math.min(canvas.width / (maxX - minX), canvas.height / (maxY - minY))
    // 计算缩放中心
    const zoomPoint = new fabric.Point(canvas.width / 2, canvas.height / 2)
    console.log('zoom', zoom, zoomPoint)
    // 开始缩放
    canvas.zoomToPoint(zoomPoint, zoom)
}

拓展功能

自定义Control

// 统一隐藏控制  ['mb', 'ml', 'mr', 'mt', 'mtr']
const hiddenControls = ['mtr']
hiddenControls.forEach(item => {
    fabric.Object.prototype.controls[item].visible = false
})
fabric.Object.prototype.transparentCorners = false
fabric.Object.prototype.cornerSize = 8


fabric.Textbox.prototype.controls = {}

// 类型单独设置
fabric.Rect.prototype.controls = {}
fabric.Textbox.prototype.controls = {}

自定义属性导出

const originalToObject = fabric.Object.prototype.toObject;
const myAdditional = ['id'];
fabric.Object.prototype.toObject = function (additionalProperties) {
    return originalToObject.call(this, myAdditional.concat(additionalProperties));
}

文本组合图形

TIP

组合图形本质上是通过fabric.Group实现,以文本框加矩形Textbox + Rect为例

需要注意的是Textbox在Group内是无法进行编辑的,需要另外编写代码实现组合图形内的文本编辑;

import {fabric} from 'fabric'
import {v4 as uuid} from "uuid";
import {TempText} from './tempText.mixin'

const DEFAULT_SHAPE_PADDING_X = 8

fabric.RectText = fabric.util.createClass(fabric.Group, {
    type: 'rect-text',
    width: 60,
    height: 40,
    fill: '#666666',
    fontSize: 16,
    textAlign: 'center',
    lineHeight: 1,
    stroke: '#000000',
    subTargetCheck: true,
    initialize: function (options, isAlreadyGrouped) {
        options = options || {}
        options.id = options.id || uuid()
        options.width = options.width || this.width
        options.height = options.height || this.height
        options.fill = options.fill || this.fill
        options.fontSize = options.fontSize || this.fontSize
        options.textAlign = options.textAlign || this.textAlign
        options.lineHeight = options.lineHeight || this.lineHeight
        options.stroke = options.stroke || this.stroke
        options.strokeWidth = options.strokeWidth || 2
        const {objects, ...restOptions} = options || {}

        const {
            id, width, height, fill, fontSize, textAlign, lineHeight, stroke, strokeWidth
        } = options
        const defaultShapeOptions = {
            type: 'rect',
            width,
            height,
            fill: '',
            stroke,
            strokeWidth
        }
        const defaultTextOptions = {
            type: 'textbox',
            width: width - DEFAULT_SHAPE_PADDING_X,
            fontSize,
            fill,
            textAlign,
            lineHeight,
            stroke: null,
            strokeWidth: 0,
            originX: 'center',
            originY: 'center'
        }
        options.objects = options.objects || [
            defaultShapeOptions,
            defaultTextOptions
        ]

        const shapeOptions = objects[0] || defaultShapeOptions
        const textOptions = objects[1] || defaultTextOptions

        shapeOptions.width = width
        shapeOptions.height = height
        shapeOptions.fill = shapeOptions.fill || ''
        shapeOptions.stroke = shapeOptions.stroke || stroke
        shapeOptions.strokeWidth = shapeOptions.strokeWidth || strokeWidth

        let shape = new fabric.Rect({
            ...shapeOptions,
            id: id + '_rect',
            left: 0,
            top: 0,
            strokeUniform: true,
        })

        textOptions.text = textOptions.text || ''
        textOptions.width = width - strokeWidth * 2
        textOptions.fill = textOptions.fill || fill
        textOptions.fontSize = textOptions.fontSize || fontSize
        textOptions.textAlign = textOptions.textAlign || textAlign
        textOptions.lineHeight = textOptions.lineHeight || lineHeight

        let text = new fabric.Textbox(textOptions.text, {
            ...textOptions,
            id: id + '_text',
            top: (height + strokeWidth) / 2,
            left: (width + strokeWidth) / 2,
            stroke: null,
            strokeWidth: 0,
            splitByGrapheme: true,
            strokeUniform: true,
            originX: 'center',
            originY: 'center'
        })
        let group = [shape, text]
        this.callSuper('initialize', group, {
            ...restOptions,
            strokeWidth: 0,
            strokeUniform: true,
            // useSetOnGroup: true,
        }, isAlreadyGrouped)

        this.on('mousedblclick', () => this.dblclickEditing())
    },
    _set: function (key, value) {
        var i = this._objects.length;
        if (this.useSetOnGroup) {
            while (i--) {
                this._objects[i].setOnGroup(key, value);
            }
        }
        if (key === 'canvas') {
            while (i--) {
                this._objects[i]._set(key, value);
            }
        }
        fabric.Object.prototype._set.call(this, key, value);
    },
    set: function (key, value) {
        this.callSuper('set', key, value)
    },
})
fabric.util.object.extend(fabric.RectText.prototype, TempText)
fabric.RectText.fromObject = function (object, callback) {
    return fabric.Object._fromObject('RectText', object, callback)
}