Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Simple HTML5 SVG Move and Resize Tool

5.00/5 (11 votes)
19 Jun 2013CPOL7 min read 77.3K   4.1K  
In this article, I explain, step by step, how we can implement a simple tool for moving and resizing HTML elements, using HTML5 SVG.

Sample Image

Table of content

Introduction

HTML5 gives us two new elements, that can be used for drawing some graphics in our page: canvas and svg. A major difference between canvas drawings and svg drawings is that the svg drawings are actually HTML elements. This article demonstrates, how we can take advantage of that feature, for creating an interactive border (svg drawing), that can be used for moving and resizing HTML elements.

In part 1 of this article, we'll go over the steps of creating our "move and resize" border.

In part 2 of this article, we'll encapsulate the result of part 1, with an object that can be used as a generic "move and resize" tool.

Background

This article assumes basic familiarity with SVG and jQuery. For more information about it, you can read the following links:

Part 1 - Creating a simple SVG movable and resizable shape

Draw the SVG shape

Create HTML5 SVG shape

The first step for drawing a movable and resizable shape is creating an HTML file that contains the shape.

Using HTML4.01 we declare the document type as follows:

XML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
         "http://www.w3.org/TR/html4/loose.dtd">

Using HTML5 the declaration is much shorter:

XML
<!DOCTYPE html>

After creating the HTML5 document, we can add a simple SVG shape into it:

XML
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="width:350px;height:200px"
    id="myShape">
    <ellipse cx="50%" cy="50%" rx="50%" ry="50%" 
        stroke="#FF0000" stroke-width="2" fill="#800000" />
</svg>

Wrap the SVG shape with resizing indicators

For wrapping our shape with a resizing border, we add another SVG drawing:

XML
<div style="left:100px;top:100px;position:relative;width:350px;height:200px" id="wrapper">

    <div style="left:0px;top:0px;position:absolute" class="internalWrapper" >
        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="width:350px;height:200px"
            id="myShape">
            <ellipse cx="50%" cy="50%" rx="50%" ry="50%" 
                stroke="#FF0000" stroke-width="2" fill="#800000" />
        </svg>
    </div>

    <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
        style="left:0px;top:0px;position:relative;width:100%;height:100%" >
        <line x1="0" y1="0" x2="100%" y2="0" stroke="#808080" 
          stroke-width="1" stroke-dasharray="5,5" />
        <line x1="0" y1="100%" x2="100%" y2="100%" 
          stroke="#808080" stroke-width="1" stroke-dasharray="5,5" />
        <line x1="0" y1="0" x2="0" y2="100%" stroke="#808080" 
          stroke-width="1" stroke-dasharray="5,5" />
        <line x1="100%" y1="0" x2="100%" y2="100%" stroke="#808080" 
          stroke-width="1" stroke-dasharray="5,5" />
        <circle cx="0" cy="0" r="3" stroke="#0000FF" stroke-width="1" fill="#CCCCFF" />
        <circle cx="100%" cy="0" r="3" stroke="#0000FF" stroke-width="1" fill="#CCCCFF" />
        <circle cx="0" cy="100%" r="3" stroke="#0000FF" stroke-width="1" fill="#CCCCFF" />
        <circle cx="100%" cy="100%" r="3" stroke="#0000FF" stroke-width="1" fill="#CCCCFF" />
    </svg>

</div>

In the code above, we wrap our SVG shape with a div element and, use another svg element for drawing the resizing border. We can achieve the same result, by simply draw the shape and its resizing border, using the same svg element. But, we will need that separation later, when the things will be little more complicated.

Apply mouse actions

Include the jQuery library

Since we are going to use some jQuery in our page, we include the jQuery library:

XML
<script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>

In our case, the jQuery's code is located under the js folder. (The code for the jQuery library can be downloaded from: http://jquery.com/)

Draw SVG shapes for the mouse actions

As mentioned before, the svg drawings are HTML elements (in contrast to canvas drawings). Therefore, in order to apply mouse actions using those elements, we can simply handle the appropriate mouse-events of them. Since the elements of the drawn border are too tiny, we add another bigger transparent elements which give us more region for catching the mouse events:

XML
<line x1="0" y1="0" x2="100%" y2="0" stroke="#000" stroke-width="5" opacity="0" />
<line x1="0" y1="100%" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" />
<line x1="0" y1="0" x2="0" y2="100%" stroke="#000" stroke-width="5" opacity="0" />
<line x1="100%" y1="0" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" />
<circle cx="0" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" />
<circle cx="100%" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" />
<circle cx="0" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" />
<circle cx="100%" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" />

In addition to the resize border, we add a transparent rectangle for enabling the move action:

XML
<rect x="0" y="0" width="100%" height="100%" fill-opacity="0.5" opacity="0" />

Handle mouse events

Define the possible mouse actions

Before handling mouse-events to apply actions on the element, we define the supported actions:

JavaScript
var ActionsEnum = {
    None: 0,
    LeftResize: 1,
    TopResize: 2,
    RightResize: 3,
    BottomResize: 4,
    TopLeftResize: 5,
    BottomLeftResize: 6,
    TopRightResize: 7,
    BottomRightResize: 8,
    Move: 9
}
Set the current mouse action appropriately

For storing the current action, we add class for each action-trigger (the SVG element that activates the action):

XML
<rect x="0" y="0" width="100%" height="100%" fill-opacity="0.5" opacity="0" 
    class="moveActionTrigger" />
<line x1="0" y1="0" x2="100%" y2="0" stroke="#000" stroke-width="5" opacity="0" 
    class="topActionTrigger" />
<line x1="0" y1="100%" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="bottomActionTrigger" />
<line x1="0" y1="0" x2="0" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="leftActionTrigger" />
<line x1="100%" y1="0" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="rightActionTrigger" />
<circle cx="0" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="topLeftActionTrigger" />
<circle cx="100%" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="topRightActionTrigger" />
<circle cx="0" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="bottomLeftActionTrigger" />
<circle cx="100%" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="bottomRightActionTrigger" />

add a variable for storing the current action:

JavaScript
var currentAction = ActionsEnum.None;

handle the mousedown event of each action-trigger to store the appropriate action:

JavaScript
var externalWrapperQueryStr = '#wrapper';

// Query strings for the action-triggers.
var moveActionTriggerQueryStr = externalWrapperQueryStr + ' .moveActionTrigger';
var topActionTriggerQueryStr = externalWrapperQueryStr + ' .topActionTrigger';
var bottomActionTriggerQueryStr = externalWrapperQueryStr + ' .bottomActionTrigger';
var leftActionTriggerQueryStr = externalWrapperQueryStr + ' .leftActionTrigger';
var rightActionTriggerQueryStr = externalWrapperQueryStr + ' .rightActionTrigger';
var topLeftActionTriggerQueryStr = externalWrapperQueryStr + ' .topLeftActionTrigger';
var topRightActionTriggerQueryStr = externalWrapperQueryStr + ' .topRightActionTrigger';
var bottomLeftActionTriggerQueryStr = externalWrapperQueryStr + ' .bottomLeftActionTrigger';
var bottomRightActionTriggerQueryStr = externalWrapperQueryStr + ' .bottomRightActionTrigger';

function initializeEventHandlers() {
    $(moveActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.Move;
    });

    $(topActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.TopResize;
    });

    $(bottomActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.BottomResize;
    });

    $(leftActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.LeftResize;
    });

    $(rightActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.RightResize;
    });

    $(topLeftActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.TopLeftResize;
    });

    $(topRightActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.TopRightResize;
    });

    $(bottomLeftActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.BottomLeftResize;
    });

    $(bottomRightActionTriggerQueryStr).mousedown(function (event) {
        currentAction = ActionsEnum.BottomRightResize;
    });
}

$(function () {
    initializeEventHandlers();
});

and, handle the document's mouseup event to clear the stored action:

JavaScript
function initializeEventHandlers() {

    // ...

    $(document).mouseup(function (event) {
        // Clear the current action.
        currentAction = ActionsEnum.None;
    });
}
Apply the appropriate mouse action

For applying the appropriate action on our shape, we handle the document's mousemove event, to perform the appropriate action:

  • Set the appropriate event-handler:
    JavaScript
    function initializeEventHandlers() {
    
        // ...
    
        $(document).mousemove(function (event) {
            onMouseMove(event);
        });
    }
  • Calculate the mouse position's delta:
    JavaScript
    function onMouseMove(event) {
        var currMouseX = event.clientX;
        var currMouseY = event.clientY;
    
        var deltaX = currMouseX - lastMouseX;
        var deltaY = currMouseY - lastMouseY;
    
        applyMouseMoveAction(deltaX, deltaY);
    
        lastMouseX = event.pageX;
        lastMouseY = event.pageY;
    }
  • Update the size and position appropriately, according to the current action:
    JavaScript
    function applyMouseMoveAction(deltaX, deltaY) {
        var deltaTop = 0;
        var deltaLeft = 0;
        var deltaWidth = 0;
        var deltaHeight = 0;
    
        if (currentAction == ActionsEnum.RightResize ||
            currentAction == ActionsEnum.TopRightResize ||
            currentAction == ActionsEnum.BottomRightResize) {
            deltaWidth = deltaX;
        }
    
        if (currentAction == ActionsEnum.LeftResize ||
            currentAction == ActionsEnum.TopLeftResize ||
            currentAction == ActionsEnum.BottomLeftResize) {
            deltaWidth = -deltaX;
            deltaLeft = deltaX;
        }
    
        if (currentAction == ActionsEnum.BottomResize ||
            currentAction == ActionsEnum.BottomLeftResize ||
            currentAction == ActionsEnum.BottomRightResize) {
            deltaHeight = deltaY;
        }
    
        if (currentAction == ActionsEnum.TopResize ||
            currentAction == ActionsEnum.TopLeftResize ||
            currentAction == ActionsEnum.TopRightResize) {
            deltaHeight = -deltaY;
            deltaTop = deltaY;
        }
    
        if (currentAction == ActionsEnum.Move) {
            deltaLeft = deltaX;
            deltaTop = deltaY;
        }
    
        updatePosition(deltaLeft, deltaTop);
        updateSize(deltaWidth, deltaHeight);
        adjustWrapper();
    }
  • Update the wrapper's position:
    JavaScript
    function updatePosition(deltaLeft, deltaTop) {
        // Calculate the new position.
        var elemLeft = parseInt($(externalWrapperQueryStr).css('left'));
        var elemTop = parseInt($(externalWrapperQueryStr).css('top'));
        var newLeft = elemLeft + deltaLeft;
        var newTop = elemTop + deltaTop;
    
        // Set the new position.
        $(externalWrapperQueryStr).css('left', newLeft + 'px');
        $(externalWrapperQueryStr).css('top', newTop + 'px');
    }
  • Update the size of the SVG shape:
    JavaScript
    function updateSize(deltaWidth, deltaHeight) {
        // Calculate the new size.
        var elemWidth = parseInt($("#myShape").width());
        var elemHeight = parseInt($("#myShape").height());
        var newWidth = elemWidth + deltaWidth;
        var newHeight = elemHeight + deltaHeight;
    
        // Don't allow a too small size.             
        if (newWidth < 0) {
            newWidth = 0;
        }
        if (newHeight < 0) {
            newHeight = 0;
        }
    
        // Set the new size.
        $("#myShape").css('width', newWidth + 'px');
        $("#myShape").css('height', newHeight + 'px');
    }
  • Adjust the wrapper's elements according to the size of the SVG shape:
    JavaScript
    var internalWrapperQueryStr = externalWrapperQueryStr + ' .internalWrapper';
    
    function adjustWrapper() {
        var elemWidth = $("#myShape").width();
        var elemHeight = $("#myShape").height();
    
        $(internalWrapperQueryStr).width(elemWidth);
        $(internalWrapperQueryStr).height(elemHeight);
        $(externalWrapperQueryStr).width(elemWidth);
        $(externalWrapperQueryStr).height(elemHeight);
    }

Compatibility with different browsers

Up to here, we created a SVG shape and, wrapped it with SVG drawing for enabling moving and resizing that shape. When running it using Internet Explorer, the result is as expected:

Internet Explorer result

But, when running it using Chrome or Firefox, we get a different result (The whole of the SVG drawings that out of the boundaries of the svg element, aren't shown):

Chrome and Firefox result

In order to fix that behavior, we change the resizing border to be drawn using appropriate fixed values (instead of relational values). For that purpose, we:

  • Give a place for the SVG drawings of the resizing border, to be in the svg element's boundaries:
    • Set the internal wrapper's position to be at 8 (the radius of the corner action trigger) pixels offset:
      XML
      <div style="left:8px;top:8px;position:absolute" class="internalWrapper" >
      
          <!-- ... -->
      
      </div>
    • Set the external wrapper's size to be 16 pixels (8 pixels for each side) more than the SVG shape's size:
      JavaScript
      var cornerActionTriggerRadius = 8;
      
      function adjustWrapper() {
          var elemWidth = parseInt($("#myShape").width());
          var elemHeight = parseInt($("#myShape").height());
          var externalWrapperWidth = (elemWidth + cornerActionTriggerRadius * 2) + 'px';
          var externalWrapperHeight = (elemHeight + cornerActionTriggerRadius * 2) + 'px';
      
          $(internalWrapperQueryStr).width($("#myShape").width());
          $(internalWrapperQueryStr).height($("#myShape").height());
          $(externalWrapperQueryStr).width(externalWrapperWidth);
          $(externalWrapperQueryStr).height(externalWrapperHeight);
      }
  • Adjust the SVG drawings of the resizing border, to fit the boundaries of the svg element:
    • Add a class for each element of the resizing-border drawings:
      XML
      <line x1="0" y1="0" x2="100%" y2="0" stroke="#808080" 
        stroke-width="1" stroke-dasharray="5,5" class="topDrawing" />
      <line x1="0" y1="100%" x2="100%" y2="100%" stroke="#808080" 
        stroke-width="1" stroke-dasharray="5,5" class="bottomDrawing" />
      <line x1="0" y1="0" x2="0" y2="100%" stroke="#808080" stroke-width="1" 
        stroke-dasharray="5,5" class="leftDrawing" />
      <line x1="100%" y1="0" x2="100%" y2="100%" stroke="#808080" 
        stroke-width="1" stroke-dasharray="5,5" class="rightDrawing" />
      <circle cx="0" cy="0" r="3" stroke="#0000FF" 
        stroke-width="1" fill="#CCCCFF" class="topLeftDrawing" />
      <circle cx="100%" cy="0" r="3" stroke="#0000FF" 
        stroke-width="1" fill="#CCCCFF" class="topRightDrawing" />
      <circle cx="0" cy="100%" r="3" stroke="#0000FF" 
        stroke-width="1" fill="#CCCCFF" class="bottomLeftDrawing" />
      <circle cx="100%" cy="100%" r="3" stroke="#0000FF" 
        stroke-width="1" fill="#CCCCFF" class="bottomRightDrawing" />
    • Define functions for setting the appropriate attributes of the svg elements:
      JavaScript
      function setRectangleAttributes(rectQueryStr, x, y, width, height) {
          var rectElem = $(rectQueryStr);
          rectElem.attr('x', x);
          rectElem.attr('y', y);
          rectElem.attr('width', width);
          rectElem.attr('height', height);
      }
      
      function setLineAttributes(lineQueryStr, x1, y1, x2, y2) {
          var lineElem = $(lineQueryStr);
          lineElem.attr('x1', x1);
          lineElem.attr('y1', y1);
          lineElem.attr('x2', x2);
          lineElem.attr('y2', y2);
      }
      
      function setCircleAttributes(circleQueryStr, cx, cy) {
          var circleElem = $(circleQueryStr);
          circleElem.attr('cx', cx);
          circleElem.attr('cy', cy);
      }
    • Set the elements of the resizing border, according to the size of the SVG shape:
      JavaScript
      // Query strings for the resizing border's drawings.
      var topDrawingQueryStr = externalWrapperQueryStr + ' .topDrawing';
      var bottomDrawingQueryStr = externalWrapperQueryStr + ' .bottomDrawing';
      var leftDrawingQueryStr = externalWrapperQueryStr + ' .leftDrawing';
      var rightDrawingQueryStr = externalWrapperQueryStr + ' .rightDrawing';
      var topLeftDrawingQueryStr = externalWrapperQueryStr + ' .topLeftDrawing';
      var topRightDrawingQueryStr = externalWrapperQueryStr + ' .topRightDrawing';
      var bottomLeftDrawingQueryStr = externalWrapperQueryStr + ' .bottomLeftDrawing';
      var bottomRightDrawingQueryStr = externalWrapperQueryStr + ' .bottomRightDrawing';
      
      function adjustResizingBorder() {
          var elemWidth = parseInt($("#myShape").width());
          var elemHeight = parseInt($("#myShape").height());
      
          // Get the minimum and maximum values for X and Y.
          var minX = cornerActionTriggerRadius + 'px';
          var minY = cornerActionTriggerRadius + 'px';
          var maxX = (cornerActionTriggerRadius + elemWidth) + 'px';
          var maxY = (cornerActionTriggerRadius + elemHeight) + 'px';
      
          // Adjust moving rectange.
          setRectangleAttributes(moveActionTriggerQueryStr, minX, 
                   minY, elemWidth + 'px', elemHeight + 'px');
      
          // Adjust resizing border lines.
          setLineAttributes(topDrawingQueryStr, minX, minY, maxX, minY);
          setLineAttributes(bottomDrawingQueryStr, minX, maxY, maxX, maxY);
          setLineAttributes(leftDrawingQueryStr, minX, minY, minX, maxY);
          setLineAttributes(rightDrawingQueryStr, maxX, minY, maxX, maxY);
          setLineAttributes(topActionTriggerQueryStr, minX, minY, maxX, minY);
          setLineAttributes(bottomActionTriggerQueryStr, minX, maxY, maxX, maxY);
          setLineAttributes(leftActionTriggerQueryStr, minX, minY, minX, maxY);
          setLineAttributes(rightActionTriggerQueryStr, maxX, minY, maxX, maxY);
      
          // Adjust resizing border circles.
          setCircleAttributes(topLeftDrawingQueryStr, minX, minY);
          setCircleAttributes(topRightDrawingQueryStr, maxX, minY);
          setCircleAttributes(bottomLeftDrawingQueryStr, minX, maxY);
          setCircleAttributes(bottomRightDrawingQueryStr, maxX, maxY);
          setCircleAttributes(topLeftActionTriggerQueryStr, minX, minY);
          setCircleAttributes(topRightActionTriggerQueryStr, maxX, minY);
          setCircleAttributes(bottomLeftActionTriggerQueryStr, minX, maxY);
          setCircleAttributes(bottomRightActionTriggerQueryStr, maxX, maxY);
      }

Improve user experience

Mouse-over effect

For adding a mouse-over effect for the action-triggers, we add a class for each action-trigger's element:

XML
<rect x="0" y="0" width="100%" height="100%" fill-opacity="0.5" opacity="0" 
    class="actionTrigger moveActionTrigger" />
<line x1="0" y1="0" x2="100%" y2="0" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger topActionTrigger" />
<line x1="0" y1="100%" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger bottomActionTrigger" />
<line x1="0" y1="0" x2="0" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger leftActionTrigger" />
<line x1="100%" y1="0" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger rightActionTrigger" />
<circle cx="0" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger topLeftActionTrigger" />
<circle cx="100%" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger topRightActionTrigger" />
<circle cx="0" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger bottomLeftActionTrigger" />
<circle cx="100%" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger bottomRightActionTrigger" />

and, add a style for the hover state of that class:

CSS
<style type="text/css">
    .actionTrigger
    {
        transition: opacity 0.5s;
        opacity: 0;
    }
        
    .actionTrigger:hover
    {
        transition: opacity 0.3s;
        opacity: 0.3;
    }
</style>

Appropriate mouse-cursor

For showing an appropriate mouse-cursor for the action-triggers, we can set the cursor style of the appropriate element, to the appropriate mouse-cursor:

XML
<rect x="0" y="0" width="100%" height="100%" fill-opacity="0.5" opacity="0" 
    class="actionTrigger moveActionTrigger" style="cursor:move" />
<line x1="0" y1="0" x2="100%" y2="0" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger topActionTrigger" style="cursor:n-resize" />
<line x1="0" y1="100%" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger bottomActionTrigger" style="cursor:s-resize" />
<line x1="0" y1="0" x2="0" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger leftActionTrigger" style="cursor:w-resize" />
<line x1="100%" y1="0" x2="100%" y2="100%" stroke="#000" stroke-width="5" opacity="0" 
    class="actionTrigger rightActionTrigger" style="cursor:e-resize" />
<circle cx="0" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger topLeftActionTrigger" style="cursor:nw-resize" />
<circle cx="100%" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger topRightActionTrigger" style="cursor:ne-resize" />
<circle cx="0" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger bottomLeftActionTrigger" style="cursor:sw-resize" />
<circle cx="100%" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" opacity="0" 
    class="actionTrigger bottomRightActionTrigger" style="cursor:se-resize" />

The result is:

Part 1 result

Part 2 - Encapsulating the result of Part 1 within an object

Encapsulate the move and resize behavior within an object

Wrap an HTML element with move and resize behavior

In part 1 of this article, we created a movable and resizable shape, using some SVG elements. In this part of the article, we'll create a reusable object, that can by used, for applying the move and resize behavior, for any HTML element (that is affected from the position, left, top, width and height CSS properties).

The first step is: creating an object for encapsulating the move and resize logic:

JavaScript
function MoveAndResizeElementWrapper(elementToWrap) {
    this.originalElement = elementToWrap;
}

In this object constructor function, we get an HTML DOM element and, store it in a property for a later use.

In addition to that, we add properties for storing the HTML text for the wrapper's elements (the same elements as we created on part 1):

JavaScript
var MoveAndResizeTool_ElementWrapper_wrappersCounter = 0;

function MoveAndResizeElementWrapper(elementToWrap) {
    
    // ...

    // Since we want a unique id for each wrapper, we add a counter value to the end of each id.
    MoveAndResizeTool_ElementWrapper_wrappersCounter++;

    this.wrapperId = 'MoveAndResizeTool_ElementWrapper' +
        MoveAndResizeTool_ElementWrapper_wrappersCounter.toString();

    this.wrapperStr = '<div style="position:relative" id="' + this.wrapperId + '">' +
        '<div style="left:8px;top:8px;position:absolute" class="internalWrapper"></div>' +
        '</div>';
}

MoveAndResizeElementWrapper.prototype.resizingBorderStr =
    '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" 
      style="left:0px;top:0px;position:relative;width:100%;height:100%" >' +
    '<style type="text/css"> .actionTrigger { transition: opacity 0.5s; ' + 
    'opacity: 0;} .actionTrigger:hover{transition: opacity 0.3s;opacity: 0.3;}</style>' +
    '<line x1="0" y1="0" x2="100%" y2="0" stroke="#808080" 
      stroke-width="1" stroke-dasharray="5,5" class="topDrawing" />' +
    '<line x1="0" y1="100%" x2="100%" y2="100%" stroke="#808080" 
      stroke-width="1" stroke-dasharray="5,5" class="bottomDrawing" />' +
    '<line x1="0" y1="0" x2="0" y2="100%" stroke="#808080" 
      stroke-width="1" stroke-dasharray="5,5" class="leftDrawing" />' +
    '<line x1="100%" y1="0" x2="100%" y2="100%" stroke="#808080" 
      stroke-width="1" stroke-dasharray="5,5" class="rightDrawing" />' +
    '<circle cx="0" cy="0" r="3" stroke="#0000FF" stroke-width="1" 
      fill="#CCCCFF" class="topLeftDrawing" />' +
    '<circle cx="100%" cy="0" r="3" stroke="#0000FF" stroke-width="1" 
      fill="#CCCCFF" class="topRightDrawing" />' +
    '<circle cx="0" cy="100%" r="3" stroke="#0000FF" stroke-width="1" 
      fill="#CCCCFF" class="bottomLeftDrawing" />' +
    '<circle cx="100%" cy="100%" r="3" stroke="#0000FF" stroke-width="1" 
      fill="#CCCCFF" class="bottomRightDrawing" />' +
    '<rect x="0" y="0" width="100%" height="100%" fill-opacity="0.5" 
      opacity="0" class="actionTrigger moveActionTrigger" style="cursor:move" />' +
    '<line x1="0" y1="0" x2="100%" y2="0" stroke="#000" stroke-width="5" 
      opacity="0" class="actionTrigger topActionTrigger" style="cursor:n-resize" />' +
    '<line x1="0" y1="100%" x2="100%" y2="100%" stroke="#000" stroke-width="5" 
      opacity="0" class="actionTrigger bottomActionTrigger" style="cursor:s-resize" />' +
    '<line x1="0" y1="0" x2="0" y2="100%" stroke="#000" stroke-width="5" 
      opacity="0" class="actionTrigger leftActionTrigger" style="cursor:w-resize" />' +
    '<line x1="100%" y1="0" x2="100%" y2="100%" stroke="#000" stroke-width="5" 
      opacity="0" class="actionTrigger rightActionTrigger" style="cursor:e-resize"/>' +
    '<circle cx="0" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" 
      opacity="0" class="actionTrigger topLeftActionTrigger" style="cursor:nw-resize" />' +
    '<circle cx="100%" cy="0" r="8" stroke="#000" stroke-width="0" fill="#000" 
      opacity="0" class="actionTrigger topRightActionTrigger" style="cursor:ne-resize" />' +
    '<circle cx="0" cy="100%" r="8" stroke="#000" stroke-width="0" fill="#000" 
      opacity="0" class="actionTrigger bottomLeftActionTrigger" style="cursor:sw-resize" />' +
    '<circle cx="100%" cy="100%" r="8" stroke="#000" stroke-width="0" 
      fill="#000" opacity="0" class="actionTrigger 
      bottomRightActionTrigger" style="cursor:se-resize" />' +
    '</svg>';

add properties for storing the query strings for the wrapper's elements:

JavaScript
function MoveAndResizeElementWrapper(elementToWrap) {
    
    // ...

    this.externalWrapperQueryStr = '#' + this.wrapperId;
    this.internalWrapperQueryStr = this.externalWrapperQueryStr + ' .internalWrapper';

    // Query strings for the action-triggers.
    this.moveActionTriggerQueryStr = this.externalWrapperQueryStr + ' .moveActionTrigger';
    this.topActionTriggerQueryStr = this.externalWrapperQueryStr + ' .topActionTrigger';
    this.bottomActionTriggerQueryStr = this.externalWrapperQueryStr + ' .bottomActionTrigger';
    this.leftActionTriggerQueryStr = this.externalWrapperQueryStr + ' .leftActionTrigger';
    this.rightActionTriggerQueryStr = this.externalWrapperQueryStr + ' .rightActionTrigger';
    this.topLeftActionTriggerQueryStr = this.externalWrapperQueryStr + ' .topLeftActionTrigger';
    this.topRightActionTriggerQueryStr = this.externalWrapperQueryStr + ' .topRightActionTrigger';
    this.bottomLeftActionTriggerQueryStr = this.externalWrapperQueryStr + ' .bottomLeftActionTrigger';
    this.bottomRightActionTriggerQueryStr = this.externalWrapperQueryStr + ' .bottomRightActionTrigger';

    // Query strings for the resizing border's drawings.
    this.topDrawingQueryStr = this.externalWrapperQueryStr + ' .topDrawing';
    this.bottomDrawingQueryStr = this.externalWrapperQueryStr + ' .bottomDrawing';
    this.leftDrawingQueryStr = this.externalWrapperQueryStr + ' .leftDrawing';
    this.rightDrawingQueryStr = this.externalWrapperQueryStr + ' .rightDrawing';
    this.topLeftDrawingQueryStr = this.externalWrapperQueryStr + ' .topLeftDrawing';
    this.topRightDrawingQueryStr = this.externalWrapperQueryStr + ' .topRightDrawing';
    this.bottomLeftDrawingQueryStr = this.externalWrapperQueryStr + ' .bottomLeftDrawing';
    this.bottomRightDrawingQueryStr = this.externalWrapperQueryStr + ' .bottomRightDrawing';
}

and, add a function for wrapping the HTML element:

JavaScript
MoveAndResizeElementWrapper.prototype.addWrapperElements = function () {
    // Wrap the original element with a resizing border.
    $(this.originalElement).wrap(this.wrapperStr);
    $(this.internalWrapperQueryStr).after(this.resizingBorderStr);

    // Set the external wrapper's position to be 8 (the radius of the
    // corner action trigger) pixels less than the original element's position.
    var elemLeft = parseInt($(this.originalElement).css('left'));
    var elemTop = parseInt($(this.originalElement).css('top'));
    var wrapperLeft = (elemLeft - this.cornerActionTriggerRadius) + 'px';
    var wrapperTop = (elemTop - this.cornerActionTriggerRadius) + 'px';
    $(this.externalWrapperQueryStr).css('left', wrapperLeft);
    $(this.externalWrapperQueryStr).css('top', wrapperTop);
    $(this.externalWrapperQueryStr).css('position', $(this.originalElement).css('position'));

    // Set original element's position to be at the top-left corner of the internal wrapper.
    $(this.originalElement).css('left', 0);
    $(this.originalElement).css('top', 0);
    $(this.originalElement).css('position', 'relative');
}

After we have an HTML element, wrapped with the appropriate elements, we can handle the move and resize behavior, in the same manner as we did in part 1. For convenience, here are the functions (These functions are as same as the equivalent functions from part 1, with one difference - these functions use object properties instead of global variables):

  • Set the appropriate event handlers:
    JavaScript
    MoveAndResizeElementWrapper.prototype.initializeEventHandlers = function () {
        var wrapper = this;
    
        $(this.moveActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.Move;
        });
    
        $(this.topActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.TopResize;
        });
    
        $(this.bottomActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.BottomResize;
        });
    
        $(this.leftActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.LeftResize;
        });
    
        $(this.rightActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.RightResize;
        });
    
        $(this.topLeftActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.TopLeftResize;
        });
    
        $(this.topRightActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.TopRightResize;
        });
    
        $(this.bottomLeftActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.BottomLeftResize;
        });
    
        $(this.bottomRightActionTriggerQueryStr).mousedown(function (event) {
            wrapper.currentAction = wrapper.ActionsEnum.BottomRightResize;
        });
    
        $(document).mouseup(function (event) {
            // Clear the current action.
            wrapper.currentAction = wrapper.ActionsEnum.None;
        });
    
        $(document).mousemove(function (event) {
            wrapper.onMouseMove(event);
        });
    }
  • Handle the mousemove event:
    • Calculate the mouse position's delta:
      JavaScript
      MoveAndResizeElementWrapper.prototype.onMouseMove = function (event) {
          var currMouseX = event.clientX;
          var currMouseY = event.clientY;
      
          var deltaX = currMouseX - this.lastMouseX;
          var deltaY = currMouseY - this.lastMouseY;
      
          this.applyMouseMoveAction(deltaX, deltaY);
      
          this.lastMouseX = event.pageX;
          this.lastMouseY = event.pageY;
      }
    • Update the size and position appropriately, according to the current action:
      JavaScript
      MoveAndResizeElementWrapper.prototype.applyMouseMoveAction = function (deltaX, deltaY) {
          var deltaTop = 0;
          var deltaLeft = 0;
          var deltaWidth = 0;
          var deltaHeight = 0;
      
          if (this.currentAction == this.ActionsEnum.RightResize ||
                   this.currentAction == this.ActionsEnum.TopRightResize ||
                   this.currentAction == this.ActionsEnum.BottomRightResize) {
              deltaWidth = deltaX;
          }
      
          if (this.currentAction == this.ActionsEnum.LeftResize ||
                   this.currentAction == this.ActionsEnum.TopLeftResize ||
                   this.currentAction == this.ActionsEnum.BottomLeftResize) {
              deltaWidth = -deltaX;
              deltaLeft = deltaX;
          }
      
          if (this.currentAction == this.ActionsEnum.BottomResize ||
                   this.currentAction == this.ActionsEnum.BottomLeftResize ||
                   this.currentAction == this.ActionsEnum.BottomRightResize) {
              deltaHeight = deltaY;
          }
      
          if (this.currentAction == this.ActionsEnum.TopResize ||
                   this.currentAction == this.ActionsEnum.TopLeftResize ||
                   this.currentAction == this.ActionsEnum.TopRightResize) {
              deltaHeight = -deltaY;
              deltaTop = deltaY;
          }
      
          if (this.currentAction == this.ActionsEnum.Move) {
              deltaLeft = deltaX;
              deltaTop = deltaY;
          }
      
          this.updatePosition(deltaLeft, deltaTop);
          this.updateSize(deltaWidth, deltaHeight);    
          this.adjustWrapper();
      }
    • Update the wrapper's position:
      JavaScript
      MoveAndResizeElementWrapper.prototype.updatePosition = function (deltaLeft, deltaTop) {
          // Calculate the new position.
          var elemLeft = parseInt($(this.externalWrapperQueryStr).css('left'));
          var elemTop = parseInt($(this.externalWrapperQueryStr).css('top'));
          var newLeft = elemLeft + deltaLeft;
          var newTop = elemTop + deltaTop;
      
          // Set the new position.
          $(this.externalWrapperQueryStr).css('left', newLeft + 'px');
          $(this.externalWrapperQueryStr).css('top', newTop + 'px');
      }
    • Update the size of the original element:
      JavaScript
      MoveAndResizeElementWrapper.prototype.updateSize = function (deltaWidth, deltaHeight) {
          // Calculate the new size.
          var elemWidth = parseInt($(this.originalElement).width());
          var elemHeight = parseInt($(this.originalElement).height());
          var newWidth = elemWidth + deltaWidth;
          var newHeight = elemHeight + deltaHeight;
      
          // Don't allow a too small size.
          var minumalSize = this.cornerActionTriggerRadius * 2;
          if (newWidth < minumalSize) {
              newWidth = minumalSize;
          }
          if (newHeight < minumalSize) {
              newHeight = minumalSize;
          }
      
          // Set the new size.
          $(this.originalElement).css('width', newWidth + 'px');
          $(this.originalElement).css('height', newHeight + 'px');
      }
    • Adjust the wrapper's elements according to the size of the original element:
      JavaScript
      MoveAndResizeElementWrapper.prototype.cornerActionTriggerRadius = 8;
      
      MoveAndResizeElementWrapper.prototype.adjustWrapper = function () {
          var elemWidth = parseInt($(this.originalElement).width());
          var elemHeight = parseInt($(this.originalElement).height());
          var externalWrapperWidth = (elemWidth + this.cornerActionTriggerRadius * 2) + 'px';
          var externalWrapperHeight = (elemHeight + this.cornerActionTriggerRadius * 2) + 'px';
      
          $(this.internalWrapperQueryStr).width($(this.originalElement).width());
          $(this.internalWrapperQueryStr).height($(this.originalElement).height());
          $(this.externalWrapperQueryStr).width(externalWrapperWidth);
          $(this.externalWrapperQueryStr).height(externalWrapperHeight);
      
          // Adjust the resizing border.
          this.adjustResizingBorder();
      }
    • Adjust the resizing border according to the size of the original element:
      JavaScript
      MoveAndResizeElementWrapper.prototype.adjustResizingBorder = function () {
          var elemWidth = parseInt($(this.originalElement).width());
          var elemHeight = parseInt($(this.originalElement).height());
      
          // Get the minimum and maximum values for X and Y.
          var minX = this.cornerActionTriggerRadius + 'px';
          var minY = this.cornerActionTriggerRadius + 'px';
          var maxX = (this.cornerActionTriggerRadius + elemWidth) + 'px';
          var maxY = (this.cornerActionTriggerRadius + elemHeight) + 'px';
      
          // Adjust moving rectange.
          this.setRectangleAttributes(this.moveActionTriggerQueryStr, 
                    minX, minY, elemWidth + 'px', elemHeight + 'px');
      
          // Adjust resizing border lines.
          this.setLineAttributes(this.topDrawingQueryStr, minX, minY, maxX, minY);
          this.setLineAttributes(this.bottomDrawingQueryStr, minX, maxY, maxX, maxY);
          this.setLineAttributes(this.leftDrawingQueryStr, minX, minY, minX, maxY);
          this.setLineAttributes(this.rightDrawingQueryStr, maxX, minY, maxX, maxY);
          this.setLineAttributes(this.topActionTriggerQueryStr, minX, minY, maxX, minY);
          this.setLineAttributes(this.bottomActionTriggerQueryStr, minX, maxY, maxX, maxY);
          this.setLineAttributes(this.leftActionTriggerQueryStr, minX, minY, minX, maxY);
          this.setLineAttributes(this.rightActionTriggerQueryStr, maxX, minY, maxX, maxY);
      
          // Adjust resizing border circles.
          this.setCircleAttributes(this.topLeftDrawingQueryStr, minX, minY);
          this.setCircleAttributes(this.topRightDrawingQueryStr, maxX, minY);
          this.setCircleAttributes(this.bottomLeftDrawingQueryStr, minX, maxY);
          this.setCircleAttributes(this.bottomRightDrawingQueryStr, maxX, maxY);
          this.setCircleAttributes(this.topLeftActionTriggerQueryStr, minX, minY);
          this.setCircleAttributes(this.topRightActionTriggerQueryStr, maxX, minY);
          this.setCircleAttributes(this.bottomLeftActionTriggerQueryStr, minX, maxY);
          this.setCircleAttributes(this.bottomRightActionTriggerQueryStr, maxX, maxY);
      }
      
      MoveAndResizeElementWrapper.prototype.setRectangleAttributes = 
                function (rectQueryStr, x, y, width, height) {
          var rectElem = $(rectQueryStr);
          rectElem.attr('x', x);
          rectElem.attr('y', y);
          rectElem.attr('width', width);
          rectElem.attr('height', height);
      }
      
      MoveAndResizeElementWrapper.prototype.setLineAttributes = 
               function (lineQueryStr, x1, y1, x2, y2) {
          var lineElem = $(lineQueryStr);
          lineElem.attr('x1', x1);
          lineElem.attr('y1', y1);
          lineElem.attr('x2', x2);
          lineElem.attr('y2', y2);
      }
      
      MoveAndResizeElementWrapper.prototype.setCircleAttributes = function (circleQueryStr, cx, cy) {
          var circleElem = $(circleQueryStr);
          circleElem.attr('cx', cx);
          circleElem.attr('cy', cy);
      }

For enabling showing and hiding of the wrapper, we add two more functions:

JavaScript
MoveAndResizeElementWrapper.prototype.showWrapper = function () {
    this.addWrapperElements();
    this.initializeEventHandlers();
}

MoveAndResizeElementWrapper.prototype.hideWrapper = function () {
    // Set original element's position, to be in the same position, after the wrapper is removed.
    var wrapperLeft = parseInt($(this.externalWrapperQueryStr).css('left'));
    var wrapperTop = parseInt($(this.externalWrapperQueryStr).css('top'));
    var elemLeft = (wrapperLeft + this.cornerActionTriggerRadius) + 'px';
    var elemTop = (wrapperTop + this.cornerActionTriggerRadius) + 'px';
    $(this.originalElement).css('left', elemLeft);
    $(this.originalElement).css('top', elemTop);
    $(this.originalElement).css('position', $(this.externalWrapperQueryStr).css('position'));

    // Put the original element instead of the wrapped element.
    $(this.externalWrapperQueryStr).replaceWith(this.originalElement);
}

Create move and resize wrappers by jQuery selectors

For wrapping HTML elements with move and resize behavior, according to a given jQuery selector, we add another object:

JavaScript
function MoveAndResizeTool(jquerySelector) {
    this.wrappedElements = new Array();
    this.isShown = false;

    var selectedElements = $(jquerySelector);

    for (var elementInx = 0; elementInx < selectedElements.length; elementInx++) {
        var currElement = selectedElements[elementInx];
        this.wrappedElements[elementInx] = new MoveAndResizeElementWrapper(currElement);
    }
}

In this object constructor function, we find the appropriate DOM elements according to the given selector and, create a move and resize wrapper for each of them.

For enabling showing and hiding the wrappers of the elements, we add two more functions:

JavaScript
MoveAndResizeTool.prototype.show = function () {
    if (this.isShown == false) {
        for (var elementInx = 0; elementInx < this.wrappedElements.length; elementInx++) {
            var currElement = this.wrappedElements[elementInx];
            currElement.showWrapper();
        }

        this.isShown = true;
    }
}

MoveAndResizeTool.prototype.hide = function () {
    if (this.isShown == true) {
        for (var elementInx = 0; elementInx < this.wrappedElements.length; elementInx++) {
            var currElement = this.wrappedElements[elementInx];
            currElement.hideWrapper();
        }

        this.isShown = false;
    }
}

Finally, we add a function for creating and showing the MoveAndResizeTool:

JavaScript
function WrapWithMoveAndResizeTool(jquerySelector) {
    var resizeTool = new MoveAndResizeTool(jquerySelector);
    resizeTool.show();

    return resizeTool;
}

Using MoveAndResizeTool

For demonstrating the use of the created MoveAndResizeTool, we create an HTML document that applies the MoveAndResizeTool on some elements. In that document we:

  • Add the scripts for the jQuery library and our MoveAndResizeTool:
    XML
    <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="js/MoveAndResizeTool.js"></script>
  • Add some elements:
    • SVG elements:
      XML
      <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
          style="left:20px;top:200px;width:300px;height:200px;position:absolute" class="mySvgs">
          <ellipse cx="50%" cy="50%" rx="50%" ry="50%" 
            stroke="#FF0000" stroke-width="2" fill="#800000" />
      </svg>
      <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
          style="left:340px;top:200px;width:100px;height:200px;position:absolute;overflow:hidden" 
          class="mySvgs">
          <line x1="0" y1="0" x2="100%" y2="100%" stroke="#008000" 
            stroke-width="15" stroke-linecap="round" />
      </svg>
      <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
          style="left:460px;top:200px;width:100px;height:200px;position:absolute" class="mySvgs">
          <rect x="0" y="0" width="100%" height="100%" stroke="#8000FF" fill="#8080FF"
              rx="10" ry="10" stroke-width="5" />
      </svg>
    • Text:
      XML
      <div style="left:260px;top:20px;width:100px;height:110px;
            position:absolute;background:#afa;overflow:hidden;
            text-overflow:ellipsis" id="myParagraph">
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
          Text Text Text Text Text Text Text Text Text Text Text Text Text Text 
      </div>
    • Image:
      XML
      <img src="img/Hydrangeas.jpg" alt="An image" id="myImage"
          style="left:380px;top:20px;width:170px;height:150px;position:absolute" />
    • Button:
      XML
      <div style="left:260px;top:140px;
              width:100px;height:40px;position:absolute" id="myButton">
          <button style="left:0px;top:0px;width:100%;
              height:100%;position:absolute">My button</button>
      </div>
  • Wrap the added elements with MoveAndResizeTool objects:
    JavaScript
    var svgResizeTool;
    var paragraphResizeTool;
    var imageResizeTool;
    var buttonResizeTool;
    
    $(function () {
        svgResizeTool = WrapWithMoveAndResizeTool(".mySvgs");
        paragraphResizeTool = WrapWithMoveAndResizeTool("#myParagraph");
        imageResizeTool = WrapWithMoveAndResizeTool("#myImage");
        buttonResizeTool = WrapWithMoveAndResizeTool("#myButton"); 
    });
  • Add panel for enabling showing and hiding the MoveAndResizeTool wrappers:
    • Functions for showing and hiding the MoveAndResizeTool wrappers:
      JavaScript
      function showSvgElementsTool() {
          svgResizeTool.show();
      
          document.getElementById("btnShowSvg").disabled = true;
          document.getElementById("btnHideSvg").disabled = false;
      }
      
      function hideSvgElementsTool() {
          svgResizeTool.hide();
      
          document.getElementById("btnShowSvg").disabled = false;
          document.getElementById("btnHideSvg").disabled = true;
      }
      
      function showParagraphTool() {
          paragraphResizeTool.show();
      
          document.getElementById("btnShowParagraph").disabled = true;
          document.getElementById("btnHideParagraph").disabled = false;
      }
      
      function hideParagraphTool() {
          paragraphResizeTool.hide();
      
          document.getElementById("btnShowParagraph").disabled = false;
          document.getElementById("btnHideParagraph").disabled = true;
      }
      
      function showImageTool() {
          imageResizeTool.show();
      
          document.getElementById("btnShowImage").disabled = true;
          document.getElementById("btnHideImage").disabled = false;
      }
      
      function hideImageTool() {
          imageResizeTool.hide();
      
          document.getElementById("btnShowImage").disabled = false;
          document.getElementById("btnHideImage").disabled = true;
      }
      
      function showButtonTool() {
          buttonResizeTool.show();
      
          document.getElementById("btnShowButton").disabled = true;
          document.getElementById("btnHideButton").disabled = false;
      }
      
      function hideButtonTool() {
          buttonResizeTool.hide();
      
          document.getElementById("btnShowButton").disabled = false;
          document.getElementById("btnHideButton").disabled = true;
      }
    • Buttons for activating those functions:
      XML
      <div style="left:10px;top:10px;width:auto;height:auto;position:absolute" 
                   class="operationsPanel">
          <div style="text-align:center;color:#080">Toggle MoveAndResizeTool</div>
          <table>
          <tr>
          <td>SVG elements: </td>
          <td>
          <button id="btnShowSvg" onclick="showSvgElementsTool();">Show</button>
          <button id="btnHideSvg" onclick="hideSvgElementsTool();">Hide</button>
          </td>
          </tr>
          <tr>
          <td>Paragraph: </td>
          <td>
          <button id="btnShowParagraph" onclick="showParagraphTool();">Show</button>
          <button id="btnHideParagraph" onclick="hideParagraphTool();">Hide</button>
          </td>
          </tr>
          <tr>
          <td>Image: </td>
          <td>
          <button id="btnShowImage" onclick="showImageTool();">Show</button>
          <button id="btnHideImage" onclick="hideImageTool();">Hide</button>
          </td>
          </tr>
          <tr>
          <td>Button: </td>
          <td>
          <button id="btnShowButton" onclick="showButtonTool();">Show</button>
          <button id="btnHideButton" onclick="hideButtonTool();">Hide</button>
          </td>
          </tr>
          </table>
      </div>

The result is:

Part 2 result

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)