4
\$\begingroup\$

How does this look for basic image rotation? What is there to be improved here?

<p>    
  <img src='images/rotation/dsc_0052.jpg' id='rotateImg0' alt='image rotation' />
  <img src='images/rotation/bilde1.jpg' id='rotateImg1' style='display:none;' alt='image rotation'  />
  <img src='images/rotation/bilde2.jpg' id='rotateImg2' style='display:none;' alt='image rotation' />
  <img src='images/rotation/bilde3.jpg' id='rotateImg3' style='display:none;' alt='image rotation' />
  <img src='images/rotation/bilde4.jpg' id='rotateImg4' style='display:none;' alt='image rotation' />
  <img src='images/rotation/dsc_0043.jpg' id='rotateImg5' style='display:none;' alt='image rotation' />  
</p>

  <script type='text/javascript'>
      var rotation = function () {
          var currentImage;
          var count = function () {              
              // Figure out how many images we have on the rotation          
              var x = 0;
              while (document.getElementById('rotateImg' + (x + 1).toString()))
                  x++;
              return x;
          } ();

          var hideImages = function () {
              // Hide all the images              
              for (var i = 0; i < count; i++) {
                  document.getElementById('rotateImg' + i.toString()).style.display = 'none';
              }
          };

          var showImage = function (number) {
              document.getElementById('rotateImg' + number.toString()).style.display = 'block';
          };

          var fn = {};
          fn.setupRotation = function () {
              // Show the first image
              currentImage = 0;
              showImage(currentImage);

              // Start the rotation
              var interval = setInterval("rotation.advanceRotation()", 4000);
          };

          fn.advanceRotation = function () {
              if (currentImage + 1 == count)
                  currentImage = 0;
              else
                  currentImage++;
              hideImages();
              showImage(currentImage);
          }

          return fn;
      } ();

      rotation.setupRotation();      
  </script>
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Three things I noticed:

1. Looking through the document with DOM methods is a slow process. There's often no way around it, and it won't crash anything here, but try to minimize it when you can. From page 183 of Stoyan Stefanov's JavaScript Patterns:

DOM access is expensive; it's the most common bottleneck when it comes to JavaScript performance. This is because the DOM is usually implemented separately from the JavaScript engine...

The bottom line is that DOM access should be reduced to a minimum. This means:

  • Avoiding DOM access in loops
  • Assigning DOM references to local variables and working with the locals
  • Using selectors API where available
  • Caching the length when iterating over HTML collections

I've done that here. I created one extra variable named images and assigned it an empty array. Then, using your counting function, I store the image references in it.

var currentImage,
    images = [],

      ...

    count = (function () {              
      // Figure out how many images we have on the rotation          
      var x = 0;
      while (document.getElementById('rotateImg' + (x + 1).toString())) {
        images[x] = document.getElementById('rotateImg' + x.toString());
        x++;
      }
      return images.length;
    }()),

(If you're able to change the HTML, you can avoid the loop altogether and collect these images in one line. Just add an id to the element that contains them...

<p id="container">
  <img src='images/rotation/dsc_0052.jpg' id='rotateImg0' alt='image rotation' />
  ...
</p>

...and collect them with...

var images = document.getElementById("container").getElementsByTagName("img");

But this doesn't help you if you can't change the markup.)

Now you can run hideImage and showImages on the array instead of having to walk the DOM every time.

2. When the first argument to setInterval is a string, the browser runs eval() -- an implementation of the JavaScript compiler -- on it before firing. That's also expensive and unnecessary here. From page 111 of Douglas Crockford's JavaScript: The Good Parts:

The browser provides setTimeout and setInterval functions that can take string arguments or function arguments. When given string arguments, setTimeout and setInterval act as eval. The string argument form also should be avoided.

Getting rid of the quotes and the closing parentheses makes it a plain old function reference, which works fine.

So I changed this:

var interval = setInterval("rotation.advanceRotation()", 4000);

To this:

var interval = setInterval(rotation.advanceRotation, 4000);

3. Not crucial here. You can do all your variable declaration with one var statement per function. This reduces the chances of your variables being overwritten or unavailable due to hoisting. From JavaScript Patterns again:

JavaScript enables you to have multiple var statements anywhere in a function, and they all act as if the variables were declared at the top of the function... This can lead to logical errors when you use a variable and then declare it further in the function.

So I took out the multiple var statements and declared them all at the top of the outer function.

The final code:

<html lang="en">
  <head>
  <title>Rice Flour Cookies' image rotation test</title>

  </head>

  <body>
    <p>    
      <img src='images/rotation/dsc_0052.jpg' id='rotateImg0' alt='image rotation' />
      <img src='images/rotation/bilde1.jpg' id='rotateImg1' style='display:none;' alt='image rotation'  />
      <img src='images/rotation/bilde2.jpg' id='rotateImg2' style='display:none;' alt='image rotation' />
      <img src='images/rotation/bilde3.jpg' id='rotateImg3' style='display:none;' alt='image rotation' />
      <img src='images/rotation/bilde4.jpg' id='rotateImg4' style='display:none;' alt='image rotation' />
      <img src='images/rotation/dsc_0043.jpg' id='rotateImg5' style='display:none;' alt='image rotation' />  
    </p>
  </body>
</html>

<script type='text/javascript'>
  var rotation = function () {
    var currentImage,
        images = [],
        count,
        hideImages,
        showImage,
        fn;

        count = (function () {              
          // Figure out how many images we have on the rotation          
          var x = 0;
          while (document.getElementById('rotateImg' + (x + 1).toString())) {
            images[x] = document.getElementById('rotateImg' + x.toString());
            x++;
          }
          return images.length;
        })();

        hideImages = function () {
          // Hide all the images              
          for (var i = 0; i < count; i++) {
            images[i].style.display = 'none';
          }
        };

        showImage = function (number) {
          images[number].style.display = 'block';
        };

        fn = {};
        fn.setupRotation = function () {
          // Show the first image
          currentImage = 0;
          showImage(currentImage);

          // Start the rotation
          var interval = setInterval(rotation.advanceRotation, 4000);
        };

        fn.advanceRotation = function () {
          if (currentImage + 1 == count)
            currentImage = 0;
          else
            currentImage++;
            hideImages();
            showImage(currentImage);
        };

        return fn;
    } ();

  rotation.setupRotation();      
</script>

And you can see it at work here: http://james.da.ydrea.ms/riceflourcookies/rotate.html

\$\endgroup\$
1
  • \$\begingroup\$ Thanks, I have implemented most of what you suggest on my (private) site. It works very well. \$\endgroup\$ Commented Nov 7, 2011 at 17:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.