Dealing with images in content management systems (page 4)
Navigation:
Page 1 | Page 2 | Page 3 | Page 4 | Page 5 | Samples
(Download the source code for this article)
Client Side Canvas Script
The key point to remember is that the user's selection is
just a transparent div element with a border set to a dashed line, and
absolutely positioned to appear to float over the canvas image.
The job of the canvas script is to:
- Set
all the elements up in the correct positions on the page
- Initialise
the selection div in a suitable default position and size, at the correct
aspectRatio if both ImageWidth and ImageHeight were specified on the
control
- Respond
to the user's mouse moving and clicking, such that:
- When
the mouse pointer is near the corners or edges of the selection, the
cursor changes to a resize icon indicating a resize can be performed in
the appropriate direction
- When
the mouse pointer is inside the selection and away from the edge, the
cursor changes to a move icon
- When
the mouse pointer is near the corners or edges of the selection, clicking
and dragging will have the effect of resizing the selection in an
intuitive natural way
- When
the mouse pointer is inside the selection, clicking and dragging should
move the selection.
- If
the user does resize the selection and as aspectRatio is being enforced,
the script should ensure that the selection is maintained at the correct
aspect ratio even though the user's mouse movements would otherwise
change the ratio. This constraining of the selection is familiar from
image editing packages – the script should ensure that the selection
behaves in an intuitive way as the user drags the edges to resize.
- Store
the position and size of the selection relative to the canvas prior to the
page being resubmitted
When the page loads the initialise(..) method is called,
with parameter values that the control wrote out in the prerender phase
described earlier.
First of all, references to all the html elements we need to
manipulate are stored in variables for ease of use:
...
oCanvas = document.getElementById(canvasID);
oSelection = document.getElementById(selectionBoxID);
...
Then, we resize and position the popupDiv element so that it
is a little wider than the canvas image and inset from the top and left of the
viewport. We also store the position and size of the canvas itself for later
use:
canvasRect = rectangle(oCanvas);
The rectangle function needs some explaining. It is used to
represent the position and size of an element without having to continually access
its style properties (e.g., oElement.style.left). It also allows us to capture
a snapshot of an element that might be changing. This is particularly useful
when dealing with the user's selection. Rather than continuously adjusting the
size and position of the selection div, we use a more abstract object to
represent the dimensions and location of the selection (or any other element):
// returns an object that represents the size and position of the element oBlock
function rectangle(oBlock)
{
return {
x: parseInt(oBlock.style.left),
y: parseInt(oBlock.style.top),
w: parseInt(oBlock.style.width),
h: parseInt(oBlock.style.height)
};
}
This method returns an object that has w, h, x, and y
properties. It is analogous to the System.Drawing.Rectangle we were using
earlier. At the beginning of a sequence of operations on the selection, we can
obtain a new rectangle object to represent a snapshot of the current selection.
We can then scale, move and constrain this representation by altering its x, y,
w and h properties and when we're done, call setSelection with our altered
rectangle to apply its new position and location to the selection div:
// applies the size and position information in the
// rectangle rect to the selection div
function setSelection(rect)
{
oSelection.style.left = rect.x + "px";
oSelection.style.top = rect.y + "px";
oSelection.style.width = rect.w + "px";
oSelection.style.height = rect.h + "px";
}
This means we are not continuously changing the style
properties of the selection element itself, which might confuse the issue when
resizing.
Back in the initialise(..) method, we continue to set up the
page by position and sizing the selection appropriately.
if(iReqdWidth > 0 && iReqdHeight > 0)
{
bConstrain = true;
aspectRatio = iReqdWidth / iReqdHeight;
}
bConstrain is a global variable that is checked when the
user resizes the selection to see if the selection should be "tweaked" to
ensure that the correct aspectRatio is maintained. This demonstrates the use of
rectangle:
var selection = rectangle(oSelection);
constrain(selection);
setSelection(selection);
We store a snapshot of the selection in the variable
selection, then call constrain(..), then apply the dimensions of the rectangle
back to the "real" selection.
The constrain(..) function will be described shortly.
To finish off the initialisation we hook up some event
handlers for mouse events:
// now hook up some event handling:
document.onmousemove = move;
document.onmouseup = up;
document.onmousedown = down;
oCanvas.ondrag = function(){return false;}
oSelection.ondrag = function(){return false;}
document.ondrag = function(){return false;}
We need the mouse event handlers to be on the whole document
rather than just the selection or the canvas because in a dragging or resizing
operation the user might move the mouse outside of the canvas or even the
popupDiv as part of normal use. This is especially true when an aspect ratio is
being enforced and the image is being constrained as it's being resized, as the
point on the selection the user first grabbed might not stay in sync with the
user's mouse as the selection dimensions are constrained by the script. The
final three event handlers are essential to cancel any drag events. The browser
might otherwise handle these by appearing to select those parts of the page the
user moves the mouse over while moving or resizing the selection. This would
give a very messy user experience.
Moving the mouse
As the user moves the mouse, the move(e) function will be
called. Two global variables, bMoving and bResizing, keep track of whether the
script has decided the user is moving or resizing the selection. They will be
set in response to a mousedown event. Initially both these will be false,
indicating the user is just moving the mouse around. The bulk of the move
function is a three condition if statement, the last of which is the "just
moving the mouse around" condition. We will come back to the first two, which
handle moving and resizing, in a moment.
...
else
{
resizeXMode = "";
resizeYMode = "";
var targetSize = 15;
if(p.x >= selection.x && p.x <= (selection.x + selection.w)
&& p.y >= selection.y && p.y <= (selection.y + selection.h))
{
// cursor is inside the selection
// default to move behaviour
oSelection.style.cursor = "move";
oCanvas.style.cursor = "move";
bCanMove = true;
// further checks for hotspots omitted...
...
The two global variables resizeXMode and resizeYMode keep
track of what sort of resizing the user would be allowed to perform given the
current mouse position. They vary independently. The resizeXMode variable can
be either "E"(ast) or "W"(est) and resizeYMode can be either "N"(orth) or
"S"(outh). As the user moves the mouse around the script in this else block
will set these two variables.
They are both blanked at the start of the block, and then we
check to see whether or not the mouse pointer is inside the selection. If so,
we set bCanMove to true, indicating that the user would be allowed to initiate
a move operation from this point. We also set the cursor to the move symbol. We
then further check to see if the user is in one of eight "hotspot" areas from
which a resize operation could be started and if so, set the resizeXMode and
resizeYMode variables accordingly. We also set the cursor symbol to point in
the appropriate direction. The sizes of these hotspots are governed by the
targetSize variable, which here is set at 15 pixels.

The rest of this condition is a lot of tedious code that
finds out which of these hotspots the cursor is over, and sets the resizeXMode
and resizeYMode variables, and the cursor icon, appropriately.
mouseup and mousedown
The down(e) and up() functions handle the mouse being pressed
and released. Throughout this script we make heavy use of the positions at
which mousemove and mousedown events occur. To make this easier to code against
we use a function which returns an object that represents the position at which
an event occurred:
// returns an object that represents the position relative to
// popupOrigin that event e occurred.
function point(e)
{
if (e.pageX || e.pageY)
{
this.x = e.pageX;
this.y = e.pageY;
}
else if (e.clientX || e.clientY)
{
// need to use both document.body and document.documentElement to
// cater for various combinations of IE 5/6 in normal and quirksmode
this.x = e.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft;
this.y = e.clientY + document.body.scrollTop
+ document.documentElement.scrollTop;
}
// give the point position relative to the popup
this.x -= popupOrigin.x;
this.y -= popupOrigin.y;
}
This can be seen in the down(e) event handler that follows.
Note that the first line allows the event to be handled in a cross-browser
manner as discussed on quirksmode here: http://www.quirksmode.org/js/events_access.html
function down(e)
{
if (!e) var e = window.event;
downPoint = new point(e);
originalRect = rectangle(oSelection);
if(bCanMove && resizeXMode == "" && resizeYMode == "")
{
bMoving = true;
}
if(resizeXMode != "" || resizeYMode != "")
{
bResizing = true;
}
return false;
}
function up()
{
bMoving = false;
bResizing = false;
}
When the user presses and holds the mouse button, we first
of all store the position of the mouse at which the down event occurred:
downPoint = new point(e);
This will be used later to determine how far the mouse has
moved. We also store the size and position of the selection as a rectangle in
the originalRect variable – again, this will be used to determine how much to
resize or move the selection later.
If the mouse was over the selection but not in a hotspot
(resizeXMode == "" && resizeYMode == ""), then we set bMoving to true.
The next time the move function is called in response to further mouse movement,
we'll enter the first condition in the "if" statement. However, if the mouse
pointer is over a hotspot then we set the global variable bResizing to true.
The next time the move function is called in response to user mouse movements
the second condition in its "if" statement will be called. This state of
affairs continues until the user stops holding the mouse button down. The up
event handler couldn't be simpler – it just sets the two globals bMoving and
bResizing to false, so that further mouse movements default to the "just moving
the mouse around" state discussed already.
Moving is actually the simplest state to handle:
function move(e)
{
if(bInMove) return; // we're already processing a mousemove event
bInMove = true;
oCanvas.style.cursor = "auto";
oSelection.style.cursor = "auto";
bCanMove = false;
if (!e) var e = window.event;
var p = new point(e);
var selection = rectangle(oSelection);
if(bMoving)
{
var dx = p.x - downPoint.x;
var dy = p.y - downPoint.y;
selection.x = 0 + originalRect.x + dx;
selection.y = 0 + originalRect.y + dy;
setSelection(selection);
checkConfine(selection);
}
...
As already mentioned, we capture a snapshot of the current
selection size and position. Then we find out how far the mouse has moved since
the user started moving (in down(e)) and store the difference in dx and dy.
Then all we do is set the new selection position to the old one plus the
differences, and call setSelection to apply the new selection rectangle to the
selection div. We then call checkConfine(..), which disables the OK button and
displays a warning message if the selection is anywhere outside the canvas:
function checkConfine(rect)
{
var msg = "";
if((rect.x) < canvasRect.x)
msg += "Selection extends to the left of the canvas. ";
if(rect.y < canvasRect.y)
msg += "Selection extends above canvas. ";
if((rect.x + rect.w) > (canvasRect.x + canvasRect.w))
msg += "Selection extends to the right of the canvas. ";
if((rect.y + rect.h) > (canvasRect.y + canvasRect.h))
msg += "Selection extends below the canvas. ";
if(msg)
{
oConfirmButton.disabled = true;
oDebugInfo.innerHTML = "WARNING: " + msg;
}
else
{
oConfirmButton.disabled = false;
oDebugInfo.innerHTML = "";
}
}
Navigation:
Page 1 | Page 2 | Page 3 | Page 4 | Page 5 | Samples