How to drag and drop between canvases in Fabric.js

前端 未结 2 1592
有刺的猬
有刺的猬 2021-02-02 04:52

I understand Fabric.js have built-in support on drag-n-drop within the same canvas.

How can we make it work for multiple canvas?

Or from an non-canvas html eleme

2条回答
  •  醉梦人生
    2021-02-02 05:24

    Drag and drop between canvases is possible in Fabric.js, but involves some manipulation of private properties. For this reason it is not guaranteed to be functional with future versions of Fabric.js.

    Working demo: https://jsfiddle.net/mmalex/kdbu9f3y/

    Video capture: https://youtu.be/nXZgCmIrpqQ

    Key features:

    ✓ can drag and drop between any number of canvases (not only two),

    ✓ can drag back and forth between canvases without interruption,

    ✓ can transform (mirror) dropped image without interruption of manipulation.


    Step 1 – prepare canvases, load images, arrange everything for demo:

        //create two canvases
        var canvas0El = document.getElementById("c0");
        canvas0El.width = canvas0El.offsetWidth;
        canvas0El.height = canvas0El.parentElement.offsetHeight;
    
        var canvas1El = document.getElementById("c1");
        canvas1El.width = canvas1El.offsetWidth;
        canvas1El.height = canvas1El.parentElement.offsetHeight;
    
        var canvas0 = new fabric.Canvas('c0');
        canvas0.setBackgroundColor('rgba(19, 19, 19, 0.25)');
        canvas0.renderAll();
    
        var canvas1 = new fabric.Canvas('c1');
        canvas1.setBackgroundColor('rgba(92, 18, 18, 0.25)');
        canvas1.renderAll();
    
        // add loaded image on left canvas
        var onImageLoaded = function(oImg) {
            oImg.originX = "center";
            oImg.originY = "center";
    
            oImg.left = this.x;
            oImg.top = this.y;
    
            canvas0.add(oImg);
            oImg.canvas = canvas0;
            imgArrow = oImg;
        };
    
        var config = { crossOrigin: 'anonymous' };
    
        var baseUrl = "http://mbnsay.com/rayys/images";
        var url0 = baseUrl + "/arrow-right-green.png";
        var url1 = baseUrl + "/arrow-right-icon.png";
        var url2 = baseUrl + "/arrow-right-blue.png";
    
        // load some images
        fabric.Image.fromURL(url0, onImageLoaded.bind({ x: 56,  y: 96 }), config);
        fabric.Image.fromURL(url0, onImageLoaded.bind({ x: 156, y: 96 }), config);
    
        fabric.Image.fromURL(url1, onImageLoaded.bind({ x: 56,  y: 2*96 }), config);
        fabric.Image.fromURL(url1, onImageLoaded.bind({ x: 156, y: 2*96 }), config);
    
        fabric.Image.fromURL(url2, onImageLoaded.bind({ x: 56,  y: 3*96 }), config);
        fabric.Image.fromURL(url2, onImageLoaded.bind({ x: 156, y: 3*96 }), config);
    

    Step 2 – subscribe object:moving events on both canvases, and watch when object center crossing the canvas border. When object crosses the border, it has to be

    1. remove from source canvas,
    2. paste into destination canvas,
    3. migrate internal canvas transformations (will be explained separately)
        var onObjectMoving = function(p) {
            var viewport = p.target.canvas.calcViewportBoundaries();
    
            if (p.target.canvas === canvas0) {
                if (p.target.left > viewport.br.x) {
                    console.log("Migrate: left -> center");
                    migrateItem(canvas0, canvas1, p.target);
                    return;
                }
            }
            if (p.target.canvas === canvas1) {
                if (p.target.left < viewport.tl.x) {
                    console.log("Migrate: center -> left");
                    migrateItem(canvas1, canvas0, p.target);
                    return;
                }
            }
        };
    
        canvas0.on("object:moving", onObjectMoving);
        canvas1.on("object:moving", onObjectMoving);
    

    Step 3 – The core of the solution, migrate object between canvases not interrupting mouse manipulations. Hard to explain, just follow the comments in code.

        var migrateItem = function(fromCanvas, toCanvas, pendingImage) {
            // Just drop image from old canvas
            fromCanvas.remove(pendingImage);
    
            // We're going to trick fabric.js,
            // so we keep internal transforms of the source canvas, 
            // in order to inject it into destination canvas.
            var pendingTransform = fromCanvas._currentTransform;
            fromCanvas._currentTransform = null;
    
            // Make shortcuts for fabric.util.removeListener and fabric.util.addListener
            var removeListener = fabric.util.removeListener;
            var addListener = fabric.util.addListener;
    
            // Re-arrange subscriptions for source canvas
            {
                removeListener(fabric.document, 'mouseup', fromCanvas._onMouseUp);
                removeListener(fabric.document, 'touchend', fromCanvas._onMouseUp);
    
                removeListener(fabric.document, 'mousemove', fromCanvas._onMouseMove);
                removeListener(fabric.document, 'touchmove', fromCanvas._onMouseMove);
    
                addListener(fromCanvas.upperCanvasEl, 'mousemove', fromCanvas._onMouseMove);
                addListener(fromCanvas.upperCanvasEl, 'touchmove', fromCanvas._onMouseMove, {
                    passive: false
                });
    
                if (isTouchDevice) {
                    // Wait 500ms before rebinding mousedown to prevent double triggers
                    // from touch devices
                    var _this = fromCanvas;
                    setTimeout(function() {
                        addListener(_this.upperCanvasEl, 'mousedown', _this._onMouseDown);
                    }, 500);
                }
            }
    
            // Re-arrange subscriptions for destination canvas
            {
                addListener(fabric.document, 'touchend', toCanvas._onMouseUp, {
                    passive: false
                });
                addListener(fabric.document, 'touchmove', toCanvas._onMouseMove, {
                    passive: false
                });
    
                removeListener(toCanvas.upperCanvasEl, 'mousemove', toCanvas._onMouseMove);
                removeListener(toCanvas.upperCanvasEl, 'touchmove', toCanvas._onMouseMove);
    
                if (isTouchDevice) {
                    // Unbind mousedown to prevent double triggers from touch devices
                    removeListener(toCanvas.upperCanvasEl, 'mousedown', toCanvas._onMouseDown);
                } else {
                    addListener(fabric.document, 'mouseup', toCanvas._onMouseUp);
                    addListener(fabric.document, 'mousemove', toCanvas._onMouseMove);
                }
            }
    
            // We need this timer, because we want Fabric.js to complete pending render
            // before we inject, because it causes some unpleasant image jumping.
            setTimeout(function() {
                // Add image to destination canvas,
                pendingImage.scaleX *= -1;
                pendingImage.canvas = toCanvas;
                pendingImage.migrated = true;
                toCanvas.add(pendingImage);
    
                // and inject transforms from source canvas
                toCanvas._currentTransform = pendingTransform;
    
                // as we have mirrored the image, we mirror transforms too
                toCanvas._currentTransform.scaleX *= -1;
                toCanvas._currentTransform.original.scaleX *= -1;
    
                // finally don't forget to make pasted object selected
                toCanvas.setActiveObject(pendingImage);
            }, 10);
        };
    

    Have fun!

提交回复
热议问题