Grow your CSS skills. Land your dream job.

Crop Top

Published by Guest Author

The following is a guest post by Parker Bennett where he explores some different ways to approach the behavior of fluid and responsive images.

Sometimes you want an image to resize responsively but restrict its height — cropping it then as it widens. Here, we explore three options with various trade-offs.

The happy couple, front and center: background-position: center bottom.

Option One: background-image

Edit on CodePen

Here, in place of an <img>, we create a div with a background-image and use CSS3's dandy new background-size: cover to have it size proportionally. As a bonus, we can easily crop from the top, center, or bottom using background-position.

One potential issue is we need to specify a height for the div to show up. This gives it a fixed height that doesn't scale proportionally (at least not without CSS media queries). When it gets narrow enough, the sides start cropping (this might be preferable, it depends).

Also, a quick check with caniuse.com shows this will fail conspicuously in IE8 (showing the image full size), but thanks to Louis-Rémi Babé, there's a background-size polyfill workaround (though it does require a relative or fixed position, and a z-index). IE8 also needs a polyfill for CSS media queries. There are a couple options.

.bg-image {
  /* image specified in separate class below */
  height: 240px;
  width: 100%; 
}

.bg-image-wedding {
  background-image: url(img/photo-wedding_1200x800.jpg);

  /* lt ie8 */
  -ms-background-position-x: center;
  -ms-background-position-y: bottom;
  background-position: center bottom;

  /* scale bg image proportionately */
  background-size: cover;
    
  /* ie8 workaround - http://louisremi.github.io/background-size-polyfill/ */
  -ms-behavior: url(/backgroundsize.min.htc);
  /* prevent scaling past src width (or not) */
  /* max-width: 1200px; */
}

/* example media queries */
@media only screen and (min-width : 768px) {
  .bg-image { height: 320px; }
}

@media only screen and (min-width : 1200px) {
  .bg-image { height: 400px; }
}

That's me in the middle: background-position: center center.

Something to note, background-size: cover will readily upscale larger than the src image's native size (or not, if you set a max-width).*

Still, there are some downsides to using a background-image. It's not as semantic or modular as an img, so it's less straightforward to maintain. You're seemingly stuck with a fixed height or cumbersome CSS media queries. Also, users can't save the image as easily (sometimes preferable).

Option Two: img with a Twist

Edit on CodePen

Here, we use an img with max-width set to a percentage of the containing element so it scales responsively, then wrap it in a div with overflow: hidden and a specified height or max-height.

wedding


When you resize past 700px, the bride disappears.

.crop-height {
  /* max-width: 1200px; /* img src width (if known) */
  max-height: 320px;
  overflow: hidden; 
}

img.scale {
  /* corrects inline gap in enclosing div */
  display: block;
  max-width: 100%;
  /* just in case, to force correct aspect ratio */
  height: auto !important;
  width: auto\9; /* ie8+9 */
  /* lt ie8 */
  -ms-interpolation-mode: bicubic; 
}

As you can see, the bottom of the image now gets cropped as it widens. But what if you want it to crop from the top? Surprisingly, you can — using CSS3's transform:rotate() we add a "flip" class to both img.scale and div.crop-height — flipping the img all the way around.

wedding


There's the happy couple! (Unless you're using IE8 or lower.)

/* apply to both img.scale and div.crop-height */
.flip {
  -webkit-transform: rotate(180deg);
  -moz-transform:    rotate(180deg);
  -ms-transform:     rotate(180deg);
  -o-transform:      rotate(180deg);
  transform:         rotate(180deg);
  /* needed? not sure */
  zoom: 1;
}

img.flip {
  /* if native or declared width of img.scale
     is less than div.crop-height, this will
     flipped img left */
  float: right;
  /* add clearfix if needed */ 
}

Novel, but limited to top-cropping only. And, as you might expect, this doesn't work in IE8, either — perhaps failing less conspicuously (it will just crop from the bottom as before). The bad news is, I haven't found any polyfill options that work because they need a specified height and width.

Option Three: Hybrid Approach

Edit on CodePen

What if we could have the advantages of specifying an img (e.g., using max-height so we get proportional vertical scaling below a certain height), but also the flexible cropping and IE8 polyfill support afforded a background-image? We can!


The img has visibility: hidden, the background-image has background-position: center center.

The trick is to make the responsively-sizing img invisible. With visibility: hidden it retains layout, so we see the background-image behind it. (Since it's the same image source it shouldn't have to download twice.) If you want user-friendly access, you could instead use opacity: 0. Now users can drag the image or right-click to save. (Opacity and background-size need some extra IE8 workaround bits.)

wedding


opacity: 0. A background-image you can drag or right-click to save. Works in IE8 (with help).

Another thought is to use a more compressed proxy img, and use CSS media queries to serve a higher-resolution background-image as needed. This img could even be a proportionately smaller size (e.g., matching your max-height), or watermarked. Check out this CSS-Tricks post on media queries, and this helpful resolution mixin for Sass.

wedding


Here, a 70 KB half-scale img is holding the space for the higher-res background-image.

.crop-height {
    /* max-width: 1200px; /* img src width (if known) */
    max-height: 320px;
    overflow: hidden; }

.bg-image-wedding {
    /* for small devices */
    background-image: url(img/photo-wedding_1200x800.jpg);
    /* lt ie8 */
    -ms-background-position-x: center;
    -ms-background-position-y: bottom;
    background-position: center bottom;
    /* scale bg image proportionately */
    background-size: cover;
    /* ie8 workaround - http://louisremi.github.io/background-size-polyfill/ */
    -ms-behavior: url(/backgroundsize.min.htc);
    */ prevent scaling past src width (or not) */
    /* max-width: 1200px; */ }

.invisible {
    visibility: hidden; }

.transparent {
    /* trigger hasLayout for IE filters below */
    zoom: 1;
    /* 0 opacity in filters still displays layout */
    -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
    filter: alpha(opacity=10);
    opacity: 0; }

/* example media query for smaller non-retina devices */
@media
    only screen and (max-device-width : 600px) and (-webkit-max-device-pixel-ratio: 1),
    only screen and (max-device-width : 600px) and (        max-device-pixel-ratio: 1) {
      
     .bg-image-wedding {
       background-image: url(img/photo-wedding_600x400.jpg); 
     }
}

/* example media query for retina ipad and up */
@media
    only screen and (min-device-width : 768px) and (-webkit-min-device-pixel-ratio: 1.5),
    only screen and (min-device-width : 768px) and (        min-device-pixel-ratio: 1.5) {

      .bg-image-wedding {
        background-image: url(img/photo-wedding_1200x800@1.5x.jpg); 
      }
}

Wrapping Up

Until the day where CSS3's object-fit is supported, which may be a while, this hybrid option seems like the best approach to me. Still, it's nice to have options. I hope you got something out of my way-too-thorough exploration. You can take a look at the source code for more, edit on CodePen, or download the example files here. If you have any questions, comments, or corrections, drop me a line: parker@parkerbennett.com.


* You can make an img upscale like a background-image if you want. You just need to "pre-enlarge" it, adding a proportionately larger width and height to the img element itself: <img width="2400px" height="1600px" src="img/photo-wedding_1200x800.jpg /> (edit on CodePen).

Comments

  1. AB
    Permalink to comment#

    Looks like someone’s about to get bludgeoned to death in the background :S

    • Nathan
      Permalink to comment#

      lmfaooo! well spotted :)

      Made me look closer at the image. A couple of other interesting people there to.

      Would have loved to see the snap a second or so later…

  2. Fabio
    Permalink to comment#

    Hey Parker,
    Nice post! I follow this website on my feed reader, and I enjoy it a lot.

    About your post:
    What about using background-size:100% and background-position:bottom center ?

    < div style=”display:block; width:100%; height:300px; background-image:url(http://cdn.css-tricks.com/wp-content/uploads/2013/06/photo-wedding_1200x800.jpg); background-position:bottom center; background-size:100%;”>

    • Parker
      Permalink to comment#

      Thanks, Fabio! You can see how background-size:100% differs by resizing the pane smaller than the aspect-ratio of the image. In your example the image repeats vertically. We can add background-repeat: no-repeat, but with the image bottom center, we see a gap above the image because the div remains 300px in height as the image gets smaller. With background-size:cover, the image fills that gap but crops on the sides.

  3. Dan
    Permalink to comment#

    Thank you for this great post. I’m building a piece as we speak and with just one line of your example you fixed about 5 problems for me. Great post.

  4. Great article about css, quality content for front-end engineer!!!

  5. Is there an easy way to pan this image with mouse (to see rest of the photo)?

  6. You’re seemingly stuck with a fixed height or cumbersome CSS media queries.

    Or you can use the padding trick to keep a responsive aspect ratio and center the image within that.

    .background {height:0; padding-bottom:40%;}
    
    • Parker
      Permalink to comment#

      Thanks, Corey! I added this “padding trick” to the CodePen for Option One for comparison.
      The problem with this is it doesn’t restrict the height of the image, which was how I started down this path (I had a client who wanted things “above the fold”). It acts a lot like max-width:100% on an img, except you have some cropping flexibility using background-position and varying the padding-bottom.

    • J
      Permalink to comment#

      ^ this.

      You can even do this on the img element itself to avoid using an extra wrapper DIV.

       img {height: 0; padding: 0 0 40%; background: url(img/photo-wedding_1200x800.jpg) center bottom;}
  7. velPL
    Permalink to comment#

    And how about replacing img src in hybrid option with inline 1x1px size transparent image to avoid loading both http://parkerbennett.com/croptop/img/photo-wedding_1200x800.jpg and http://parkerbennett.com/croptop/img/photo-wedding_600x400.jpg on startup (chrome loads both images). It might be not quite semantic-friendly, but reduces one request.

    • Parker
      Permalink to comment#

      You can — and you can eliminate the http request entirely by coding the 1-pixel gif as a data uri:

      img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"

      But apart from being less semantic and user-friendly, it doesn’t work to set an aspect ratio by declaring a height and width — as your image scales down it becomes a square. I appended the CodePen for Option Three to demonstrate this. (You could create a transparent gif with a matching aspect ratio, e.g., 3px by 2px, if maintaining that is important.)

  8. Fabio
    Permalink to comment#

    Hi Parker,
    You are right. I noticed that a few seconds after I sent the comment. Sorry about that. So I immediately created a JSFIDDLE test and posted it here, but for some reason it didn’t save the last version of the code. I put it there again, in case you want to take a look. However that fix is only suitable if the images used into those boxes keep a specific aspect ratio.

    Ps.: I’m not sure if you wanted anybody to keep filling you comment area with extra ideas (like I’m doing), but I just liked the question involving your post, so tried to see how I would do it myself. Maybe someone can improve the approach I gave to the question.

    http://jsfiddle.net/gomidefabio/BVXGj/15/

    • Parker
      Permalink to comment#

      Hey, this is great, Fabio — this is exactly the kind of exchange of ideas I was hoping for!

      To sum up, Fabio suggested a refinement of Option One: When the image width is in proportion to the declared height, use a media query to switch the height to auto. Now the image will continue to scale down in proportion. Smart!

    • Parker
      Permalink to comment#

      Ah. I should clarify, Fabio. Your approach works, but it’s basically duplicating what max-height:500px would do, without needing a media-query. That’s the advantage of using a transparent placeholder img — it provides the height we would otherwise need to specify for the background-image to display, and we can restrain it with min-width, max-width, min-height or max-height.

  9. Great post Chris – it came in at the right time as I was working on something similar. I admit it, I am going to bite the code but give credit where it’s due :-)

  10. Parker
    Permalink to comment#

    Hey, I neglected to mention that you can fine-tune cropping with background-position by using percentages, e.g., background-position: 30% 60%. I revised the CodePen on Option Three to demonstrate.

  11. Dan
    Permalink to comment#

    Oh boy! Thanks a lot for this post! It saved me a ton of work. The background-size:cover property is awesome. Thanks again!

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".