Search code examples
javascriptreactjsfabricjs

Fabric JS storing steps in database


This question might be slightly opinionated, sorry in advance for that. Is there any way to store the steps done on a FabricJS canvas in an efficient way? Let's say I am a user and I would like to continue editing my canvas later so I close down the window, and next time I open it we can load all the previous steps from the database? I am considering storing canvas.toJSON() into the database, but it is very large, much larger than I need it to be, since the change done is often very small. Is there any function or convention that is shorter and also "loadable" to a canvas? I am considering creating my own convention for this that only store the actual change, i.e. the delta, is that a good idea?


Solution

  • TL;DR;

    You can remove unnecessary (default) object data and compress using lz-string


    The reason the result is so long is that it gives you all the properties including the defaults. So you can get around that by removing default values.

    This example script removes every object property that has a value of 0 or null.

    var canvas = new fabric.Canvas('c');
    canvas.add(new fabric.Rect({left: 50,  top: 50, height: 20, width: 20,  fill: 'green'}));
    canvas.add(new fabric.Circle({left: 100,  top: 100, radius: 50, fill: 'red'}));
    
    let output = canvas.toJSON();
    let copy = {...output, objects:[]};
    
    (output.objects || []).map(object => {
      let objectCopy = {};
      for (let key in object) {
        val = object[key];
        if (val !== null && val !== 0){
            objectCopy[key] = object[key]
        } else {
            // console.log('skipped', key, object[key])
        }
      }
      copy.objects.push(objectCopy);
    })
    var canvas2 = new fabric.Canvas('d');
    canvas2.loadFromJSON(JSON.stringify(canvas));
    
    console.log(JSON.stringify(canvas).length);
    console.log(JSON.stringify(copy).length);
    #canvas-wrapper {
      position: relative;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.3.1/fabric.min.js" integrity="sha512-ACqMrfAtm537AWzgx/xQ57JnFxXeq8RylQMGg4y/e6M2ew4Z8NycE8aId/Bt2ZE+w1gNsox3MgwxKl7SGMRdtA==" crossorigin="anonymous"></script>
    <div id="canvas-wrapper"><canvas id="c" width="400" height="200" /></div>
    <div id="canvas-wrapper"><canvas id="d" width="400" height="200" /></div>

    This gives us about 20% improvement.

    Also, this approach is naive and leaves a lot of room for improvement. It can't handle groups (nested objects), and if you override a default value of 1 to 0 (ie strokeWidth), it will ignore it 😱 and lead to a buggy result. So the solution would have to take the full list of default settings for each object type into consideration, but it could easily give you over 50% compressions ratio in common uses.

    The other option is to use compression library (like lz-string) to compress your string result. This can, out-of-the-box, give you a %70-%80 improvement. This is much faster and much more reliable.

    The ideal,IMHO, is to do both which could get you down to 5-10% of original