18.3. Scripting StylesThe crux of DHTML is the ability to use JavaScript to dynamically change the style attributes applied to individual elements within a document. The DOM Level 2 standard defines an API that makes this quite easy to do. In Chapter 17, we saw how to use the DOM API to obtain references to document elements either by tag name or ID or by recursively traversing the entire document. Once you've obtained a reference to the element whose styles you want to manipulate, you use the element's style property to obtain a CSS2Properties object for that document element. This JavaScript object has JavaScript properties corresponding to each of the CSS1 and CSS2 style attributes. Setting these properties has the same effect as setting the corresponding styles in a style attribute on the element. Reading these properties returns the CSS attribute value, if any, that was set in the style attribute of the element. It is important to understand that the CSS2Properties object you obtain with the style property of an element specifies only the inline styles of the element. You cannot use the properties of the CSS2Properties object to obtain information about the style-sheet styles that apply to the element. By setting properties on this object, you are defining inline styles that effectively override style-sheet styles. Consider the following script, for example. It finds all <img> elements in the document and loops through them looking for ones that appear (based on their size) to be banner advertisements. When it finds an ad, it uses the style.visibility property to set the CSS visibility attribute to hidden, making the ad invisible:
I've transformed this simple script into a "bookmarklet" by converting it to a javascript: URL and bookmarking it in my browser. I take subversive pleasure in using the bookmarklet to immediately hide distracting animated ads that won't stop animating. Here's a version of the script suitable for bookmarking:
The bookmarklet is written with very compact code and is intended to be formatted on a single line. The javascript: at the beginning of this bookmarklet identifies it as a URL whose body is a string of executable content. The void 0 statement at the end causes the code to return an undefined value, which means that the browser continues to display the current web page (minus its banner ads, of course!). Without the void 0, the browser would overwrite the current web page with the return value of the last JavaScript statement executed. 18.3.1. Naming Conventions: CSS Attributes in JavaScriptMany CSS style attributes, such as font-family, contain hyphens in their names. In JavaScript, a hyphen is interpreted as a minus sign, so it is not possible to write an expression like: element.style.font-family = "sans-serif"; Therefore, the names of the properties of the CSS2Properties object are slightly different from the names of actual CSS attributes. If a CSS attribute name contains one or more hyphens, the CSS2Properties property name is formed by removing the hyphens and capitalizing the letter immediately following each hyphen. Thus, the border-left-width attribute is accessed through the borderLeftWidth property, and you can access the font-family attribute with code like this: element.style.fontFamily = "sans-serif"; There is one other naming difference between CSS attributes and the JavaScript properties of CSS2Properties. The word "float" is a keyword in Java and other languages, and although it is not currently used in JavaScript, it is reserved for possible future use. Therefore, the CSS2Properties object cannot have a property named float to correspond to the CSS float attribute. The solution to this problem is to prefix the float attribute with the string "css" to form the property name cssFloat. Thus, to set or query the value of the float attribute of an element, use the cssFloat property of the CSS2Properties object. 18.3.2. Working with Style PropertiesWhen working with the style properties of the CSS2Properties object, remember that all values must be specified as strings. In a style sheet or style attribute, you can write: position: absolute; font-family: sans-serif; background-color: #ffffff; To accomplish the same thing for an element e with JavaScript, you have to quote all of the values: e.style.position = "absolute"; e.style.fontFamily = "sans-serif"; e.style.backgroundColor = "#ffffff"; Note that the semicolons go outside the strings. These are just normal JavaScript semicolons; the semicolons you use in CSS style sheets are not required as part of the string values you set with JavaScript. Furthermore, remember that all the positioning properties require units. Thus, it is not correct to set the left property like this: e.style.left = 300; // Incorrect: this is a number, not a string e.style.left = "300"; // Incorrect: the units are missing Units are required when setting style properties in JavaScript, just as they are when setting style attributes in style sheets. The correct way to set the value of the left property of an element e to 300 pixels is: e.style.left = "300px"; If you want to set the left property to a computed value, be sure to append the units at the end of the computation: e.style.left = (x0 + left_margin + left_border + left_padding) + "px"; As a side effect of appending the units, the addition of the unit string converts the computed value from a number to a string. You can also use the CSS2Properties object to query the values of the CSS attributes that were explicitly set in the style attribute of an element or to read any inline style values previously set by JavaScript code. Once again, however, you must remember that the values returned by these properties are strings, not numbers, so the following code (which assumes that the element e has its margins specified with inline styles) does not do what you might expect it to: var totalMarginWidth = e.style.marginLeft + e.style.marginRight; Instead, you should use code like this: var totalMarginWidth = parseInt(e.style.marginLeft) + parseInt(e.style.marginRight); This expression simply discards the unit specifications returned at the ends of both strings. It assumes that both the marginLeft and marginRight properties were specified using the same units. If you exclusively use pixel units in your inline styles, you can usually get away with discarding the units like this. Recall that some CSS attributes, such as margin, are shortcuts for other properties, such as margin-top, margin-right, margin-bottom, and margin-left. The CSS2Properties object has properties that correspond to these shortcut attributes. For example, you might set the margin property like this:
Arguably, it is easier to set the four margin properties individually: e.style.marginTop = topMargin + "px"; e.style.marginRight = rightMargin + "px"; e.style.marginBottom = bottomMargin + "px"; e.style.marginLeft = leftMargin + "px"; You can also query the values of shortcut properties, but this is rarely worthwhile, because typically you must then parse the returned value to break it up into its component parts. This is usually difficult to do, and it is much simpler to query the component properties individually. Finally, let me emphasize again that when you obtain a CSS2Properties object from the style property of an HTMLElement, the properties of this object represent the values of inline style attributes for the element. In other words, setting one of these properties is like setting a CSS attribute in the style attribute of the element: it affects only that one element, and it takes precedence over conflicting style settings from all other sources in the CSS cascade. This precise control over individual elements is exactly what we want when using JavaScript to create DHTML effects. When you read the values of these CSS2Properties properties, however, they return meaningful values only if they've previously been set by your JavaScript code or if the HTML element with which you are working has an inline style attribute that sets the desired property. For example, your document may include a style sheet that sets the left margin for all paragraphs to 30 pixels, but if you read the leftMargin property of one of your paragraph elements, you'll get the empty string unless that paragraph has a style attribute that overrides the style sheet setting. Thus, although the CSS2Properties object is useful for setting styles that override any other styles, it does not provide a way to query the CSS cascade and determine the complete set of styles that apply to a given element. Later in this chapter we will briefly consider the getComputedStyle( ) method, which does provide this ability. 18.3.3. Example: Dynamic Bar ChartsWhen adding graphs and charts to your HTML documents, you typically implement them as static, inline images. Because the CSS layout model is heavily based on rectangular boxes, however, it is possible to dynamically create bar charts using JavaScript, HTML, and CSS. Example 18-3 shows how this can be done. This example defines a function makeBarChart( ) that makes it simple to insert bar charts into your HTML documents. The code for Example 18-3 uses the techniques shown in Chapter 17 to create new <div> elements and add them to the document and the techniques discussed in this chapter to set style properties on the elements it creates. No text or other content is involved; the bar chart is just a bunch of rectangles carefully sized and positioned within another rectangle. CSS border and background-color attributes are used to make the rectangles visible. The example includes some simple math to compute the height in pixels of each bar based on the values of the data to be charted. The JavaScript code that sets the position and size of the chart and its bars also includes some simple arithmetic to account for the presence of borders and padding. With the techniques shown in this example, you should be able to modify Example 18-2 to include a JavaScript function that dynamically creates windows of any specified size. Figure 18-3 shows a bar chart created using the makeBarChart( ) function as follows: <html> <head> <title>BarChart Demo</title> <script src="BarChart.js"></script> </head> <body> <h1>y = 2<sup>n</sup></h1> <script>makeBarChart([2,4,8,16,32,64,128,256,512], 600, 250, "red");</script> <i>Note that each bar is twice as tall as the one before it, the result of rapid exponential growth.</i> </body> </html> ![]() Figure 18-3. A dynamically created bar chartExample 18-3. Dynamically creating bar charts
18.3.4. DHTML AnimationsSome of the most powerful DHTML techniques you can achieve with JavaScript and CSS are animations. There is nothing particularly special about DHTML animations; all you have to do is periodically change one or more style properties of an element or elements. For example, to slide an image into place from the left, you increment the image's style.left property repeatedly, until it reaches the desired position. Or you can repeatedly modify the style.clip property to "unveil" the image pixel by pixel. Example 18-4 contains a simple HTML file that defines a div element to be animated and a short script that changes the background color of the element every 500 milliseconds. Note that the color change is done simply by assigning a value to a CSS style property. What makes it an animation is that the color is changed repeatedly, using the setInterval( ) function of the Window object. (You'll need to use setInterval( ) or setTimeout( ) for all DHTML animations; you may want to refresh your memory by reading about these functions in the client-side reference section.) Finally, note the use of the modulo (remainder) operator % to cycle through the colors. Consult Chapter 5 if you've forgotten how that operator works. Example 18-4. A simple color-changing animation
Example 18-4 produces a very simple animation. In practice, CSS animations typically involve modifications to two or more style properties (such as top, left, and clip) at the same time. Setting up complex animations using a technique like that shown in Example 18-4 can get quite complicated. Furthermore, in order to avoid becoming annoying, animations should typically run for a short while and then stop, but there is no way to stop the animation produced by Example 18-4. Example 18-5 shows a JavaScript file that defines a CSS animation function that makes it much easier to set up animations, even complex ones. The animateCSS( ) function defined in this example is passed five arguments. The first specifies the HTMLElement object to be animated. The second and third arguments specify the number of frames in the animation and the length of time each frame should be displayed. The fourth argument is a JavaScript object that specifies the animation to be performed. And the fifth argument is an optional function that should be invoked once when the animation is complete. The fourth argument to animateCSS( ) is the crucial one. Each property of the JavaScript object must have the same name as a CSS style property, and the value of each property must be a function that returns a legal value for the named style. Every time a new frame of the animation is displayed, each of these functions is called to generate a new value for each of the style properties. Each function is passed the frame number and the total elapsed time and can use these arguments to help it return an appropriate value. An example should make the use of animateCSS( ) much clearer. The following code moves an element up the screen while gradually uncovering it by enlarging its clipping region:
The next code fragment uses animateCSS( ) to move a Button object in a circle. It uses the optional fifth argument to animateCSS( ) to change the button text to "Done" when the animation is complete. Note that the element being animated is passed as the argument to the function specified by the fifth argument:
The code in Example 18-5 is fairly straightforward; all the real complexity is embedded in the properties of the animation object that you pass to animateCSS( ), as we'll see shortly. animateCSS( ) defines a nested function called displayNextFrame( ) and does little more than use setInterval( ) to arrange for displayNextFrame( ) to be called repeatedly. displayNextFrame( ) loops through the properties of the animation object and invokes the various functions to compute the new values of the style properties. Note that because displayNextFrame( ) is defined inside animateCSS( ), it has access to the arguments and local variables of animateCSS( ), even though displayNextFrame( ) is invoked after animateCSS( ) has already returned! This works even if animateCSS( ) is called more than once to animate more than one element at a time. (If you don't understand why this works, you may want to review Section 11.4.) Example 18-5. A framework for CSS-based animations
Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|
|