CSS背景大小:覆盖+背景附着:固定剪裁背景图像

42

我有一个包含背景图像的图表列表,类似于以下内容:

<ul>
  <li>
    <figure style="background-image: url(...);"></figure>
  </li>
  <li>
    <figure style="background-image: url(...);"></figure>
  </li>
  <li>
    <figure style="background-image: url(...);"></figure>
  </li>
</ul>

这些图片都将它们的 background-size 设置为 cover ,并将 background-attachment 设置为 fixed

figure {
  width: 100%;
  height: 100%;
  background-size: cover;
  background-attachment: fixed;
}

当每个图像占据整个视口时,这很好,但如果有任何偏移,背景图像就会被裁剪。

据我所知,这是有意设计的(https://developer.mozilla.org/en-US/docs/Web/CSS/background-size#Values)。

我希望图片在垂直或水平方向上只裁剪一个方向,并且根据图形本身的大小居中。

我知道有JavaScript解决方案,但是否有一种使用CSS的方法来实现呢?

这里是一个有效的示例:http://codepen.io/Godwin/pen/KepiJ


它只在演示中水平裁剪。你还有什么意思? - Zach Saucier
@ZachSaucier,对我来说,在Chrome和Firefox中它被水平和垂直地剪裁了。看看地图,如果你知道美国应该是什么样子的,那么就没有新英格兰、佛罗里达或西部州。 - Godwin
1
(如果您的屏幕高度较小) - Godwin
9个回答

68

很遗憾,这只是CSS中固定定位工作方式的一个副作用,没有纯CSS的解决方法 - 你必须使用Javascript。

出现这种情况的原因是因为background-attachment: fixedbackground-size: cover的组合。当您指定background-attachment: fixed时,它会使background-imageposition: fixed图像一样运行,这意味着它不再处于页面流和定位上下文中,并且相对于视口而不是其所在的元素成为背景图像。

因此,每当您同时使用这些属性时,cover值将相对于视口的大小计算,而不考虑元素本身的大小,这就是为什么当元素与视口大小相同时可以按预期工作,但当元素小于视口大小时会被裁剪的原因。

为了解决这个问题,您基本上需要使用background-attachment: scroll并在JS中绑定一个事件监听器来监听scroll事件,以手动更新background-position,相对于窗口滚动的距离来模拟固定定位,但仍然相对于容器元素而不是视口来计算background-size: cover


3
为什么在最后一段建议使用 CSS 大小和 JS 定位而不是 CSS 定位和 JS 大小? - Will
2
不是特别的,两种方式都可以实现,虽然我没有尝试过你提到的那种方式。主要是因为在这种情况下,我认为JS定位比JS调整大小更快/更容易编写。 - Ennui
5
有一种更简单的方法来实现这个。不要使用背景定位(background-position)的固定(fixed)属性,而是创建一个具有固定定位(position: fixed)的背景元素(background element),然后使用background-size: cover来调整大小。这将根据背景元素与视口的大小覆盖图像,并仍然给你一个固定的背景。 - Matthew Dean

23

这个问题有一个jQuery的解决方案:http://jsfiddle.net/QN9cH/1/,虽然不是最理想的解决方案,但至少可以解决问题 :)

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

鼠标滚轮太过于跳跃。 - VVS

4

Nick Noordijk的回答让我找到了正确的方向,但我喜欢避免每次滚动事件发生时都执行计算的脚本。这是我的版本,只在页面加载或屏幕大小更改时执行计算:

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 + '%');
}

请注意,这只适用于横向的"横幅"图片 - 使用background-size: auto nn%不能像background-size: cover一样具有同样的优势,不管您的图像在任何方向上是否超出范围都可以工作。

自适应高度在vh中的最佳解决方案。 - VVS
非常好的解决方案! - Katarina Perović

3
background: url(<image>) center top no-repeat fixed;
background-size: auto <width size in %>;

没有真正的解决方案。但是,您可以将图像的大小高度设置为auto而不是cover。并在元素中以%调整宽度大小,直到它咬住边界。

如果您调整窗口大小,则不会被裁剪。相反,它将始终保持原始图像大小格式。通过以上方法,您基本上可以“缩放”它,但不会“覆盖”它。


2

background-size: cover;属性确实会裁剪图像,以便填充区域并且不留下任何空白。

background-size: contain;属性会确定哪个维度更大,并根据此进行缩放。因此,如果您有一个100px x 100px的块和一个200x150px的背景图像,则将background-size设置为contain将使图像缩放为100x75px。在这种情况下,如果元素的宽高比与图像的宽高比不同,则会出现空白。

您还可以手动控制优先级,假设您知道图像的宽高比。

因此,如果您知道您的图像始终为100x200px,则意味着宽度始终是小尺寸,高度是大尺寸。

现在将background-size设置为100%自动,则可以确保您不会获得空白空间,但最终会出现裁剪。如果将其设置为background-size:auto 100%,则可以确保不会发生裁剪,高度永远不会出现空白(但宽度会)。

如果您想要裁剪并只是将图像居中,请使用background-position:50%。


谢谢您的回复,但这并没有解决问题。这只是解释了contain如何工作,但我要解决的问题是关于cover的。问题在于当图像高度大于视口时,cover会剪裁水平和垂直两个方向,我希望它可以只剪裁一个方向,而不是两个方向,也不是像contain一样全不剪裁。 - Godwin
你可以使用background-size: 100% auto;。这将确保元素内的宽度不被裁剪且大小为100%。如果高度小于元素的高度,则会有空白区域。否则,它将被裁剪。 - kakashigr
我希望宽度和高度始终被覆盖。 - Godwin
这可以通过两种方式实现:
  1. 如果元素的高度和宽度与图片相同,则将background-size设置为background-size: 100% 100%;。但是,如果它们不同,则图像将具有错误的纵横比。
  2. 如果元素具有不同的宽度和高度或其中一个是可变的(变化的),则无法填充区域而不剪切。
- kakashigr
2
谢谢你的帮助,但我认为你可能误解了问题。我非常清楚background-size应该如何工作,问题是当同时使用background-attachment时,它似乎会意外地改变行为。 - Godwin

1
另一个非常简单的解决方案是使用vwvh单位(如果您不知道,这在大多数情况下有完全可以接受的浏览器支持)。例如,不要使用background-size:cover,而要使用background-size:auto 100vh
如果您不熟悉vwvh,它们是视口单位,对应于视口宽度和视口高度。这些值对应于视口高度或宽度的百分比。例如,50vw表示视口宽度的50%。
对于我们的目的,我们可以简单地告诉背景图像占用视口高度的100%。 这里是一个演示。 如果您需要适应不同的纵横比,可以利用aspect-ratio @media规则

即使我使用 auto 100vh,问题仍然存在。 - aradalvand

0

一种为分别排列的元素实现视差效果的示例(而非全屏元素):

HTML:

<div style="background-image: url('/path/to/image.jpg')">Content</div>

CSS:

#element_to_scroll {
    background-repeat: no-repeat;
    background-size: cover; 
    background-position-x: center; 
    background-position-y: 0;   
}

js:

$(window).scroll(function() {
    var realImageWidth = 1280;
    var realImageHeight = 1024;
    var viewportBottom = $(window).scrollTop() + $(window).height();
    $('#element_to_scroll').each(function(){
        var scrollAmountPx = realImageHeight/(realImageWidth/$(this).outerWidth())-$(this).outerHeight();
        var elementOffsetFromBottom = viewportBottom-$(this).offset().top;
        var scrollAreaHeight = $(this).outerHeight() + $(window).height();
        if(elementOffsetFromBottom>0 && elementOffsetFromBottom<scrollAreaHeight) {
            var backgroundPositionOffset = Math.ceil(scrollAmountPx/scrollAreaHeight*elementOffsetFromBottom);
            //$(this).css('background-position-y',"-"+backgroundPositionOffset+"px");
            $(this).clearQueue().animate({'background-position-y':"-"+backgroundPositionOffset+"px"},50);
        }
    });
});

-1

2018年7月更新:现在有一行代码可以解决这个问题

我曾经也遇到过这个问题,直到阅读了this article之后才知道,我可以将以下代码添加到我的CSS中:

will-change: transform;

它起作用了!

现在我的CSS看起来像这样:

.myClass{
    position: relative;
    background: hsla(300, 100%, 90%, 0.1);
    overflow: hidden;
    &::before {
        background-image: url(/img/bg.jpg);
        background-size: cover;
        background-size: cover !important;
        -webkit-background-size: cover !important;
        background-repeat: repeat repeat !important;
        background-attachment: scroll !important;//"fixed" is the desired effect, but mobile browsers find it too "expensive" and disabled it, so use "scroll"
        background-position: 30% 50%;
        @media(min-width: $screen-sm-min){
            background-attachment: fixed !important;
            background-position: 30% 20%;
            will-change: transform;
        }//768
        content: "";
        display: block;
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        opacity: 0.1;
    }
}

在我的Windows Chrome、Edge和Safari上可以工作,但是在Firefox上不行。


-8

我做了一个带有响应式背景图片的版本。

.flexslider .slides > li {
  background-position: center center;
  background-repeat: no-repeat;
  -webkit-background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  background-size: cover;
}
#slider,
.alink {
  min-height: 300px
}
.alink {
  display: block
}

http://jsfiddle.net/onigetoc/grpku1gk/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接