Search code examples
javascriptjqueryhtmldrag-and-drop

Adding more images to the canvas to drag and drop


I want to be able to spawn a new image to the canvas at the click of a button, rather than having to manually edit the code.

I have the following HTML5/JavaScript code that allows images to be dragged and dropped between multiple canvases and it works perfectly for what I require.

What I am doing:

<canvas style="float: left" height="125" width="400" id="cvs1">[No canvas support]</canvas>
<canvas style="float: left; margin-left: 100px" height="125" width="400" id="cvs2">[No canvas support]</canvas>

<script src="http://www.rgraph.net/libraries/RGraph.common.core.js" ></script>

<script>
    window.onload = function ()
    {
        var canvas1 = document.getElementById("cvs1");
        var canvas2 = document.getElementById("cvs2");
        var context1 = canvas1.getContext('2d');
        var context2 = canvas2.getContext('2d');
        var imageXY  = {x: 5, y: 5};




        /**
        * This draws the image to the canvas
        */
        function Draw ()
        {
            //Clear both canvas first
            canvas1.width = canvas1.width
            canvas2.width = canvas2.width
            
            //Draw a red rectangle around the image
            if (state && state.dragging) {
                state.canvas.getContext('2d').strokeStyle = 'red';
                state.canvas.getContext('2d').strokeRect(imageXY.x - 2.5,
                                                         imageXY.y - 2.5,
                                                         state.image.width + 5,
                                                         state.image.height + 5);
            }
            
            // Now draw the image
            state.canvas.getContext('2d').drawImage(state.image, imageXY.x, imageXY.y);
        }




        canvas2.onclick =
        canvas1.onclick = function (e)
        {
            
            if (state && state.dragging) {
                state.dragging = false;
                Draw();
                return;
            }





            var mouseXY = RGraph.getMouseXY(e);

            state.canvas    = e.target;
            
            if (   mouseXY[0] > imageXY.x
                && mouseXY[0] < (imageXY.x + state.image.width)
                && mouseXY[1] > imageXY.y
                && mouseXY[1] < (imageXY.y + state.image.height)) {

                state.dragging       = true;
                state.originalMouseX = mouseXY[0];
                state.originalMouseY = mouseXY[1];
                state.offsetX         = mouseXY[0] - imageXY.x;
                state.offsetY         = mouseXY[1] - imageXY.y;

            }
        }

        canvas1.onmousemove =
        canvas2.onmousemove = function (e)
        {

            if (state.dragging) {
            
                state.canvas = e.target;
                
                var mouseXY = RGraph.getMouseXY(e);
                
                // Work how far the mouse has moved since the mousedon event was triggered
                var diffX = mouseXY[0] - state.originalMouseX;
                var diffY = mouseXY[1] - state.originalMouseY;

                imageXY.x = state.originalMouseX + diffX - state.offsetX;
                imageXY.y = state.originalMouseY + diffY - state.offsetY;
                
                Draw();
                
                e.stopPropagation();
            }
        }

        /**
        * Load the image on canvas1 initially and set the state up with some defaults
        */
        state = {}
        state.dragging     = false;
        state.canvas       = document.getElementById("cvs1");
        state.image        =  new Image();
        state.image.src    = 'http://www.rgraph.net/images/logo.png';
        state.offsetX      = 0;
        state.offsetY      = 0;

        state.image.onload = function ()
        {
            Draw();
        }
    }
</script>

This can also be seen on this JS Fiddle (Note: you have to click the image before you can drag it)

The problem I am having:

I would like to add more images to the canvases so that any of the images can then be dragged and dropped between however many canvases I choose to create.

I can quite easily add more canvases to the page to drag and drop between, however when it comes to adding/spawning more images to the canvases I cannot get it to work.

The only way I can think of being able to do this is by repeating the Draw() function for every single image that gets added. That would mean if I wanted 30 images to be able to drag and drop between 10 different canvases for example, I would need to repeat the Draw() function 30 times. Surely that cannot be the best way to do this?

Unless I am missing something very obvious I cannot see another way of doing this?


Solution

  • Here’s how to configure your code to create multiple objects and click-drag between canvases.

    Demo: http://jsfiddle.net/m1erickson/Bnb6A/

    enter image description here

    Code an object factory function that creates new draggable objects and put all new objects in an array.

    Keep this information about each draggable object:

    • dragging (true/false) indicating if this object is currently being dragged
    • image: the image for this object
    • x,y: the current top,left position of this object
    • width,height: the size of this object
    • offsetX,offsetY: indicates where the mouse clicked relative to top,left of this object
    • draw: (a function) that allows this object to draw itself (surrounded by a red rect if it’s being dragged)

    On click:

    • Get the mouse position
    • Clear both canvases
    • Iterate through the object array
    • If an object is being dragged, unset the dragging flag
    • If an object is now selected, set the dragging flag and set the offsetX,offsetY for the starting mouse position relative to this object.
    • Redraw all objects

    On mousemove:

    • Get the mouse position
    • Clear both canvases
    • Iterate through the object array
    • If an object is being dragged, move it to the mouse position (setting x,y adjusted for the offset)
    • Redraw all objects

    Code:

    <!doctype html>
    <html>
    <head>
    <link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
    <script src="http://code.jquery.com/jquery.min.js"></script>
    
    <style>
        body{ background-color: ivory; padding:20px; }
        canvas{ border: 1px solid #808080; }
    </style>
    
    <script>
        $(function(){
    
            var canvas1 = document.getElementById("cvs1");
            var canvas2 = document.getElementById("cvs2");
    
            var contexts=[];
            contexts.push(canvas1.getContext('2d'));
            contexts.push(canvas2.getContext('2d'));
    
    
            function clearAll(){
                //Clear both canvas first
                canvas1.width = canvas1.width
                canvas2.width = canvas2.width
            }
    
            canvas1.onclick=function(e){ handleClick(e,1); };
            canvas2.onclick=function(e){ handleClick(e,2); };
            //
            function handleClick(e,contextIndex){
                e.stopPropagation();
    
                var mouseX=parseInt(e.clientX-e.target.offsetLeft);
                var mouseY=parseInt(e.clientY-e.target.offsetTop);
    
                clearAll();
    
                for(var i=0;i<states.length;i++){
    
                    var state=states[i];
    
                    if(state.dragging){
                        state.dragging=false;
                        state.draw();
                        continue;
                    }
    
                    if ( state.contextIndex==contextIndex
                        && mouseX>state.x && mouseX<state.x+state.width
                        && mouseY>state.y && mouseY<state.y+state.height)
                    {
                        state.dragging=true;
                        state.offsetX=mouseX-state.x;
                        state.offsetY=mouseY-state.y;
                        state.contextIndex=contextIndex;
                    }
    
                    state.draw();
                }
            }
    
            canvas1.onmousemove = function(e){ handleMousemove(e,1); }
            canvas2.onmousemove = function(e){ handleMousemove(e,2); }
            //
            function handleMousemove(e,contextIndex){
                e.stopPropagation();
    
                var mouseX=parseInt(e.clientX-e.target.offsetLeft);
                var mouseY=parseInt(e.clientY-e.target.offsetTop);
    
                clearAll();
    
                for(var i=0;i<states.length;i++){
    
                    var state=states[i];
    
                    if (state.dragging) {
                        state.x = mouseX-state.offsetX;
                        state.y = mouseY-state.offsetY;
                        state.contextIndex=contextIndex;
                    }
                    state.draw();
                }
            }
    
    
            var states=[];
    
            var img=new Image();
            img.onload=function(){
                states.push(addState(0,0,img));
            }
            img.src="http://www.rgraph.net/images/logo.png";
    
    
    
    
            function addState(x,y,image){
                    state = {}
                    state.dragging=false;
                    state.contextIndex=1;
                    state.image=image;
                    state.x=x;
                    state.y=y;
                    state.width=image.width;
                    state.height=image.height;
                    state.offsetX=0;
                    state.offsetY=0;
                    state.draw=function(){
                        var context=contexts[this.contextIndex-1];
                        if (this.dragging) {
                            context.strokeStyle = 'red';
                            context.strokeRect(this.x,this.y,this.width+5,this.height+5)
                        }
                        context.drawImage(this.image,this.x,this.y);
                    }
                    state.draw();
                    return(state);
            }
    
            $("#more").click(function(){
                states.push(addState(states.length*100,0,img));
            });
    
    
        }); // end $(function(){});
    </script>
    
    </head>
    
    <body>
      <button id="more">Add Image</button><br>
      <canvas height="125" width="300" id="cvs1">[No canvas support]</canvas><br>
      <canvas height="125" width="300" id="cvs2">[No canvas support]</canvas>
    </body>
    </html>