Friday 20 August 2021

Fabric.js svg elements to pop up on click to be edited and then return to its previous size/position

I am developing an app in html5 and I'm using fabric.js to manipulate some graphics. I need to make some of the paths to pop up when clicked in order to be edited on a closer view as their real size is quite small and the app will be display on mobile screens. This is what I have done until now:

  canvas = new fabric.Canvas('c', { preserveObjectStacking: true });
    canvas.selection = false;
    fabric.Object.prototype.borderColor = 'transparent';
    fabric.Object.prototype.cornerColor = '#d50000';
    fabric.Object.prototype.transparentCorners = false;
    fabric.Object.prototype.cornerStyle = 'circle';
    canvas.hoverCursor = 'pointer';

    // delete icon
    var deleteIcon = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Cpath style='fill:%23d50000;' d='m 146.8759,307.20476 38.95015,0 114.10246,100.31923 114.10245,-100.31923 38.95016,0 -120.53144,131.86736 120.53144,131.86889 -38.95016,0 -114.10245,-100.31924 -114.10246,100.31924 -38.95015,0 L 267.29322,439.07212 146.8759,307.20476 Z'/%3E%3C/svg%3E";

    var img = document.createElement('img');
    img.src = deleteIcon;

    fabric.Object.prototype.controls.deleteControl = new fabric.Control({
        x: 0.5,
        y: -0.5,
        offsetY: 76,
        offsetX: -15,
        cursorStyle: 'pointer',
        mouseUpHandler: deleteObject,
        render: renderIcon,
        cornerSize: 24
    });

    function deleteObject(eventData, transform) {
        var target = transform.target;
        var canvas = target.canvas;
        canvas.remove(target);
        canvas.requestRenderAll();
    }

    function renderIcon(ctx, left, top, styleOverride, fabricObject) {
    var size = this.cornerSize;
    ctx.save();
    ctx.translate(left, top);
    ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
    ctx.drawImage(img, -size/2, -size/2, size, size);
    ctx.restore();
    }

    // bg

    canvas.backgroundColor= "#ccc";

    //* 31
    var v31 = new fabric.Path('m 305.16587,542.07107 9.80091,-0.98571 7.18636,10.09881 c -5.31084,6.69609 -15.03687,7.83521 -21.68907,2.48023 z');
    v31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1});
    canvas.add(v31);
    v31.set('selectable', false);
    p31 = new fabric.Path('m 305.16587,542.07107 -4.70183,11.59335 c -6.69611,-5.31031 -7.83714,-15.03769 -2.48451,-21.69214 z');
    p31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(p31);
    p31.set('selectable', false);
    var l31 = new fabric.Path('m 314.96678,541.08536 -9.80091,0.98571 -7.31191,-9.93775 c 5.22415,-6.76398 14.93463,-8.02848 21.6553,-2.7598 z');
    l31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(l31);
    l31.set('selectable', false);
    var d31 = new fabric.Path('m 314.96678,541.08536 4.70184,-11.59338 c 6.6961,5.31032 7.83714,15.0377 2.48451,21.69215 z');
    d31.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(d31);
    d31.set('selectable', false);
    var group31 = new fabric.Group([ v31, l31, p31, d31 ], {left: 210, top: 386, opacity: 1});

    // 41
    var v41 = new fabric.Path('m 265.11576,542.24785 -9.80091,-0.98571 -7.18636,10.09881 c 5.31083,6.69609 15.03687,7.83521 21.68907,2.48023 z');
    v41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1});
    canvas.add(v41);
    v41.set('selectable', false);
    var p41 = new fabric.Path('m 265.11576,542.24785 4.70182,11.59335 c 6.69612,-5.31031 7.83715,-15.03769 2.48452,-21.69214 z');
    p41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(p41);
    p41.set('selectable', false);
    var l41 = new fabric.Path('m 255.31485,541.26214 9.80091,0.98571 7.31191,-9.93775 c -5.22416,-6.76398 -14.93462,-8.02848 -21.6553,-2.7598 z');
    l41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(l41);
    l41.set('selectable', false);
    var d41 = new fabric.Path('m 255.31485,541.26214 -4.70184,-11.59337 c -6.6961,5.31032 -7.83715,15.03769 -2.48451,21.69214 z');
    d41.set({fill: '#fff', stroke: '#1a1a1a', strokeWidth: 1.5, opacity: 1 });
    canvas.add(d41);
    d41.set('selectable', false);
    var group41 = new fabric.Group([ v41, l41, p41, d41 ], {left: 173.5, top: 386, opacity: 1});

    // create grid
    function createGrid() {
    canvas.add(new fabric.Path('m 0,49.54498 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,56.08866 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,68.942311 416,0"', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,87.171121 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,110.7751 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,116.61767 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,126.90059 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,137.88462 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,146.08928 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,168.96708 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,172.70632 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,203.08768 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,205.191 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,246.08898 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,247.7249 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,278.57366 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,281.3781 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,304.98207 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,313.39537 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,324.1457 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,334.42862 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,340.50489 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,363.40776 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,382.10398 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,395.19133 416,0', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 0,401.73501 416,0', { opacity: '0', selectable: false }));

    canvas.add(new fabric.Path('m 32.485,0 -3.19e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 39.963,0 1.69e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 54.453,0 -2.61e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 73.383,0 -3.39e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 97.454,0 4.3e-5,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 99.557,0 3.68e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 111.009,0 -2e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 125.498,0 3.7e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 131.341,0 -6e-5,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 156.581,0 -1.6e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 158.45,0 4.6e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 190,0 3.3e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 225.991,0 -4.5e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 257.54,0 4.2e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 259.17583,0 3.4e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 284.65,0 -6e-5,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 290.726,0 2.1e-4,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 304.74837,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 316.43351,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 318.77054,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 343.07562,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 361.77184,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 376.02771,0 0,441.23077', { opacity: '0', selectable: false }));
    canvas.add(new fabric.Path('m 383.7399,0 0,441.23077', { opacity: '0', selectable: false }));
    }

    // call this to actually create grids
    createGrid();

    // menu
    var redbtn = new fabric.Path('m 536.95273,607.72242 c 10e-6,9.21831 -7.47292,16.69124 -16.69123,16.69123 -9.21831,1e-5 -16.69123,-7.47292 -16.69123,-16.69123 2e-5,-9.2183 7.47293,-16.6912 16.69123,-16.6912 9.2183,0 16.69123,7.4729 16.69123,16.6912 z');
    redbtn.set({fill: '#d50000', stroke: '#1a1a1a', strokeWidth: 2, opacity: 1, left:363, top:454, hoverCursor: 'pointer' });
    canvas.add(redbtn);
    redbtn.set('selectable', false);

    var badrbtn = new fabric.Path('m 444.06184,672.53045 c -1.15491,3.93506 1.09854,8.06136 5.03317,9.21638 3.93466,1.15501 8.06055,-1.09864 9.21545,-5.0337 1.1549,-3.93505 -1.09853,-8.06135 -5.03318,-9.21637 -3.93466,-1.15501 -8.06054,1.09865 -9.21544,5.03369 z m -0.38452,1.80161 c 0.008,4.13284 3.325,7.52008 7.4597,7.56212 l -0.0905,9.21705 c -9.21992,-0.11093 -16.61428,-7.65898 -16.43826,-16.87185 z m 7.56106,7.70446 c 4.13199,-0.0601 7.47657,-3.4204 7.46616,-7.55577 l 9.21652,-0.0262 c 0.006,9.22152 -7.44695,16.71174 -16.66036,16.65255 z m 7.61272,-7.45432 c -0.008,-4.13282 -3.32498,-7.52008 -7.45971,-7.56213 l 0.0905,-9.21704 c 9.21993,0.11092 16.61428,7.65899 16.43826,16.87186 z m -7.46384,-7.70858 c -4.13241,0.006 -7.52004,3.32371 -7.56298,7.45881 l -9.21608,-0.0931 c 0.11285,-9.22081 7.66177,-16.61429 16.87371,-16.43626 z');
badrbtn.set({fill: '#007aff', stroke: '#d50000', strokeWidth: 2, opacity: 1, left:315, top:514, hoverCursor: 'pointer' });
canvas.add(badrbtn);
badrbtn.set('selectable', false);

    var oligo = new fabric.Path('m 258.83096,607.64956 a 15.904878,15.904878 0 0 1 -15.90488,15.90489 15.904878,15.904878 0 0 1 -15.9049,-15.90489 15.904878,15.904878 0 0 1 15.9049,-15.90488 15.904878,15.904878 0 0 1 15.90488,15.90488 z');
    oligo.set({fill: '#fff', stroke: '#0d47a1', strokeWidth: 2, opacity: 1, left:172, top:471, lockRotation: 'true', originX: 'center',  originY: 'center'});
    canvas.add(oligo);
    oligo.setControlsVisibility({ mt: false, mb: false, ml: false, mr: false, bl: false, br: false, tl: false, tr: false, mtr: false, });

    var rtnd = new fabric.Path('m 258.6723,674.6758 c 10e-6,8.78402 -7.12086,15.9049 -15.90488,15.9049 -8.78403,10e-6 -15.90491,-7.12087 -15.9049,-15.9049 0,-8.78402 7.12088,-15.90489 15.9049,-15.90488 8.78402,0 15.90488,7.12086 15.90488,15.90488 z');
    rtnd.set({fill: '#fff', stroke: '#d50000', strokeWidth: 2, opacity: 1, left:172, top:531, lockRotation: 'true', originX: 'center',  originY: 'center'});
    canvas.add(rtnd);
    rtnd.setControlsVisibility({ mt: false, mb: false, ml: false, mr: false, bl: false, br: false, tl: false, tr: false, mtr: false, });

    // snap to grid
    canvas.on('object:moving', function(options) {
    const horizontalSnappingPoints = options.target.canvas._objects
        .filter((el, index) => options.target.canvas._objects
        .some((sameTopEl, sameTopElIndex) => sameTopEl.top === el.top && sameTopElIndex !== index)).map(item => item.left);
    const verticalSnappingPoints = options.target.canvas._objects
        .filter((el, index) => options.target.canvas._objects
        .some((sameLeftEl, sameLeftElIndex) => sameLeftEl.left === el.left && sameLeftElIndex !== index)).map(item => item.top)
        options.target.set({
        left: horizontalSnappingPoints.reduce(function (prev, curr) {
        return (Math.abs(curr - options.target.left)    < Math.abs(prev - options.target.left) ? curr : prev);
        }),
        top: verticalSnappingPoints.reduce(function (prev, curr) {
        return (Math.abs(curr - options.target.top)     < Math.abs(prev - options.target.top) ? curr : prev);
        }),
    });
    });

    function saveCanvas(save){
    localStorage.setItem("draw", JSON.stringify(canvas.toObject()));
    }

    if (newCanvas){
    var newCanvas = JSON.parse(localStorage.getItem("draw"));
    canvas.loadFromJSON()
    } else {
    canvas = new fabric.Canvas('c', { preserveObjectStacking: true });

    canvas.selection = false;
    fabric.Object.prototype.borderColor = 'transparent';
    fabric.Object.prototype.cornerColor = '#d50000';
    fabric.Object.prototype.transparentCorners = false;
    fabric.Object.prototype.cornerStyle = 'circle';
    canvas.hoverCursor = 'pointer';
    }
.controls {
        display: flex;justify-content:center;
    }
    .wrapper {
        width:100%;height:auto;
        position: relative;
        display:flex;
        justify-content:center;
    }
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js" type="text/javascript"></script>
    </head>
    <body>
    <div class="wrapper">
    <div>
    <canvas id="c" width="416" height="570" style="text-align:center;"></canvas>
    <div>

    <div class="controls">
        <button id="save" onClick="saveCanvas()">Save canvas</button>
    </div>
    </div>
  </body>

Then what I need is:

  1. The 4 elements at the bottom will function as helpers or buttons inside the canvas. The red and blue ones should be color pickers where the user will take the fill color and the stroke color with a click/tap and then pass them to one of the areas on the two upper elements. Each area of the upper elements should be colored independet of the rest of the group.

  2. The other two elements at tthe bottom will function as draggable objects which should be placed on top of the upper elements. (this part is working ok)

  3. The upper elements, as there will be many more of them and the app will be displayed on mobile screens, need to be colored using one of the bottom color pickers. But as the tip of a finger is much bigger than the area to tap it will be necessary to make those upper elements to pop up on a triggering action, which should be fired by a mouse click/touch-screen event. Then they will be colored in an easy way.

  4. The elements when pop up should fill the whole superior half of the screen no matter the position they were before, and, after being edited, should be returned to theier initial position by another triggering action (maybe a tiny close button appended to the pop up). The trigering action should be located in the element itself and not in any external button.

I have added the fabric.js serialization and deserialization in order to save the canvas to the localStorage and later retrieve it from it to go on editing. The problem here is that, if I place the JSON.parse(localStorage.getItem("draw")) and the if else at the beginnig, rigth after the canvas initiation the draggable elements stop working, but if I place it at the bottom, then the draggable items still work but theier final position is not saved and when reloaded the canvas is displayed as if the user have not moved/changed anything. Then I need to:

  1. Use the Save button to store only the colored and the dragged elements according to the user's choices and later return them to the exact position, with the exact colors they were saved. I enhanced the word only because, if all the elements are to be saved, it will be and enourmous and unnecesary amount of info to be stored once and again, mainly if we take into account that every user should edit and store a lot of this graphics.

NOTE:There will be a lot of this upper items on the canvas and almost 16 items at the bottom menu. I just placed two of each as a sample.

If someone knows how to solve it, or at least a part of it, I will be very thankfull.



from Fabric.js svg elements to pop up on click to be edited and then return to its previous size/position

No comments:

Post a Comment