Template:Uppsala-SwedenTemplatetest
From 2011.igem.org
JavaScript - Floating layers |
|
Interesting effects and functionality can be achieved on Web pages by the use of floating content, or layers. By floating I mean content that moves about the page, and that does not appear in a fixed location with respect to the page layout. The content may move to follow window scrolling such that it is always visible in the window for example. Imagine how handy this type of functionality could be when displaying a large number of rows in a table when a series of buttons need to be provided for actioning the selection of some rows. Without a scroll sensitive floating button-bar, the table would either require button duplication in amongst the rows, duplication of the buttons at the top and bottom, or a bunch of frames to allow the user to easily select some rows and action them. With the floating controls, the user could scroll away, selecting rows as required, then simply action them using constantly in-view buttons. Calling this floating content a layer, although semantically and conceptually convenient, is not quite right. Layers as such do not exist in Web pages. Netscape used to have a <layer> tag that provided much of the same functionality, but today this does not exist. Instead, floating content is achieved with the use of position controlled <div>s. However, we shall still refer to the floating content as a layer for the sake of convenience. With the advent of Cascading Style Sheets (CSS) and the integration of the Document Object Model (DOM) into JavaScript, the positioning and control of <div>s has become an everyday task. Let's look at an example.
Here is a <div> that just contains some text. It can be floated
as you will see.
Now press the button to see what happens next. The explanationYou should have seen the lilac rectangle detach itself from the page and float towards the top-left of the containing window. Scroll around, make the window larger and smaller, and see what happens then too. NOTE: Those of you viewing this page with FireFox will have to use the scrollbar to scroll with, not your mouse-wheel, as there is (at the time of writing) a bug precluding scroll events from firing when the mouse wheel is moved. The floating rectangle should move to stay 15 pixels from the top left of the containing window. It is set to 50% of the width of the page, so growing or shrinking the width should show an effect too. So how is it done ? Well, first a <div> needs to be defined on the page that can be identified later on. This is achieved by setting the id attribute to a page-unique value, floatlayer in the example, as shown below. Along with the id attribute, rudimentary CSS properties are set too, via the inline style attribute. The div will become the floating layer.
<div id="floatlayer" style="width:50%;background:#d0d0ff;border:solid black 1px;padding:5px"> Here is a <tt><div></tt> that just contains some text. It can be floated as you will see. </div> The CSS styles set the background colour, border and padding properties. By default the div is positioned relative to the rest of the page content, that is, it goes with the flow. So, how does it become detached and what makes it float around ? A floating layer objectA line of JavaScript is executed when the page loads that defines an object that will be used to hold the non-DOM state of the floating layer, along with some associated object methods to manipulate that state.
new FloatLayer('floatlayer',15,15,10); The parameters passed reflect the value of the id attribute of the associated div, and set the desired x and y float offsets, and the relative speed of floating. The code for the object constructor is shown below.
1. var FloatLayers = new Array(); 2. var FloatLayersByName = new Array(); 3. 4. function FloatLayer(name, offX, offY, speed) { 5. this.index = FloatLayers.length; 6. 7. FloatLayers.push(this); 8. FloatLayersByName[name] = this; 9. 10. this.name = name; 11. this.floatX = 0; 12. this.floatY = 0; 13. this.tm = null; 14. this.steps = speed; 15. this.alignHorizontal = (offX >= 0) ? leftFloater : rightFloater; 16. this.alignVertical = (offY >= 0) ? topFloater : bottomFloater; 17. this.ifloatX = Math.abs(offX); 18. this.ifloatY = Math.abs(offY); 19. } Lines 1 and 2 define some arrays that will be used to store the newly created FloatLayer objects by index (traditional numeric subscript) and by name (like a hash table). These arrays enable simple lookup of the objects later on. The objects do not exist within the DOM and so no DOM methods can be used to find them. With IE and FireFox it is possible to define new properties on the DOM element object itself which could be used to store the new object, but I would not recommend this approach as it may not be possible with all browsers. It is much safer to separate the element from the new object. When in a JavaScript object method, be it constructor or otherwise, accessing the object's instance properties or variables is done through the this keyword. It represents the object of interest. To create a new object variable simply assign to this.variable, where variable is the name of the variable you wish to create. You can see this being used in line 5 of the constructor to set the array subscript value for the new object, called index. The object is then pushed onto the FloatLayers array and added to the FloatLayersByName hash table, lines 7 and 8. The other state variables are then set to their initial values on lines 10 through 14. The name is set first to the value passed in, the desired position values for left, floatX, and top, floatY, are initialised, the timer function reference variable, tm, is null'd and the speed calculation factor, steps, is set to the passed value. Next the instance movement functions are decided upon according to the values passed in for the float offsets, lines 15 and 16. Dynamic method assignment is a feature of JavaScript objects, allowing instance methods to be set during, or post construction, varying from instance to instance as required. Thinking of them as function pointer variables is a good analogy. The functions assigned to the instance methods must exist and be in scope. This type of object method is different from the prototype definition which applies to all objects of that class, unless overridden specifically. Class methods will be used later on. The functions in this example are leftFloater or rightFloater for the horizontal movement, and topFloater or bottomFloater for vertical movement. They will be described in detail later on. The functions are used as follows.
The last thing to do during construction is to get the absolute values of the float offsets and store them for use later, lines 17 and 18. DetachmentRight, so now there is an object representing the state of the floating layer and a div which will become the floating layer. So, what next ? When the button is pressed, the onclick event handler JavaScript function, detach, is called. The function is as shown below.
1. function detach(){ 2. lay = document.getElementById('floatlayer'); 3. left = getXCoord(lay); 4. top = getYCoord(lay); 5. lay.style.position = 'absolute'; 6. lay.style.top = top; 7. lay.style.left = left; 8. getFloatLayer('floatlayer').initialize(); 9. alignFloatLayers(); 10. } Line 2 of the function fetches the DOM element that represents the div. It does so by passing the value of the id attribute, in this case floatlayer, to the document.getElementById DOM function. Once the element has been saved to a variable, it is possible to manipulate the CSS properties to detach the div from the relative page flow and make it free floating. First though, it is necessary to retrieve the absolute position of the div so that, when made into a floating layer, it can be initially repositioned to look as though nothing has happened. This is achieved by calling 2 further functions that get the left and top position values from the element. This is done on lines 3 and 4, the code for the functions is shown below.
function getXCoord(el) { x = 0; while(el){ x += el.offsetLeft; el = el.offsetParent; } return x; } function getYCoord(el) { y = 0; while(el){ y += el.offsetTop; el = el.offsetParent; } return y; } The element hierarchy needs to be traversed upwards from the div so that the position of any enclosing element can be taken into account. Next, the div is made free floating by setting the element's CSS position property to absolute, line 5 of the detach function. This causes the browser to re-flow the page ignoring the div, which it displays at its last known position. However, because the page has been re-flowed that position is not where it used to be relative to the rest of the content. This is why it is necessary to re-set the position to that prior to floating, by setting the element's CSS top and left properties to the values found earlier, lines 6 and 7 of the function. Moving the floaterLines 8 and 9 of the detach function is where things start to get a bit more interesting. The function getFloatLayer is called with the id value of the now floating div, floatlayer. The function returns the associated FloatLayer object created when the page loaded. The object's initialize method is then called. The function getFloatLayer is a convenience method for looking up an instance of a FloatLayer object in the FloatLayersByName hash table. The code is shown below.
function getFloatLayer(name){ return FloatLayersByName[name]; } When we created the FloatLayer object for the floating layer the code in the constructor did not set an object method for initialize, so how does this work ? JavaScript objects can have prototypical methods defined against their class, using the class's prototype property. These are much like the dynamic object methods, but apply to all objects of that class and are assigned outside of the constructor or object methods, as shown below for FloatLayer.
FloatLayer.prototype.initialize=defineFloater; There are more class methods that get assigned to the FloatLayer class to handle movement, but more of that later. First the initialize method. It is implemented by the defineFloater function as shown below.
1. function defineFloater(){ 2. this.layer = document.getElementById(this.name); 3. this.width = this.layer.offsetWidth; 4. this.height = this.layer.offsetHeight; 5. this.prevX = this.layer.offsetLeft; 6. this.prevY = this.layer.offsetTop; 7. } The function is intended to be called prior to the first movement of the layer, so that it can set up the initial values for use in calculating the layer's trajectory. Line 2 of the function stores the DOM element object for the associated div into the FloatLayer object. Storing the DOM object for the div must be deferred until this point, rather than happening in the object constructor, because, at the point the constructor is called, the element will not have been rendered, so will not exist. However, because the initialize method is called by the detach function, which is in turn called in response to the pressing of a button on the rendered page, the div is guaranteed to exist, and so can be stored. On lines 3 through 6, the current size and position of the floating layer are obtained from the DOM object, and stored into the FloatLayer object, for use later on. The prevX and prevY object variables are set to the position. These values are used as part of the trajectory calculation later on, and so are set up to reflect the current display location of the layer. Note here that no object hierarchy is traversed. This is because a floating layer is positioned with respect to the page, not the enclosing elements, so there is no hierarchy. Okay, so now we have an initialised object the represents the floating div. Line 9 of the detach function sets things in motion, by calling the alignFloatLayers function. This function is shown below.
1. FloatLayer.prototype.align=alignFloater; 2. 3. function alignFloatLayers() { 4. for( var i=0; i<FloatLayers.length; i++) 5. FloatLayers[i].align(); 6. } The function is another convenience method. It iterates over all the objects defined in the FloatLayers array, line 4, and calls their align method, line 5. The align method is another class method and is assigned to the alignFloater function, line 1. This function kick starts the movement of the floating layer, if need be, and is shown below.
1. function alignFloater(){ 2. if(this.layer == null) this.initialize(); 3. this.alignHorizontal(); 4. this.alignVertical(); 5. if( this.prevX != this.floatX || this.prevY != this.floatY ){ 6. if(this.tm == null) 7. this.tm=setTimeout('FloatLayers['+this.index+'].adjust()',50); 8. } 9. } A check is made to make sure that the layer is initialised prior to going forwards on line 2. This is in case a FloatLayer has not yet been touched by other code in the page, which can happen in the case where multiple floating layers are in use. Next, on lines 3 and 4, the alignment object methods are called. What these do is dependent on the values the methods were set to in the object constructor. In any case, the methods set the floatX and floatY object variables to the desired final location of the layer, according to the current window scroll position and size. The different methods are shown below.
1. function leftFloater() { 2. this.floatX = document.body.scrollLeft + this.ifloatX; 3. } 4. function topFloater() { 5. this.floatY = document.body.scrollTop + this.ifloatY; 6. } 7. function rightFloater() { 8. this.floatX = document.body.scrollLeft + 9. document.body.clientWidth - this.ifloatX - this.width; 10. } 11. function bottomFloater() { 12. this.floatY = document.body.scrollTop + 13. document.body.clientHeight - this.ifloatY - this.height; 14. } On line 5 of the alignFloatLayer function, a check is made to see if the floating layer needs to be moved, or whether it is in its final location already. If it needs to be moved, and a movement has not already been scheduled, line 6, a timer is set to perform a step along the trajectory to the final location, line 7. JavaScript timers are described in detail on the Delayed Actions page. The action performed by the timer is to call the adjust method of the FloatLayer object being aligned. This method is another of the class methods and is shown below. The method is assigned to the adjustFloater function. It calculates the next step along the movement trajectory from current location to desired final location for the FloatLayer object of interest, and is the main function in moving a floating layer.
1. FloatLayer.prototype.adjust=adjustFloater; 2. 3. function adjustFloater() { 4. this.tm=null; 5. if(this.layer.style.position!='absolute')return; 6. 7. var dx = Math.abs(this.floatX-this.prevX); 8. var dy = Math.abs(this.floatY-this.prevY); 9. 10. if (dx < this.steps / 2) 11. cx = (dx >= 1) ? 1 : 0; 12. else 13. cx = Math.round(dx/this.steps); 14. 15. if (dy < this.steps/2) 16. cy = (dy >= 1) ? 1 : 0; 17. else 18. cy = Math.round(dy/this.steps); 19. 20. if (this.floatX > this.prevX) 21. this.prevX += cx; 22. else if (this.floatX < this.prevX) 23. this.prevX -= cx; 24. 25. if (this.floatY > this.prevY) 26. this.prevY += cy; 27. else if (this.floatY < this.prevY) 28. this.prevY -= cy; 29. 30. this.layer.style.left = this.prevX; 31. this.layer.style.top = this.prevY; 32. 33. if (cx != 0 || cy != 0){ 34. if(this.tm == null) 35. this.tm=setTimeout('FloatLayers['+this.index+'].adjust()',50); 36. }else 37. alignFloatLayers(); 38. } The first thing is to acknowledge the expiration of the timer that called the function, by setting the tm object variable to null, line 4. This also allows subsequent timers to be set. Next, as a sanity check, the CSS position mode of the div is checked to make sure it is absolute, line 5. This is required because the function is called as the result of a timer expiration, so the opportunity is there for other code to alter the property prior to the timer firing. On lines 7 and 8 the difference, or delta, between the current location, held in prevX and prevY, and the desired location, held in floatX and floatY is calculated. If either value is 0, no further location change will take place in that axis. Lines 10 through 18 calculate the axis changes necessary to move the layer to its next step on the trajectory to the final location. If an axis delta is larger than half the speed regulator value, steps, the axis change is one steps value of the delta. Otherwise, if there is a delta, the change is a single pixel. The calculation allows the layer to move fast if the delta is large, and slowly if the delta is small, giving the effect of the layer sliding into place gently. Experimentation with the steps value shows how the motion is affected. Next, on lines 20 through 28, any axis change is applied to the current location variables, according to whether the layer needs to move up, down, left or right to get to its final location. Then, once the new current location has been calculated, it is applied to the CSS left and top properties for the div object, moving it with respect to the page, lines 30 and 31. Lastly, a check is performed to see if any further movement is required, lines 33 through 37. The check is two-phase. Phase one is simple: was there a change this time ? If so, schedule a further call to this FloatLayer object's adjust method. If not, do phase two: call the alignFloatLayer function. This function, if you remember, recalculates the final location for each floating layer with respect to the window scroll position and size. Essentially, this is a safety net that catches window resize and scroll effects that took place during trajectory adjusting. The final requirement is to arrange for window resize and scroll events to call the alignFloatLayers function aswell, via the body element onresize and onscrollevent handlers.
<body onresize="alignFloatLayers()" onscroll="alignFloatLayers()"> The codeThe easiest way to incorporate this functionality into a page is to collect the various non-page specific parts together into a dedicated JavaScript file, usually denoted by a js extension, and include it into the page via a <script> tag.
<script language="javascript" src="FloatLayer.js"></script> Such a file is available on the downloads page. |
Content of this page Copyright © Robert Quince 1996 - 2005. |