CSS background-size: cover + background-attachment: fixed clipping background images

Unfortunately this is simply an artifact of how fixed positioning works in CSS and there is no way around it in pure CSS - you have to use Javascript.

The reason this happens is due to the combination of background-attachment: fixed and background-size: cover. When you specify background-attachment: fixed it essentially causes the background-image to behave as if it were a position: fixed image, meaning that it's taken out of the page flow and positioning context and becomes relative to the viewport rather than the element it's the background image of.

So whenever you use these properties together, the cover value is being calculated relative to the size of the viewport irrespective of the size of the element itself, which is why it works as expected when the element is the same size as the viewport but is cropped in unexpected ways when the element is smaller than the viewport.

To get around this you basically need to use background-attachment: scroll and bind an event listener to the scroll event in JS that manually updates the background-position relative to how far the window has been scrolled in order to simulate fixed positioning but still calculate background-size: cover relative to the container element rather than the viewport.


There's a jQuery fix for this: http://jsfiddle.net/QN9cH/1/ I know it's not optimal but at least it works :)

$(window).scroll(function() {
  var scrolledY = $(window).scrollTop();
  $('#container').css('background-position', 'left ' + ((scrolledY)) + 'px');
});

Nick Noordijk's answer put me on the right track, but I like to avoid scripts that perform a calculation every time the scroll event happens. Here's my version that only performs the calculation when page loads or screen size changes:

html:

<div class="fake-img"></div>

css:

.fake-img {
    display: block;
    height: 280px;  /* set the height here */
    width: 100%;
    background-image: url('http://example.com/path/to/image.jpg');
    background-repeat: no-repeat;
    background-position: center 68px;
    background-size: auto 50%; /* make a "best guess" here as a default */
    background-attachment: fixed;
    position: relative;
}

jQuery:

$(window).on('resize load orientationchange', function(){
    responsive_calc();
});

var responsive_calc = function(){

    // get the viewport height
    var h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;

    // get the element height
    var bannerHeight = $('.fake-img').height();

    // get the integer percentage value difference between them
    var bgHeightPercent = Math.ceil(bannerHeight/h*100);

    // set background-size height to match the element instead of the viewport
    $('.fake-img').css('background-size', 'auto ' + bgHeightPercent + '%');
}

Note that this really only works with landscape "banner" images - using background-size: auto nn% doesn't have the same advantage of background-size: cover in working no matter if your image has excess in either direction.