I was smoking today with a buddy in the back. He said, “A grain of ash on my head is a mountain.”


































  1. It encapsulates the CANVAS API, making it easier to operate
  2. There are parsers to load SVG
  3. Event mechanics and layer concepts
  4. Support for exporting and parsing serialization current state
  5. Basic edit element functionality


Text generated

Fabric.text Generates Text

Fabric.itext generates editable text

Textbox The ITExt-based Textbox class allows the user to automatically resize and wrap text rectangles. With its y scale locked, the user can only change the width, and the height automatically adjusts according to the wrapping of the line

I’m using fabric.itext here

let canvas = new fabric.Canvas('c');
let text = new fabric.IText('hello world', { fontFamily: 'Comic Sans'});
    canvas.add(text);
Copy the code

Image rendering

let canvas = new fabric.Canvas('c');
fabric.Image.fromURL('example.jpg'.function(img) {
    img.filters.push(
        new fabric.Image.filters.Sepia(),
        new fabric.Image.filters.Brightness({ brightness: 100 }));

    img.applyFilters();
    canvas.add(img);
});
Copy the code

The editor

The above is actually according to the user’s input configuration to generate the corresponding material drag to the corresponding position

// Enable drag and move
canvas.selection = true

document.getElementById('id').addEventListener('click'.(e) = > {
			// Use getObjects to get the Index of the layer that the Canvas wants to change and change the properties above
      canvas.item(0) ['fill'] = e.value;
      canvas.requestRenderAll();
})
Copy the code

Form generated

Generating the table is a little more cumbersome, and the final expectation is something like this

Let’s start by sorting out the table functionality we want to implement

Overall design idea

  1. Generate the corresponding text object from the mock data
  2. Draw a table based on the values calculated above
  3. Puts the text into the cell that should belong to the text based on the current table position and text object properties
  4. Finally render it as a whole

Generate the corresponding text object from the mock data

let mockTable = [
    ['date'.'time'.'Course Contents \n Course Contents'],
    ['The 16th of August'.'12:00 a.m..Course Contents Course Contents Course Contents Course Contents Course Contents Course Contents Course Contents Course contents Course contents Course Contents Course Contents Course Contents course Contents course Contents course Contents course Contents'],
    ['The 16th of August'.'12:00 a.m..Course Contents Course Contents Course Contents course Contents course Contents course Contents course contents]]// Table information
let tableInfo = []

function createText(text) {
    return new fabric.IText(text, {
        fontSize: 30.fontWeight: 400}); }/ / initialization
mockTable.forEach((row, i) = > {
    tableInfo[i] = []
    row.forEach((col, j) = > {
        mockTable[i][j] = createText(mockTable[i][j])
        // Set the header font
        if (i === 0) {
            mockTable[i][j].set('fontSize'.40)
            mockTable[i][j].set('fontWeight'.700)
            mockTable[i][j].set('fill'.'RGB (178,86,49)')
        }
        tableInfo[i][j] = {
            width:0.height:0}})})Copy the code


Calculate the values according to the above text object properties and draw the table

// Set up temporary array conveniently, later render at once
let list = []
for (let i = 0; i < mockTable.length; i++) {
    for (let j = 0; j < mockTable[0].length; j++) {
        tableInfo[i][j].width = getWidth(j)
        tableInfo[i][j].height = getHeight(i)
        let [x, y] = getPostion(j, i)
        let rect = new fabric.Rect({
            left: x,
            top: y,
            // Set the header background style
            fill: i === 0 ? 'RGB (231225186). : 'white'.width: tableInfo[i][j].width,
            height: tableInfo[i][j].height,
            stroke: 'RGB (161127123)..strokeWidth: 2,})// Center the text vertically
        mockTable[i][j].set('left', x + (tableInfo[i][j].width - mockTable[i][j].width) / 2)
        mockTable[i][j].set('top', y + (tableInfo[i][j].height - mockTable[i][j].height) / 2)
        list.push(rect)
        list.push(mockTable[i][j])
    }
}

// Set the layer for easy editing
let group = new fabric.Group(list, {
    left: 100.top: 100
});

canvas.selection = true
group.set('selectable'.true);
canvas.add(group);
Copy the code


Tool function

// Calculate the maximum column width
function getWidth(x) {

    let maxWidth = mockTable[0][x].width;
    for (let i = 0; i < mockTable.length; i++) {
        maxWidth = Math.max(maxWidth, mockTable[i][x].width)
    }
    return maxWidth
}

// Calculate the maximum row height
function getHeight(y) {
    let maxHeight = mockTable[y][0].height;
    for (let i = 0; i < mockTable[0].length; i++) {
        maxHeight = Math.max(maxHeight, mockTable[y][i].height)
    }
    return maxHeight
}

// Add row width and height to calculate coordinates
function getPostion(x, y) {
    let x1 = 0;
    let y1 = 0;
    for (let i = 0; i < x; i++) {
        x1 += tableInfo[y][i].width
    }
    for (let j = 0; j < y; j++) {
        y1 += tableInfo[j][x].height
    }
    return [x1, y1]
}
Copy the code

Undo and advance

Undo and forward mainly maintain two stacks

  1. When initializing, add the current save history to the stack, ensuring that it is always at the bottom and not deleted
  2. Clear forward temporary stack and add to history stack at operation time
  3. Advance and undo to move the top element on to another stack
	let popDom = document.querySelector('#pop');
    let pushDom = document.querySelector('#push');
		
    let historyList = [JSON.stringify(canvas)];
    let tempHistoryList = [];

    canvas.on('mouse:up:before'.function () {
        tempHistoryList = []
        historyList.push(JSON.stringify(canvas))
    });

    popDom.addEventListener('click'.() = > {
        if (historyList.length === 1) {
            canvas.clear()
            canvas.loadFromJSON(JSON.parse(historyList[0])).renderAll()
            return
        }
        tempHistoryList.push(JSON.parse(historyList.pop()))
        let historyAction = historyList[historyList.length - 1];
        canvas.clear()
        canvas.loadFromJSON(JSON.parse(historyAction)).renderAll()
    })

    pushDom.addEventListener('click'.() = > {
        if (tempHistoryList.length === 0) {
            alert('No forward record')
            return
        }
        let tempAction = JSON.stringify(tempHistoryList.pop());
        historyList.push(tempAction)
        canvas.clear()
        canvas.loadFromJSON(JSON.parse(tempAction)).renderAll()
    })
Copy the code


Final effect

To optimize the

  1. The data structure of the table can be set in the center of the cell, color and so on. A good data structure is also convenient for the expansion of the function in the future.
  2. When calculating the width, there will be repeated calculation. In fact, you can compare the method of directly taking the calculated attributes when there is no maximum width.
  3. In the undo function, storing the current state each time takes up a lot of memory, especially when loading the image canvas. This can lock the stack size of excess only take the latest, can also be optimizedFabricjsExport the size in JSON format.
  4. In terms of rendering, try to compare and use the original object to make changes in the corresponding rendering, or create every timeFabric.objectOr it may lead to excessive memory footprint and repeated generation of computing time.

In fact, for now, there is a lot of room for optimization, you are welcome to discuss any good suggestions. It is the first time for me to use canvas, if there is anything wrong, please correct me.

If small partners to source thanks here github.com/cdhhhhhhh/f…