Quantcast
Channel: Kimili
Viewing all articles
Browse latest Browse all 6

The Flexible Scalable Background Image, Redux

$
0
0

Some time ago, I wrote about a method for setting up background images which scale to fill a browser window, regardless of the window’s size. Those background images would retain their aspect ratio as they scaled. They also wouldn’t shrink smaller than a certain size, as defined by the site author. By in large, the method was successful in meeting its goals, and it proved to be one of the most consistently trafficked posts I’ve written.

It was, however, a hacked together solution which leaned on Javascript to do the heavy lifting—a solution I was never competely satisfied with it. It didn’t work everywhere people wanted it to work (ahem, iOS devices), and it wasn’t as elegant as it could be. I eventually did some further research on the subject, and realzed that the latest crop of browsers could do it a better way. In modern browsers, the same result can be acheived using pure CSS. This ability is thanks to the fantastic CSS3 background-size property. By utilizing that property, along with a few simple media queries, we get the result we’re looking for.

The requrements for this version of the Flexible Scalable Background remain the same as those I set forth in the previous version. They are:

  • Fill up the browser window, regardless of the window size.
  • Maintain the aspect ratio of the image (so it doesn’t distort like in a funhouse mirror).
  • Resize with the browser window as the window got bigger.
  • Maintain a minimum size that it would not shrink beyond.
  • Remain centered.
  • Not cause scrollbars.
  • Not be in the HTML as an <img /> element.

We can add a few new requrements, too:

  • Work in as many browsers as possible, including modern mobile browsers
  • Implement as much as possible using only CSS.

So let’s get to it.

It’s all in the Styles.

In the original post, I stated, rather shortsightedly:

There was no way CSS was going to do it alone, even if I used CSS3’s mythical marginally supported background-size property.

That’s simply not the case any more. background-size is supported, in one form or another, in Firefox 3.6+, IE9 beta, Chrome 5+, Safari 4+, both in the desktop and in the mobile versions (the mobile implementation is currently less complete, but more on that in a bit). With such widespread support in the newest generation of browsers, it’s a good time to start utilizing the capabilities they have to offer. For these browsers, all the CSS that is necessary is as follows:

  1. body {
  2. background-attachment: fixed;
  3. background-color: #333;
  4. background-image: url(/img/hanging-on.jpg);
  5. background-position: top center;
  6. background-repeat: no-repeat;
  7.  
  8. margin:0;
  9. padding: 0;
  10.  
  11. background-size: cover;
  12. -moz-background-size: cover;
  13. -webkit-background-size: cover;
  14. }
  15.  
  16. /*
  17. This next definition doesn't allow the background to get any smaller
  18. than a predefined size (640x426px in this case). Change the values
  19. here to match your background image size. The configuration in the
  20. flexi-background javascript file should also match these values.
  21. */
  22.  
  23. @media only all and (max-width: 640px) and (max-height: 426px) {
  24. body {
  25. background-size: 640px 426px;
  26. -moz-background-size: 640px 426px;
  27. -webkit-background-size: 640px 426px;
  28. }
  29. }
  30.  
  31. /*
  32. The next 2 definitions are for support in iOS devices.
  33. Since they don't recoginze the 'cover' keyword for background-size
  34. we need to simulate it with percentages and orientation
  35. */
  36.  
  37. @media only screen and (orientation: portrait) and (device-width: 320px), (device-width: 768px) {
  38. body {
  39. -webkit-background-size: auto 100%;
  40. }
  41. }
  42.  
  43. @media only screen and (orientation: landscape) and (device-width: 320px), (device-width: 768px) {
  44. body {
  45. -webkit-background-size: 100% auto;
  46. }
  47. }

Now there are a few things to point out here. First, background-size: cover simply tells the browser “scale the background image to fit the window without distorting the image’s aspect ratio”. That right there gets us most of what we are after here. One thing it doesn’t do, though, is set a minimium size below which the image will not scale. That’s handled in the second definition, where we specifiy the background-size in pixel dimesions. By wrapping that definition with a media query which specifies max-height and max-width values, we ensure the definition only gets applied when the window is smaller than those dimensions.

As of this writing, I found that not all browsers recognize the cover keyword for background-size, and as a result, simply ignore it. The most conspicuous of this group is Mobile Safari—the browser found on the iPhone, iPad and iPod Touch. We can overcome that limitation by setting the background-size to 100% width or height, depending on the orientation. We can target the current orientation (as well as the iOS device, using device-width) and apply the correct styles using media queries, as you see above.

Making it Work in the Less Capable Browsers

If we only wanted this functionality in the most modern browsers, we could stop right there. In the interest of playing nicely with the lesser browsers, such as IE8 and below, we have to add some additional functionality to mimic what background-size can do so well. Most of what follows is a repurposing of my original approach to this problem, which relies on JavaScript to fill in the blanks.

First thing we’ll have to do is add some additional CSS which works along with the JavaScript we’ll use. That looks like this:

  1. img#expando {
  2. display: none;
  3. position: absolute;
  4. z-index: 1;
  5. -ms-interpolation-mode: bicubic;
  6. }
  7.  
  8. .wide img#expando,
  9. .tall img#expando {
  10. display: block;
  11. }
  12.  
  13. .wide img#expando {
  14. height: auto;
  15. width: 100%;
  16. }
  17.  
  18. .tall img#expando {
  19. height: 100%;
  20. width: auto;
  21. }

Finally, we’ll have to add the JavaScript which makes it work. If you used the original flexibackground script, you’ll remember that the JavaScript relied on jQuery to do its thing. That always bothered me a bit, as I thought there wasn’t that much there that required the overhead that jQuery adds. In this version, I’ve stripped out that requirement and made the script completely self-contained. You may notice that there are some additional utility functions which take the place of what I had used jQuery for—thing like adding events and manipulating classes. They won’t interfere with anything in the event that you do have jQuery or some other library in your templates, but you no longer have to have it for this.

If you are using jQuery and you prefer the more concise code that we can write when utilizing that library, I’ve also included a version of the script which takes advantage of it in the project’s github repository. Just download the package from there and you’ll have either version to choose from. Just be sure to use either one or the other—not both together!

The standalone version of the script now looks like this:

  1. (function(){
  2.  
  3. /**
  4. CONFIGURATION:
  5. Define the size of our background image
  6. */
  7. var bgImageSize = {
  8. width: 640,
  9. height: 426
  10. };
  11.  
  12. /*END CONFIGURATION */
  13.  
  14. /**
  15. Detect support for CSS background-size. No need for any more javascript if background-size is supported.
  16. Property detection adapted from the most excellent Modernizr <http://modernizr.com>
  17. */
  18. if ((function(){
  19. var el = document.createElement('div'),
  20. bs = 'backgroundSize',
  21. ubs= bs.charAt(0).toUpperCase() + bs.substr(1),
  22. props= [bs, 'Webkit' + ubs, 'Moz' + ubs, 'O' + ubs];
  23.  
  24. for ( var i in props ) {
  25. if ( el.style[props[i]] !== undefined ) {
  26. return true;
  27. }
  28. }
  29. return false;
  30. }())) {
  31. return;
  32. };
  33.  
  34. /**
  35. We also want to leave IE6 and below out in the cold with this
  36. */
  37. if ( false /*@cc_on || @_jscript_version < 5.7 @*/ ) {
  38. return;
  39. }
  40.  
  41. /**
  42. If we've gotten here, we don't have background-size support,
  43. so we'll have to mimic it with Javascript.
  44. Let's set up some variables
  45. */
  46. var elBody,
  47. imageID= 'expando',
  48. tallClass= 'tall',
  49. wideClass= 'wide',
  50. elBgImage, elWrapper, img, url, imgAR,
  51.  
  52. /**
  53. Since we're not relying on a library, we'll need some utility functions
  54. First, basic cross browser event adders
  55. */
  56. addEvent = function(el, evnt, func) {
  57. if (el.addEventListener) {
  58. el.addEventListener(evnt, func, false);
  59. } else if (el.attachEvent) {
  60. return el.attachEvent("on" + evnt, func);
  61. } else {
  62. el['on' + evnt] = func;
  63. }
  64. },
  65.  
  66. domLoaded = function(callback) {
  67. /* Internet Explorer */
  68. /*@cc_on
  69. @if (@_win32 || @_win64)
  70. document.write('<script id="ieScriptLoad" defer src="//:"><\/script>');
  71. document.getElementById('ieScriptLoad').onreadystatechange = function() {
  72. if (this.readyState == 'complete') {
  73. callback();
  74. }
  75. };
  76. @end @*/
  77. /* Mozilla, Chrome, Opera */
  78. if (document.addEventListener) {
  79. document.addEventListener('DOMContentLoaded', callback, false);
  80. }
  81. /* Safari, iCab, Konqueror */
  82. if (/KHTML|WebKit|iCab/i.test(navigator.userAgent)) {
  83. var DOMLoadTimer = setInterval(function () {
  84. if (/loaded|complete/i.test(document.readyState)) {
  85. callback();
  86. clearInterval(DOMLoadTimer);
  87. }
  88. }, 10);
  89. }
  90. },
  91.  
  92. /**
  93. Next, a way to properly get the computed style of an element
  94. Courtesy of Robert Nyman - http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
  95. */
  96. getStyle = function(el, css){
  97. var strValue = "";
  98. if (document.defaultView && document.defaultView.getComputedStyle){
  99. strValue = document.defaultView.getComputedStyle(el, "").getPropertyValue(css);
  100. }
  101. else if (el.currentStyle){
  102. css = css.replace(/\-(\w)/g, function (strMatch, p1){
  103. return p1.toUpperCase();
  104. });
  105. strValue = el.currentStyle[css];
  106. }
  107. return strValue;
  108. },
  109.  
  110. /**
  111. Finally, some element class manipulation functions
  112. */
  113. classRegex = function(cls) {
  114. return new RegExp('(\\s|^)'+cls+'(\\s|$)');
  115. },
  116.  
  117. hasClass = function(el, cls) {
  118. return el.className.match(classRegex(cls));
  119. },
  120.  
  121. addClass = function(el, cls) {
  122. if ( ! hasClass(el, cls)) {
  123. el.className += ' ' + cls;
  124. }
  125. },
  126.  
  127. removeClass = function(el, cls) {
  128. if (hasClass(el, cls)) {
  129. el.className = el.className.replace(classRegex(cls), '');
  130. }
  131. },
  132.  
  133. /*
  134. Now we can move on with the core functionality of Flexibackground
  135. */
  136. initialize = function() {
  137.  
  138. // No need for any of this if the screen isn't bigger than the background image
  139. if (screen.availWidth <= bgImageSize.width && screen.availHeight <= bgImageSize.height) {
  140. return;
  141. }
  142.  
  143. // Grab elements we'll reference throughout
  144. elBody= document.getElementsByTagName('body')[0];
  145.  
  146. // Parse out the URL of the background image and drop out if we don't have one
  147. url = getStyle(elBody, 'backgroundImage').replace(/^url\(("|')?|("|')?\);?$/g, '') || false;
  148. if (!url || url === "none" || url === "") {
  149. return;
  150. }
  151.  
  152. // Get the aspect ratio of the image
  153. imgAR = bgImageSize.width / bgImageSize.height;
  154.  
  155. // Create a new image element
  156. elBgImage = document.createElement('img');
  157. elBgImage.src = url;
  158. elBgImage.id = imageID;
  159.  
  160. // Create a wrapper and append the image to it.
  161. // The wrapper ensures we don't get scrollbars.
  162. elWrapper = document.createElement('div');
  163. elWrapper.style.overflow= 'hidden';
  164. elWrapper.style.width= '100%';
  165. elWrapper.style.height= '100%';
  166. elWrapper.style.zIndex= '-1';
  167.  
  168. elWrapper.appendChild(elBgImage);
  169. elBody.appendChild(elWrapper);
  170.  
  171. // Fix the wrapper into position
  172. elWrapper.style.position= 'fixed';
  173. elWrapper.style.top= 0;
  174. elWrapper.style.left= 0;
  175.  
  176. // Set up a resize listener to add/remove classes from the body
  177. addEvent(window, 'resize', resizeAction);
  178.  
  179. // Set it up by triggering a resize
  180. resizeAction();
  181.  
  182. },
  183.  
  184. /**
  185. Set up the action that happens on resize
  186. */
  187. resizeAction = function() {
  188. var win = {
  189. height: window.innerHeight || document.documentElement.clientHeight,
  190. width: window.innerWidth || document.documentElement.clientWidth
  191. },
  192.  
  193. // The current aspect ratio of the window
  194. winAR = win.width / win.height;
  195.  
  196. // Determine if we need to show the image and whether it needs to stretch tall or wide
  197. if (win.width < bgImageSize.width && win.height < bgImageSize.height) {
  198. removeClass(elBody, wideClass);
  199. removeClass(elBody, tallClass);
  200. } else if (winAR < imgAR) {
  201. removeClass(elBody, wideClass);
  202. addClass(elBody, tallClass);
  203. // Center the image
  204. elBgImage.style.left = Math.min(((win.width - bgImageSize.width) / 2), 0);
  205. } else if (winAR > imgAR) {
  206. addClass(elBody, wideClass);
  207. removeClass(elBody, tallClass);
  208. elBgImage.style.left = 0;
  209. }
  210. };
  211.  
  212. // When the document is ready, run this thing.
  213. domLoaded(initialize);
  214.  
  215. })();

I should point out that I’ve dropped support of IE6. It was always a bit fiddly to get it working correctly, and even then it was never great. Everything works as you expect it to in IE7 and above, and pretty much any other browser I’ve thrown at it.

Finally

Try out a working example of this new version in any browser you have handy. Go ahead and download it for your own use from Github. I’d be glad to hear what you think and I’d love to see what you do with it.


Viewing all articles
Browse latest Browse all 6

Trending Articles