Приглашаем посетить
Зощенко (zoschenko.lit-info.ru)

The Rendering Context

The Rendering Context

While CSS positioning offers tremendous flexibility, it can often add a lot of complexity to the page. The preceding examples demonstrate using CSS positioning on elements that are positioned independently. One of the key advantages of HTML is its ability to automatically reflow contents depending on their size and the size of the window. If the Web author intends to position elements in response to the size of the window and contents, the author must write custom layout code with script rather than rely on HTML. In general, it is easier to author and maintain documents that use dynamic styles to take advantage of the automatic flow nature of HTML than to write custom layout code, and writing a custom layout manager can require a large amount of script.

Dynamic HTML exposes the information—complex as it is—necessary to create a powerful custom layout. For each element, this information includes offset information and the identity of the element from which the offsets are calculated. To write scripts that handle their own layout, you have to understand these offset relationships.

Rendering information—the size and position of each element in the body of a document—is recalculated by the browser each time the document is reflowed. Rendering information is therefore much more transient than parsing information, which includes the attributes, styles, and contents defined for the elements in the source document. The distinction between the values provided by the document and the rendering values calculated by the browser is important to understand.

For example, an element might be defined as having a width value of 20% and an unspecified height. The 20% value as well as its pixel equivalent are exposed through the style property. However, the height value is not exposed through the style property because it is not defined. When the browser renders the element, it calculates a height and exposes it as a separate property. In addition, the browser calculates and exposes the top and left positions of the element; these values are not always the same as the top and left values defined using CSS positioning.

Each element is drawn relative to another element, its offset parent. An element's offset parent provides the rendering context in which the element is drawn. The Body element is the topmost offset parent. For many elements, the Body element is the offset parent, and the browser calculates each element's position relative to the upper left corner of the document. But if an element is inside an absolutely positioned DIV element, for example, its position is calculated relative to the DIV element, which is its offset parent. An offset parent provides the context in which an element is rendered; specifically, it defines a root for the offsets that determine the element's position.

Every element exposes its rendering information. An element's offsetParent property contains a reference to the element defining its rendering context, and its offsetTop and offsetLeft properties contain its coordinates with respect to the origin defined by its offsetParent. In addition, rectangular elements generally expose offsetWidth and offsetHeight properties, which represent the element's size.

Only certain elements and elements of certain styles can define new rendering contexts and become offset parents for other elements. The following elements define new rendering contexts:

Each element has a single offset parent, and it might define a rendering context for any number of child elements. In this regard, an element's offset parent is similar to its parent element in the parsing tree. But an element's offset parent is not required to be the same as its parent in the parsing tree; its offsetParent and parentElement properties can and often will reference different elements. A diagram showing the offset parents for all elements in the document is known as the rendering tree for the document.

Figure 12-7 shows both the parsing tree and the rendering tree for the following document.

<HTML>
   <HEAD>
      <TITLE>Parsing Tree vs. Rendering Tree</TITLE>
   </HEAD>
   <BODY>
      <P>The parsing tree represents the
         <EM>containership hierarchy</EM>
         defined by the contents of the HTML document.</P>
      <DIV ID=D1 STYLE="position:absolute; top:60; left:20">
         <P>The rendering tree represents the relationship between
            elements as they are rendered by the browser.</P>
         <DIV ID=D2 STYLE="height:80; width:100%; overflow:scroll">
            <P>This code creates a scrolling element. However, it does
               not define a new <EM>coordinate system</EM>. The
               following element is positioned based on the coordinate
               system of the absolutely positioned DIV.</P>
            <IMG STYLE="position:absolute; top:60; left:40"
               SRC="img1.gif">
         </DIV>
      </DIV>
   </BODY>
</HTML>

The Rendering Context

Figure 12-7. Parsing tree and rendering tree for a document.

In this example, the Paragraph element, the EM element, and the first DIV element are all children of the Body element. The EM element becomes a rendering child of the body because its parent element, Paragraph, is not a constrained element according to the preceding list. The first DIV element, on the other hand, defines a new rendering context because it is absolutely positioned. Therefore, all elements within this DIV element are children of this rendering container unless another element creates a new rendering context within the DIV element.

The second DIV element, D2, also creates a new rendering context because it is a constrained container. This is where things seem to get tricky. When an element is positioned absolutely, it is taken out of the flow of the document and positioned relative to the nearest coordinate system. A constrained container does not necessarily define a new coordinate system. The scrolling DIV, D2, does not create a new coordinate system because no absolute or relative positioning is specified. Only elements with position values of absolute or relative create new coordinate systems. Therefore, the image inside D2 that is absolutely positioned is actually positioned relative to the first DIV, D1. This relationship is also maintained in the rendering relationship. D1 is the offset parent for D2.

A Rendering Context Demonstration

The relationship between an element and its rendering context is best understood by examining a sample HTML document. The following document, included on the companion CD, reports the offsets of any element on the page. The document also contains examples of several different ways in which a rendering context can be created. Clicking on any element in the document displays a list of offsets for each rendering context the element is contained within.

<HTML>
   <HEAD>
      <TITLE>Offset Demonstration</TITLE>
      <STYLE TYPE="text/css">
         BODY, TD, DIV, CAPTION, FIELDSET, LEGEND {cursor:default}
      </STYLE>
      <SCRIPT LANGUAGE="JavaScript">
         function doClick() {
            // Build a string of all the offsets, starting from the
            // clicked element.
            var el = event.srcElement;
            var offset = "Offsets\n";
            while (el != null) {
               offset += "\n" + el.tagName + ":  (" + el.offsetTop +
                  ", " + el.offsetLeft + ")";
               el = el.offsetParent;
            }
            alert(offset);
         }
         document.onclick = doClick;
      </SCRIPT>
   </HEAD>
   <BODY>
      <H1>Offset Demonstration</H1>
      <P>Click on an element to see its rendering context and offset
         relationship. This page helps demonstrate how an element
         becomes constrained and creates a new rendering context for
         the elements it contains.
      <P>This is a standard paragraph containing
         <EM>emphasized text</EM>.
      <TABLE BORDER>
         <CAPTION>Table <EM>Demo</EM></CAPTION>
         <TR><TD>Table Cell 1</TD>
            <TD>Table Cell <STRONG>2</STRONG></TD></TR>
         <TR><TD>Table Cell 3</TD><TD>Table Cell 4</TD></TR>
      </TABLE>
      <FIELDSET STYLE="width:200pt">
         <LEGEND>Fieldset <EM>Demo</EM></LEGEND>
         <P>This is a fieldset.
         <BUTTON><P>HTML <STRONG>Button</STRONG></BUTTON>
      </FIELDSET>
      <P STYLE="position:relative; top:50; left:160pt">This is a
         <EM>relatively</EM> positioned paragraph.</P>
      <DIV STYLE="overflow:auto; height:50pt; width:150pt
            border:1pt gray solid">
         <P>This DIV element has a constrained width and height and
            may display scrollbars if the contents <EM>do not</EM>
            fit.
      </DIV>
      <DIV STYLE="position:absolute; top:300pt; left:150pt;
            width:100pt; border:1pt gray solid">
         <DIV STYLE="position:absolute; top:0pt; left:120pt;
               width:100pt; border:1pt gray solid">
            <P>This is an absolutely positioned DIV element within
               another absolutely positioned DIV element.
         </DIV>
         <P>This is an absolutely positioned DIV element.</P>
      </DIV>
   </BODY>
</HTML>

The Offset Properties of Relatively Positioned Elements

A relatively positioned element's top and left style properties represent its offsets from its normal position in the flow, but its offsetTop and offsetLeft properties represent its position with respect to its offset parent. Figure 12-8 demonstrates the relationship between these style properties and rendered position properties.

The Rendering Context

Figure 12-8. A relatively positioned element's top and left style properties and its offsetTop and offsetLeft properties.

The offset properties are purely rendering properties that represent the calculated positions of an element in the document.

Determining Whether an Element Is in View

The following function can determine whether the upper left corner of an element is currently visible on the screen. This function returns false if the element's upper left corner is not visible, even if the element is partially on screen. The function works this way so that it can can be applied to any element on the page.

function onScreen(e) {
   // Test whether the supplied element is visible.
   var rp = e.offsetParent;
   if (rp == null)
      return false;
   var pleft = e.offsetLeft;
   var ptop = e.offsetTop;
   while (true) {
      if (!((pleft >= rp.scrollLeft) &&
            (pleft <= rp.scrollLeft + rp.clientWidth) &&
            (ptop >= rp.scrollTop) &&
            (ptop <= rp.scrollTop + rp.clientHeight)))
         return false;
      pleft += rp.offsetLeft - rp.scrollLeft ;
      ptop += rp.offsetTop - rp.scrollTop;
      rp = rp.offsetParent;
      if (rp == null)
         return true;
   }
}

This code can be easily enhanced to test whether an intrinsic control or a constrained element is visible by factoring in the width and height of the element, but this technique will not work for nonrectangular elements because they do not expose offsetWidth and offsetHeight properties.

Scrolling to an Element

Any element in the body of a document can be brought into view using the scrollIntoView method. The scrollIntoView method supports a single optional parameter that specifies whether the element should appear as the first or last line in the window. Omitting the parameter or supplying the value true scrolls the element to the first line; a value of false scrolls the element to the last line. For example, the following code scrolls the first H1 element in the document into view:

// Scroll element to the first line.
document.all.tags("H1").item(0).scrollIntoView()
// Scroll element to the last line.
document.all.tags("H1").item(0).scrollIntoView(false)

Identifying an Element at a Specified Position

The document object exposes the elementFromPoint method for identifying an element at a particular xy-coordinate position on the screen. This method takes an xy-pixel position relative to the window's client area and returns the element object at that position. The elementFromPoint method is useful for determining what element the mouse is on during an event handler. For example, the following code places the tag name of the element the mouse is on in a text box:

<HTML>
   <HEAD>
      <TITLE>Where Is the Mouse?</TITLE>
      <SCRIPT FOR="document" EVENT="onmousemove()"
            LANGUAGE="JavaScript">
         document.all.txtCurrent.value =
            document.elementFromPoint(event.x, event.y).tagName; 
      </SCRIPT>
   </HEAD>
   <BODY>
      <H1>This Is a <EM>Header.</EM></H1>
      <P>Current Element: <INPUT TYPE=TEXT ID="txtCurrent" SIZE=20>
      </P>
   </BODY>
</HTML>

The Map Element

The Map element defines a special rendering context. Because a Map element can be shared by multiple images, it is considered outside any rendering context. Therefore, the Map element returns null for the offsetParent property and 0 for the offsetTop and offsetLeft properties. The Area elements within the Map element return values for their offsetTop and offsetLeft properties relative to the upper left corner of the containing Map element and return the Map element as the offset parent. Therefore, to determine the position of an Area element on the screen, you must take into account the particular image's offsets.

Aligning Relatively Positioned Elements

Aligning elements horizontally or vertically can range from trivial, requiring no code, to somewhat complex, requiring a fair amount of code. With two absolutely positioned elements within the same coordinate system, aligning the elements is as simple as providing the same top or left property. Because relatively positioned elements are offset from their position in the flow, aligning relatively positioned elements requires a few lines of code.

The following document demonstrates how to first stack all relatively positioned elements on top of each other and then animate them back to their normal positions in the document. The code that aligns the elements is contained in the alignElements function. This function takes any relatively positioned element and stacks it on top of the element with an ID of src. Alternatively, a fixed point on the screen can be used instead of another element.

<HTML>
   <HEAD>
      <TITLE>Animating from a Single Point</TITLE>
      <STYLE TYPE="text/css">
         .fly {position:relative; color:navy; visibility:hidden}
      </STYLE>
      <SCRIPT LANGUAGE="JavaScript">
         function alignElements(el) {
            /* Position the passed-in relatively positioned
               element that is in the same coordinate system
               on top of the element whose ID is src. */
            el.style.pixelTop
               = document.all.src.offsetTop - el.offsetTop;
            el.style.pixelLeft
               = document.all.src.offsetLeft - el.offsetLeft;
            el.style.visibility = "visible";
         }

         function moveIn(el) {
            // If the element is not at its position in the flow,
            // move it closer. 
            var moved = false;
            if (el.style.pixelTop < 0) {
               el.style.pixelTop += 8;
               if (el.style.pixelTop > 0)
                  el.style.pixelTop = 0;
               moved = true;
            }
            else {
               if (el.style.pixelTop > 0)  {
                  el.style.pixelTop -= 8;
                  if (el.style.pixelTop < 0)
                     el.style.pixelTop = 0;
                  moved = true;
               }
            }
            if (el.style.pixelLeft < 0) {
               el.style.pixelLeft += 8;
               if (el.style.pixelLeft > 0)
                  el.style.pixelLeft = 0;
               moved = true;
            }

            else {
               if (el.style.pixelTop > 0) {
                  el.style.pixelLeft -= 8;
                  if (el.style.pixelLeft < 0)
                     el.style.pixelLeft = 0;
                  moved = true;
               }
            }
            /* The move variable reflects whether the element has
               moved. If the element has already reached its position
               in the flow, this function returns false. */
            return moved;
         }

         function flyInTogether() {
            var more = false;
            // Animate into place all elements with class name fly.
            for (var intLoop = 0; intLoop < document.all.length;
               intLoop++) {
               if ("fly" == document.all[intLoop].className)
                  more = moveIn(document.all[intLoop]) || more;
            }
            // Keep running until all elements reach their locations
            // in the flow.
            if (more) 
               setTimeout("flyInTogether()", 10); 
         }
  
         function setup() {
            // Align all elements that are going to be animated.
            for (var intLoop = 0; intLoop < document.all.length;

                  intLoop++) {
               if ("fly" == document.all[intLoop].className)
                  alignElements(document.all[intLoop]);
            }
            flyInTogether();
         }

         window.onload = setup;
      </SCRIPT>
   </HEAD>
   <BODY>
      <H1 ID=src>Animate from a Single Point</H1>
      <UL>
         <LI CLASS="fly"><P>Create animated documents.</P>
         <LI CLASS="fly"><P>All elements start together
            at a single point.</P>
         <LI CLASS="fly"><P>This example works using relative
            positioning.</P>
         <LI CLASS="fly"><P>First align the elements, and then fly
            them into place.</P>
         <LI CLASS="fly"><P>Once the elements are in place, this is
            a standard HTML document!</P>
         <LI CLASS="fly"><P>Simply supplying a special class name
            animates an element.</P>
      </UL>
      <P STYLE="text-align:center">Not all text must be animated!
   </BODY>
</HTML>

[Содержание]