resizing images to fit the window?

Dear Lazyweb,

What's the right way to auto-scale images such that they fit exactly inside the browser window, while preserving their aspect ratio?

The images in the DNA Lounge photo and flyer galleries auto-scale to fit in the window. However, this only works horizontally. If the image is wider than the window, it shrinks, but if the image is still taller than the window, it does not. I'd like to make it scale in both directions, so that on an iPhone, both portrait and landscape images default to being fully visible, regardless of the phone's orientation.

The current (horizontal-only) behavior is is accomplished in CSS. The image has:

    width:100%; height: auto;
    max-width:900px; max-height:600px;

I think that there's no way to make the image fit within the window using only CSS (and also preserve the image's aspect ratio). I think the only way to accomplish that is with Javascript.

So I tried this:

function scale_images() {
var maxw = window.innerWidth;
var maxh = window.innerHeight;
var aa = document.images;
for (var i = 0; i < aa.length; i++) {
var img = aa[i];
var w = img.naturalWidth;
var h = img.naturalHeight;
var r = h/w;
if (h > maxh) {
h = maxh;
w = h/r;
}
if (w > maxw) {
w = maxw;
h = w*r;
}
img.style.width = w + "px";
img.style.height = h + "px";
}
}
document.body.onload = scale_images;
document.body.onresize = scale_images;
document.body.onorientationchange = scale_images;

Well, that sort-of works, except that the viewport sizes I'm getting on iPhone are weird. In portrait mode, I am getting a 320x356 viewport (as expected) but when I rotate to landscape, I'm getting 320x139 instead of 480x208, which means the document is scaled up and the font size increases. What's the fix for this, short of disabling all scaling (even pinch-zooming)?

Surely others have solved this problem? Got examples?

Tags: , ,

25 Responses:

  1. bobrk says:

    Check out image viewing in search.

    • jwz says:

      Yeah well, the AJAX nightmare in there is absolutely no help, unless you can explain what they are doing.

  2. endquote says:

    In modern browsers, you could do something like:

    <div style="width: 100%; height: 100%; background-image:url(foo.jpg); background-size: 100% 100%;"></div>

    Of course if you then want to click on it, you'll want to add a click handler to the div, as well as cursor:pointer.

    • jwz says:

      I know how media queries work and I don't see how they are even remotely relevant here.

      • neacal says:

        Ouch. While I'm not sure this will work (I avoid web development like the plague), I can at least see how this might be relevant:

        If you know the aspect ratio of your image (i.e. it doesn't change from image to image), you could use the media query regarding the aspect ratio of the display area to conditionally make it either width 100% + height auto or vice versa.

        E.g. for max-aspect-ratio: 4/3 do one thing
        for min-aspect-ratio: 4/3 do the other thing

        I'm not sure whether the intersection of the two cases need special handling, or that won't be a problem...

      • sternutator says:

        Indeed - that was the wrong target. Instead, simply assign custom CSS classes depending on image aspect ratio:


        <!DOCTYPE HTML>
        <html>
        <head>
        <style type="text/css">
        .L { width: 100%; height: auto; }
        .P { width: auto; height: 100%; }
        </style>
        <script language="javascript" type="text/javascript">
        function scale_images() {
        var images = document.images;
        for (var i = 0; i < images.length; i++) {
        var image = images[i];
        if (image.naturalHeight/image.naturalWidth > 1.0) {
        image.className += " P";
        } else {
        image.className += " L";
        }
        }
        }
        </script>
        </head>

        <body onload="scale_images();" onresize="scale_images();" onorientationchange="scale_images();">
        <img src="logo.png">
        </body>

        Tested on iPhone and iPad.

        • sternutator says:

          PS: Ahem, forget about onresize and onorientationchange. It's not like your images change upon those events.

          • jwz says:

            So, imagine you have a square image. If you have to pick between "width:100%" and "height:100%", it's going to work when the window is portrait, or landscape, but not both. Using portrait versus landscape classes isn't the same as "fit box A inside box B".

            Anyway. I have the "fit inside the box" code working in the code I posted, the weird part that I still don't understand is why, when I rotate the phone, my viewport is 320 wide instead of 480.

            • edm says:

              Out of interest, do you get 320x139 only when loading in portrait mode and rotating (ie, after the orientationchange fires), or also when you load in landscape mode? Observing some pages through orientation change on my iPhone I get the impression that there's a choice made to just scale everything up on changing from portrait to landscape, rather than to reflow the page. Reloading the page after the orientation change gets it reflowed, differently, for the wider width. (Once I noticed this effect I'd occasionally deliberately rotate to portrait before loading then switch back to landscape, in order to get a larger font without having to scroll back and forth as a result of pinch-zooming.) So I'm wondering if the way this is achieved is by keeping the reported viewport width constant through the orientation change, and just applying a scaling factor to what gets rendered.

              Ewen

      • knowbuddy says:

        CSS3 media queries would come in handy for figuring out the orientation of the screen, then use height/width auto on the appropriate dimension:

        @media screen and (orientation:portrait) {
        img.fit {
        max-width: 100%;
        width: auto;
        /* don't specify a height and it'll be auto-scaled */
        }
        }
        /* repeat with height for landscape */

        You might make the case that it's not precisely what you asked for, as it won't always fit on the screen without scrolling. I'd make the case that I hate having to pinch-zoom images that are fit to screen, because I've got a pretty, hi-def device, and would rather scroll in one dimension than see some scaled-down version that wastes a ton of screen real estate.

        It'll be borked on noncompliant browsers, but it is at least forward-compatible and you can hold out hope that eventually the browsers will catch up.

        • knowbuddy says:

          Actually, it may be even easier than that -- no fancy hackery needed. I've got a test page here:

          http://rickosborne.org/test/bigpic.html

          It sets the dimensions of the html and body to 100%, then just sets image max-width and max-height, completely ignoring the width and height. I don't have an iPhone to test on, but it does work on my Android, which I hope is pretty close.

          • jwz says:

            Inconsistent behavior in desktop Safari: start with window big; fully shrink horizontally; fully shrink vertically, enlarge vertically; now image has wrong aspect ratio.

            Also, you are right, this isn't what I asked for. Many of the images I have are not 4:3 or close to it, because I'm dealing not only with photos but with flyers, and those come in all shapes and sizes.

        • jwz says:

          If you specify "max-width", you always have to specify "max-height: auto" or it does stupid things in MSIE (all versions, I think.) Likewise for width/height, I think.

  3. I would suggest asking at stackoverflow.com. I've got surprisingly good answers to most questions I've asked there, and as a bonus the lame ones are modded down so you don't have to read through annoying and irrelevant stuff.

  4. Chris Brent says:

    Try setting initalScale=1.0 on your viewport meta tag.
    I think this:
    http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html#//apple_ref/doc/uid/TP40006509-SW1
    Is trying to tell you what's happening. Of course webkit on android does it slightly differently.

    • jwz says:

      First thing I tried. Setting initial-scale=1.0 does nothing.

      Setting "maximum-scale=1.0" gets me proper viewports of 320x356 and 480x268, but disables pinch-zooming.

      Oh wait, upon further investigation, initial-scale=1.0 doesn't do nothing... if you load the page in portrait, and then rotate, the viewport is still 320 wide when in landscape. But if you then 1) zoom all the way out and 2) hit reload, now the viewport is 420 wide. So, that's not useful, but at least it's not being ignored...

      I'm not clear on what "-webkit-text-size-adjust:none" is supposed to do, but I also haven't seen that do anything at all.

  5. chipaca says:

    I haven't tested it with MSIE (because I don't want to know, really), but http://pyvore.com/junk/ does what I think you want, on the webkit and mozilla I tested it on.