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:
<!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:
<!DOCTYPE html>
After creating the HTML5 document, we can add a simple SVG shape into it:
<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:
<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:
<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:
<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:
<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:
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):
<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:
var currentAction = ActionsEnum.None;
handle the mousedown
event of each action-trigger to store the appropriate action:
var externalWrapperQueryStr = '#wrapper';
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:
function initializeEventHandlers() {
$(document).mouseup(function (event) {
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:
function initializeEventHandlers() {
$(document).mousemove(function (event) {
onMouseMove(event);
});
}
- Calculate the mouse position's delta:
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:
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:
function updatePosition(deltaLeft, deltaTop) {
var elemLeft = parseInt($(externalWrapperQueryStr).css('left'));
var elemTop = parseInt($(externalWrapperQueryStr).css('top'));
var newLeft = elemLeft + deltaLeft;
var newTop = elemTop + deltaTop;
$(externalWrapperQueryStr).css('left', newLeft + 'px');
$(externalWrapperQueryStr).css('top', newTop + 'px');
}
- Update the size of the SVG shape:
function updateSize(deltaWidth, deltaHeight) {
var elemWidth = parseInt($("#myShape").width());
var elemHeight = parseInt($("#myShape").height());
var newWidth = elemWidth + deltaWidth;
var newHeight = elemHeight + deltaHeight;
if (newWidth < 0) {
newWidth = 0;
}
if (newHeight < 0) {
newHeight = 0;
}
$("#myShape").css('width', newWidth + 'px');
$("#myShape").css('height', newHeight + 'px');
}
- Adjust the wrapper's elements according to the size of the SVG shape:
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:
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):
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:
<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:
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:
<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:
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:
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());
var minX = cornerActionTriggerRadius + 'px';
var minY = cornerActionTriggerRadius + 'px';
var maxX = (cornerActionTriggerRadius + elemWidth) + 'px';
var maxY = (cornerActionTriggerRadius + elemHeight) + 'px';
setRectangleAttributes(moveActionTriggerQueryStr, minX,
minY, elemWidth + 'px', elemHeight + 'px');
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);
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:
<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:
<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:
<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 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:
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):
var MoveAndResizeTool_ElementWrapper_wrappersCounter = 0;
function MoveAndResizeElementWrapper(elementToWrap) {
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:
function MoveAndResizeElementWrapper(elementToWrap) {
this.externalWrapperQueryStr = '#' + this.wrapperId;
this.internalWrapperQueryStr = this.externalWrapperQueryStr + ' .internalWrapper';
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';
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:
MoveAndResizeElementWrapper.prototype.addWrapperElements = function () {
$(this.originalElement).wrap(this.wrapperStr);
$(this.internalWrapperQueryStr).after(this.resizingBorderStr);
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'));
$(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):
For enabling showing and hiding of the wrapper, we add two more functions:
MoveAndResizeElementWrapper.prototype.showWrapper = function () {
this.addWrapperElements();
this.initializeEventHandlers();
}
MoveAndResizeElementWrapper.prototype.hideWrapper = function () {
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'));
$(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:
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:
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
:
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
:
<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:
<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:
<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:
<img src="img/Hydrangeas.jpg" alt="An image" id="myImage"
style="left:380px;top:20px;width:170px;height:150px;position:absolute" />
- Button:
<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:
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:
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:
<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: