As the author of the WWW FAQ, I regularly answer questions about the workings of the Web. If a question is frequently asked, I simply add an article to the FAQ. But sometimes a question is more detailed, more in-depth— not really a FAQ, but still of interest to others. You'll find those questions, with my answers, here in Innards along with commentary on other web-technology-related topics.
2004-12-07Broad Street Life is a bit of DHTML performance art which demonstrates the principles of high-performance DHTML Javascript coding. It does not demonstrate the principles of gorgeous web design. But if you want to learn a thing or two about writing DHTML code that runs fast while manipulating hundreds of elements many times per second, read on.
DHTML Performance Lessons
Simple, reasonable applications of DHTML don't have much of a speed problem. Maybe you have a few <div> elements you want to animate a bit, or more likely just change the appearance of on the fly when someone mouses over them. Unreasonable applications are another story.Broad Street Life plays out in a 39x30 grid. That's not large as Life simulations go, but it represents a potentially huge number of HTML elements, all created on the fly by Javascript: 1,170 cells. Every one of those is represented by a <div> element, with an <img> element inside.
My first effort ran like molasses, especially in Microsoft Internet Explorer. I generated the elements in a loop, using simple document.write() calls to create each one. I generated both an "on" cell and an "off" cell for each location in the grid, using the CSS visibility attribute to control which one appeared at any given time. Once the game began running, I called document.getElementById() almost 2,340 times for every single pass. Ouch. Performance was acceptable in Firefox, but truly appalling in Internet Explorer.
With some help from a Microsoft technical article I can no longer locate, I determined that getElementById was the major bottleneck. By fetching each element into a two-dimensional array immediately after creating it with document.write and always referring to the array in the future, I was able to increase performance by an enormous amount. Try clicking "Faster" numerous times in the game. Hums right along, now.
Still, startup time is still pretty ugly, with almost 2,340 objects to create. Today I implemented a second important optimization by replacing all of the DHTML elements for cells that are "off" with a tiled background, like so:
document.write("<div style=\"position: absolute; top: 52;
left: 10; width: " + width * 14 + "; height: " + height * 14 +
"; z-index: 0; background-repeat: repeat; background-image:
url(white_tile.jpg)\" onClick=\"clickm(event)\"></div>");
To my surprise, this did not speed up the loading of the game by a factor of just two; the improvement appears to be closer to a factor of four. Hooray for tiled backgrounds.
You'll note the onClick handler. That brings me to the most frustrating issue I encountered during the entire project: dealing with mouse click events. I wanted to allow users to click on cells in order to toggle them on and off, but I encountered no end of grief due to the overlapping "on" and "off" elements, both of which would sometimes receive the mouse event... or not... in Internet Explorer, regardless of whether they were visible or not. A similar issue comes up with the tiled background element, which might also get the click, seemingly without regard for the CSS z-index. And whether or not <div> itself would receive the click in the presence of an <img> element within was also difficult to pin down reliably across multiple browsers.
I finally dealt with this by including onClick handlers for both the individual cell <div> elements and the background <div>, and including the following "debounce" code to ignore any duplicate events that do come through:
var lastClick; var debounceInterval = 250; var lastWhere;There, isn't that perfectly ugly? But marvelously effective, and I never have to worry about those goldanged browser inconsistencies again. In this particular instance. Maybe.function clicked(i, j) { var where = i + "," + j; var now = new Date(); var ms = now.getTime(); if (where == lastWhere) { if (ms - lastClick < debounceInterval) { return; } } lastWhere = where; lastClick = ms; }
Follow us on Twitter | Contact Us
Copyright 1994-2012 Boutell.Com, Inc. All Rights Reserved.
