My Vietnam

My life in Vietnam, plain and simple

Archive for February 2009

Dynamic Resize by Background Image

with 3 comments

Not so long ago I came across a problem with dynamically setting the background images for my HTML elements. I wanted to let the user decide what background image to set for certain HTML elements through the cascading style sheet.

I did not want to bother the user with setting the dimensions of the background image, my code should handle this part automatically. To show why it’s not sufficient to just set the background image for a div element I created the following initial example:

< !DOCTYPE html PUBLIC
   "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Dynamic Background Image</title>

   <link type="text/css" rel="stylesheet" href="DynamicBackgroundImage.css" />
</head>

<body>
   <div id="aDivElementWithDimensions"></div>
   <hr />
   <div id="aDivElementWithoutDimensions"></div>
</body>
</html>

The cascading style sheet referred to by the name DynamicBackgroundImage.css in the HTML above, I put in the same folder and was pretty simple, two div elements with their background set, one element was specified with dimensions the other one wasn’t. Setting the dimensions of the second element I wanted to control dynamically:

#aDivElementWithDimensions
{
   border: solid 1px #000000;
   height: 285px;
   width: 280px;
   background-image: url(TestImage.jpg);
}

#aDivElementWithoutDimensions
{
   border: solid 1px #000000;
   background-image: url(TestImage.jpg);
}

For the test I found a suitable image of my daughter Mary, as we are under construction, she is wearing a helmet for safety:

testimage.jpg

As expected the first div element showed the background image whereas the other div element did not. The behavior was the same for both Internet Explorer 7 and Firefox 3.0.6 which I used for my experiments, figuring that there would be a pretty good chance it would work for other browsers as well if IE and FF3 worked.

dynamicbackgroundimagefirsttryhtm

My next naïve attempt at solving this problem didn’t work out very well, using a script on load I attempted to get the url for the background image so I could retrieve the dimensions and set the div element’s dimensions accordingly. I updated my HTML page with a JavaScript triggered on page load:

< !DOCTYPE html PUBLIC
   "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Dynamic Background Image</title>

   <link type="text/css" rel="stylesheet" href="DynamicBackgroundImage.css" />

   <script type="text/javascript"><!--
   function pageload()
   {
      // Get the element without dimensions
      var theDivElementWithoutDimensions =
         document.getElementById("aDivElementWithoutDimensions");

      // Create a buffer image used to retrieve
      // the dimensions of the image
      var aBufferImage = document.createElement("img");

      // Handle onload event to resize the div element
      aBufferImage.onload = function()
      {
         // Thanks to JavaScript closure we can access
         // the theDivElementWithoutDimensions variable
         // from the onload method
         with(theDivElementWithoutDimensions.style)
         {
            width = this.width+"px";
            height = this.height+"px";
         }
      }

      // Loading the image from the background image
      // into the buffer image should trigger the onload
      // event and allow us to set the dimensions for
      // the div element
      aBufferImage.src = theDivElementWithoutDimensions.style.backgroundImage;
   }
   --></script>
</head>

<body onload="pageload();">
   <div id="aDivElementWithDimensions"></div>
   <hr />
   <div id="aDivElementWithoutDimensions"></div>
</body>
</html>

However my attempt was not in anyway successful, the second div element remained collapsed not showing the background image, so what was the problem?

Well it turns out that the background image when set from CSS is not available through the normal style property found on HTML elements, but there is a way around it using the computed style instead.

Of course the computed style is not implemented in the same way for IE and FF, that would have been a bit too easy, right :-) So to get the computed style without worrying about what browser is used, I added a little wrapper function:

function getStyle(elt)
{
   var computedStyle;

   if(typeof elt.currentStyle != 'undefined')
      // IE
      computedStyle = elt.currentStyle;
   else
      // Firefox
      computedStyle = document.defaultView.getComputedStyle(elt, null);

   return computedStyle;
}

From the computed style it is possible to get the URL for the background image; computedStyle.backgroundImage which will return url(“url-for-image”) in IE and url(url-for-image) in FF, adding a wrapper function for parsing the URL as well proved useful:

function cleanupUrl(aUrl)
{
   var regExp = /^url\("?(.*?)"?\)$/ig;
   var matches = regExp.exec(aUrl);

   if(matches == null || typeof matches[1] == "undefined")
      return null;

   return matches[1];
}

With these functions added only a few adjustments were needed to the pageload-function in order to get it all up and running:

function pageload()
{
   var theDivElementWithoutDimensions =
      document.getElementById("aDivElementWithoutDimensions");
   var computedStyle =
      getStyle(theDivElementWithoutDimensions);

   var dummyImage = document.createElement("img");
   dummyImage.onload = function()
   {
      // Thanks to JavaScript closure we can access
      // the theDivElementWithoutDimensions variable
      // from the onload method
      with(theDivElementWithoutDimensions.style)
      {
         width = this.width+"px";
         height = this.height+"px";
      }
   };
   dummyImage.src = cleanupUrl(computedStyle.backgroundImage);
}

Finally I got the wanted behavior and a greater understanding of the way this specific style sheet property can be access from JavaScript.

dynamicbackgroundimageworkinghtm

But instead of stopping here I wanted more, because over the last year or so jQuery has grown in popularity, and as Scott Hanselman last year announced on his blog; Microsoft will ship jQuery with ASP.NET MVC and Visual Studio.

I didn’t know so much about jQuery until last year when I started to use it and I was taking by storm! It is such an incredible framework, and so different from other AJAX frameworks I have worked with.

I wanted to try it out for my problem, so that I didn’t have to think about computed styles. I created a copy of my initial HTML page and included the jQuery files:

< !DOCTYPE html PUBLIC
   "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Dynamic Background Image</title>

   <link type="text/css" rel="stylesheet" href="DynamicBackgroundImage.css" />

   <script type="text/javascript" src="http://code.jquery.com/jquery-latest.pack.js"></script>
   <script type="text/javascript">
      function cleanupUrl(aUrl)
      {
         var regExp = /^url\("?(.*?)"?\)$/ig;
         var matches = regExp.exec(aUrl);

         if(matches == null || typeof matches[1] == "undefined")
           return null;

         return matches[1];
      }

      function ApplyBackgroundImageDimensions(elt)
      {
         // Get the baground image using jQuery,
         // no need to worry about computed style
         // because jQuery will handle that part
         var backgroundImageUrl = cleanupUrl($(elt).css("background-image"));

         // If no background image is set then return
         if(backgroundImageUrl == null)
            return;

         // Create a dummy image to get the dimensions
         // of the image
         var dummyImage = document.createElement("img");

         // When the image is loaded then set the dimensions
         dummyImage.onload = function()
         {
            // Thanks to JavaScript closure we can access
            // the elt variable from the onload method
            $(elt).css("width", this.width+"px")
                  .css("height", this.height+"px");
         };

         // Get the URL for the background image
         dummyImage.src = backgroundImageUrl;
      }

      function pageload()
      {
         // Get the element without dimensions
         var theDivElementWithoutDimensions =
            document.getElementById("aDivElementWithoutDimensions");

         // Apply the dimensions from the background image
         ApplyBackgroundImageDimensions(theDivElementWithoutDimensions);
      }

      $(document).ready(pageload);
   </script>
</head>

<body>
   <div id="aDivElementWithDimensions"></div>
   <hr />
   <div id="aDivElementWithoutDimensions"></div>
</body>
</html>

Neat, But way too clumsy! This ruins the simplicity which can be achieved with jQuery, so I had to figure out how to create a small extension for jQuery, making my code, simple and thus easier to read.

Making an extension to jQuery turned out to be really easy, after looking at a few examples I got the idea, and I created the file DynamicBackgroundImage.js to hold my extension in. I added one function called fitToBackgroundImage which can be called on any HTML element.

I should probably build in some more error handling, but that can wait.

/*
 *  jQuery Dynamic Background Image Plugin
 *  Requires jQuery v1.2.6 or later
 *
 *  Copyright (c) Thomas Bindzus
 *  (http://bindzus.wordpress.com)
 *
 *  Dual licensed under the MIT and GPL licenses:
 *  http://www.opensource.org/licenses/mit-license.php
 *  http://www.gnu.org/licenses/gpl.html
 *
 *  Version: 1.0
 */
(function($)
{
   $.fn.fitToBackgroundImage = function()
   {
      // Helper function to extract the URL from CSS
      function cleanupUrl(aUrl)
      {
         var regExp = /^url\("?(.*?)"?\)$/ig;
         var matches = regExp.exec(aUrl);

         if(matches == null || typeof matches[1] == "undefined")
            return null;

         return matches[1];
      }

      // Returning the updated HTML elements in a
      // way that allows for other jQuery methods
      // to manipulate them further
      return this.each
      (function(index)
      {
         // Ensure we get a reference to the element
         // which we can use closure on
         var $this = $(this);

         // Get the baground image using jQuery,
         // no need to worry about computed style
         // because jQuery will handle that part
         var backgroundImageUrl = cleanupUrl($this.css("background-image"));

         // If no background image is set then return
         if(backgroundImageUrl == null)
            return;

         // Create a dummy image to get the dimensions
         // of the image
         var dummyImage = document.createElement("img");

         // When the image is loaded then
         // set the dimensions
         dummyImage.onload = function()
         {
            // Thanks to JavaScript closure we can access
            // the elt variable from the onload method
            $this.css("width", this.width+"px")
                 .css("height", this.height+"px");
         };

         // Get the URL for the background image
         dummyImage.src = backgroundImageUrl;

         // Cleanup
         dummyImage = null;
      });
   }
}) (jQuery);

With this addition finally true jQuery beauty arrived in my HTML page:

< !DOCTYPE html PUBLIC
   "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Dynamic Background Image</title>

   <link type="text/css"
      rel="stylesheet"
      href="DynamicBackgroundImage.css" />

   <script type="text/javascript" src="http://code.jquery.com/jquery-latest.pack.js"></script>
   <script type="text/javascript" src="DynamicBackgroundImage.js"></script>
   <script type="text/javascript">
   function pageload()
   {
      // Apply the dimensions from the background image
      $("#aDivElementWithoutDimensions").fitToBackgroundImage();
   }

   $(document).ready(pageload);
   </script>
</head>

<body>
   <div id="aDivElementWithDimensions"></div>
   <hr />
   <div id="aDivElementWithoutDimensions"></div>
</body>
</html>

For this post I found useful information in a lot of places, main references resulting in my completion of this post are listed below:

https://developer.mozilla.org/en/CSS/background-image
https://developer.mozilla.org/en/DOM/window.getComputedStyle
http://blog.stchur.com/2006/06/21/css-computed-style
http://www.hanselman.com/blog/jQueryToShipWithASPNETMVCAndVisualStudio.aspx
http://docs.jquery.com
http://plugins.jquery.com/project/jqTooltip

Written by Thomas Bindzus

February 8, 2009 at 18:08:40