Making things move.
In writing these posts I have no idea how fast to go. Perhaps the previous post was too much—it was more than is typically done the second day of a programming class, anyway. But on the hope it was not too much, here I go doing more.
Today I’m going to show how to make a piece of text
flee from the pointer.
This will require us to delve into the mysteries of the DOM.
For example, the p
element we used last time
has no left or right edges
(filling, by definition, the width of the document),
so we’ll use the span
element instead.
Also, we’ll bump into the annoying way that different browsers
handling the same functionality in different ways.
If we make a span
element like
s = document.createElement("span")
,
put some text in it with s.innerHTML = "something"
,
and add it to the document with document.body.appendChild(s)
we will see text appears near the top left of the document.
Well, actually of the document’s body
since that’s the part of the document that has content;
the title, URL, etc, are also part of a document.
It goes there because the default way to position a span
element
is to go left-to-right, top-to-bottom, after the most recent element added.
We can change this default by messing with the style
of the element.
Conceptually, the style stores information about how an element looks
while the element itself contains information about what it contains.
One of the things inside each element’s style is it’s position
.
s.style.position
is a string describing how,
not where, the object is placed.
We’ll replace it with s.style.position = "relative"
meaning it should be positioned relative to
the top right corner of the document body
instead of immediately after the previously added element.
We’ll then change it’s location with s.style.left = 100
and s.style.top = 100
,
stating how many pixels the span should be from the left and top edges of the document, respectively.
We now have
s = document.createElement("span")
which places the word “calm”
100 pixels from the top left corner of the document.
s.innerHTML = "calm"
document.body.appendChild(s)
s.style.position = "relative"
s.style.left = 100
s.style.top = 100
We now bump into one of the strangenesses of the DOM.
If we look at the top
we just set to 100
by doing alert(s.style.top)
we see not the number “100”
but the string “100px”
At least, we do in some browsers.
Not all web browsers do this the same way.
.
We can’t do math on a string like "100px"
and we’ll want to do math on position to move the text
so we’ll need to store the numbers too.
Fortunately, since s
is an object,
all we need to do is add a couple extra keys to it:
s.posX = 100
We could have picked anything instead of
s.posY = 100posX
and posY
;
anytime we create a name it is completely our choice what it is.
Since we defined these keys ourselves, we know that the browser won’t do any tricky changing them into strings or the like;
it’ll just store the numbers directly.
We can verify this by an alert(s.posX)
.
The next thing to do is figure out where the cursor is located.
We do this by adding an “event handler” to the document.
These are functions that are invoked in special situations.
One of them, onmousemove
is invoked anytime the cursor moves.
The value of onmousemove
needs to be a function,
so we write the following:
document.body.onmousemove = function(e) {
The first line of this method is a bit strange.
The
if (!e) { e = window.event }
mouseX = e.pageX
mouseY = e.pageY
}
!e
is true if e
is not defined.
Some browsers give information about the event
as a parameter to the onmousemove
function
and others store it inside a special window
object.
The if
statement says “if we didn’t get it as a parameter then go grab it from the window
object.”
The other two lines just look up the position of the mouse
from the event object.
Now we need to decide what to do when the mouse moves inside the span. The simplest thing to do is move the span away from the mouse.
First figure out how far the mouse is
from each edge of the span,
and then change the span’s position so the smallest of those is zero.
The distance from the top is simply the difference between the mouse position and the span’s position.
The difference from the bottom is the difference between the mouse position and the sum of the span position and the span height.
We also do the subtraction such that larger mouseY
make fromTop
bigger and fromBottom
smaller.
fromTop = mouseY - s.posY
A similar computation works for the left and right
fromBottom = (s.posY + s.offsetHeight) - mouseY
fromLeft = mouseX - s.posX
fromRight = (s.posX + s.offsetWidth) - mouseX
Now we’ll figure out which edge is closest to the mouse.
There is a built-in function called Math.min
that will help:
smallest = Math.min(fromTop, fromBottom, fromLeft, fromRight)
The second line has a new construction.
The
if (smallest <= 0) { return }
if (fromTop == smallest) { s.posY = mouseY }
if (fromLeft == smallest) { s.posX = mouseX }
if (fromBottom == smallest) { s.posY = mouseY - s.offsetHeight }
if (fromRight == smallest) { s.posX = mouseX - s.offsetWidth }
return
here says “stop this function and ignore what follows”
so we’ll only keep going if the smallest distance is positive.
We then figure out which distance is smallest
and move the numbers we are storing inside span accordingly.
Notice the use of ==
to compare values;
since =
changes a value we need to use something different to check for equality;
==
is operator used by Javascript.
Finally, we update the actual position of the span
using s.style.left
and s.style.top
.
The final function looks like
document.body.onmousemove = function(e) {
That’s it!
Assuming you have a standards-compliant browser
(i.e., not Internet Explorer
Internet Explorer, for whatever reason,
uses a different DOM than other browsers
and each version of Internet Explorer
uses a different DOM than each other version.
Most other browsers
(SRWare Iron, Midori, Konqueror, Opera, Firefox, K-Meleon, Slepnir, SeaMonkey, Camino, Chromium, Safari, etc) don’t have this problem.
),
the span will now flee from the mouse.
if (!e) { e = window.event }
mouseX = e.pageX
mouseY = e.pageY
fromTop = mouseY - s.posY
fromBottom = (s.posY + s.offsetHeight) - mouseY
fromLeft = mouseX - s.posX
fromRight = (s.posX + s.offsetWidth) - mouseX
smallest = Math.min(fromTop, fromBottom, fromLeft, fromRight)
if (smallest <= 0) { return }
if (fromTop == smallest) { s.posY = mouseY }
if (fromLeft == smallest) { s.posX = mouseX }
if (fromBottom == smallest) { s.posY = mouseY - s.offsetHeight }
if (fromRight == smallest) { s.posX = mouseX - s.offsetWidth }
s.style.left = s.posX
s.style.top = s.posY
}
Looking for comments…