CSS的background-size:cover背后的数学原理是什么?

31
我正在创建一个“图片生成器”,用户可以上传图片并添加文字或涂鸦。输出的图像大小是固定的(698x450)。
在客户端,当用户上传他们的图像时,它被设置为一个div的背景,该div的大小为698x450,并使用background-size:cover属性。这使其很好地填充了该区域。
最终组合的图像是由PHP使用GD函数生成的。我的问题是,如何在PHP中使图像按照CSS中的方式进行缩放。我希望PHP脚本的结果看起来与上面在CSS中设置图像的方式相同。 有人知道浏览器使用background-size:cover属性如何计算适当的图像缩放吗?我想将其转换为PHP。
谢谢
6个回答

57

这是计算覆盖范围的逻辑。

你有四个基础值:

imgWidth // your original img width
imgHeight

containerWidth // your container  width (here 698px)
containerHeight

这些值得出了两个比率:

imgRatio = (imgHeight / imgWidth)       // original img ratio
containerRatio = (containerHeight / containerWidth)     // container ratio

你想要找到两个新值:

finalWidth // the scaled img width
finalHeight
所以:
if (containerRatio > imgRatio) 
{
    finalHeight = containerHeight
    finalWidth = (containerHeight / imgRatio)
} 
else 
{
    finalWidth = containerWidth 
    finalHeight = (containerWidth / imgRatio)
}

...然后你就拥有了与background-size: cover相当的效果。


18
不确定为什么,但我不得不将最后一行更改为 finalHeight = (containerWidth * imgRatio) 才能使我的代码正常工作。 - Ergec
@Ergec,没错!imgRatioheight / width。我们需要将height / width乘以width来得到height。@mddw,请修正你的答案。 - George
这是官方实现还是逆向工程? - Qwerty
1
这个数学问题对我来说不起作用,Antriver的答案却有效。 - makeshyft_tom

16
我知道这是一个非常古老的问题,但是我写的答案实际上更干净,因为它在图像之间使用最大值和最小值比率而不是每个图像本身:
var originalRatios = {
  width: containerWidth / imageNaturalWidth,
  height: containerHeight / imageNaturalHeight
};

// formula for cover:
var coverRatio = Math.max(originalRatios.width, originalRatios.height); 

// result:
var newImageWidth = imageNaturalWidth * coverRatio;
var newImageHeight = imageNaturalHeight * coverRatio;

我喜欢这种方法,因为它非常系统化——也许这并不是一个正确的词语——我的意思是你可以摆脱if语句,并以更加"数学公式"的方式使其工作(即输入=输出,如果有意义的话):

var ratios = {
  cover: function(wRatio, hRatio) {
    return Math.max(wRatio, hRatio);
  },

  contain: function(wRatio, hRatio) {
    return Math.min(wRatio, hRatio);
  },

  // original size
  "auto": function() {
    return 1;
  },

  // stretch
  "100% 100%": function(wRatio, hRatio) {
    return { width:wRatio, height:hRatio };
  }
};

function getImageSize(options) {
  if(!ratios[options.size]) {
    throw new Error(options.size + " not found in ratios");
  }

  var r = ratios[options.size](
    options.container.width / options.image.width,
    options.container.height / options.image.height
  );

  return {
    width: options.image.width * (r.width || r),
    height: options.image.height * (r.height || r)
  };
}

使用方法

const { width, height } = getImageSize({
  container: {width: 100, height: 100},
  image: {width: 200, height: 50},
  size: 'cover' // 'contain' | 'auto' | '100% 100%'
});

操场

我在这里创建了一个jsbin,如果您想看看我的“系统化”是什么意思(它还具有一个scale方法,我认为在此答案中不需要,但对于一些其他事情非常有用)。


5

感谢mdi指引我正确的方向,但那似乎不太对。 这是对我有效的解决方案:

    $imgRatio = $imageHeight / $imageWidth;
    $canvasRatio = $canvasHeight / $canvasWidth;

    if ($canvasRatio > $imgRatio) {
        $finalHeight = $canvasHeight;
        $scale = $finalHeight / $imageHeight;
        $finalWidth = round($imageWidth * $scale , 0);
    } else {
        $finalWidth = $canvasWidth;
        $scale = $finalWidth / $imageWidth;
        $finalHeight = round($imageHeight * $scale , 0);
    }

3

在长时间搜索如何缩放和定位div上的背景图像以匹配html背景图像,同时支持浏览器调整大小和临时定位div后,我偶然发现了这个QA,并想出了以下解决方案。

background image of a div positioned to match html background

:root {
  /* background image size (source) */

  --bgw: 1920;
  --bgh: 1080;

  /* projected background image size and position */

  --bgscale: max(calc(100vh / var(--bgh)), calc(100vw / var(--bgw)));

  --pbgw: calc(var(--bgw) * var(--bgscale)); /* projected width */
  --pbgh: calc(var(--bgh) * var(--bgscale)); /* projected height */

  --bgLeftOverflow: calc((var(--pbgw) - 100vw) / 2); 
  --bgTopOverflow: calc((var(--pbgh) - 100vh) / 2);
}

JS 等效

window.onresize = () => {
  const vw100 = window.innerWidth
  const vh100 = window.innerHeight

  /* background image size (source) */

  const bgw = 1920
  const bgh = 1080

  /* projected background image size and position */

  const bgscale = Math.max(vh100 / bgh, vw100 / bgw)

  const projectedWidth  = bgw * bgscale | 0
  const projectedHeight = bgh * bgscale | 0

  const leftOverflow = (projectedWidth  - vw100) / 2 | 0
  const topOverflow  = (projectedHeight - vh100) / 2 | 0

  console.log(bgscale.toFixed(2), projectedWidth, projectedHeight, leftOverflow, topOverflow)
}

尝试使用此片段调整窗口大小以查看结果。
最好在全屏视图中查看。提示:打开控制台。

window.onresize = () => {
  const vw100 = window.innerWidth
  const vh100 = window.innerHeight
  const bgw = 1920
  const bgh = 1080

  const bgscale = Math.max(vh100 / bgh, vw100 / bgw)

  const projectedWidth = bgw * bgscale | 0
  const projectedHeight = bgh * bgscale | 0

  const leftOverflow = (projectedWidth - vw100) / 2 | 0
  const topOverflow = (projectedHeight - vh100) / 2 | 0

  console.log(bgscale.toFixed(2), projectedWidth, projectedHeight, leftOverflow, topOverflow)
}
:root {
  /* background image size */
  --bgurl: url('https://istack.dev59.com/3iy4y.webp');
  --bgw: 1000;
  --bgh: 600;
  
  --bgscale: max(calc(100vh / var(--bgh)), calc(100vw / var(--bgw)));
  
  --pbgw: calc(var(--bgw) * var(--bgscale));
  --pbgh: calc(var(--bgh) * var(--bgscale));
  
  --bgLeftOverflow: calc((var(--pbgw) - 100vw) / 2);
  --bgTopOverflow: calc((var(--pbgh) - 100vh) / 2);
}

html {
  background: #000 var(--bgurl) no-repeat center center fixed;
  background-size: cover;
  overflow: hidden;
}

#panel {
  --x: 100px;
  --y: 100px;
  --w: 200px;
  --h: 150px;
  
  position: absolute;
  left: var( --x);
  top: var( --y);
  width: var(--w);
  height: var(--h);
  
  background-image: var(--bgurl);
  background-repeat: no-repeat;
  background-position: calc(0px - var(--bgLeftOverflow) - var(--x)) calc(0px - var(--bgTopOverflow) - var(--y));
  background-size: calc(var( --bgscale) * var(--bgw));
  filter: invert(1);
}
<div id="panel"></div>


2

使用background-size: cover时,背景图像会被缩放到覆盖整个背景的最小尺寸。

因此,在宽度小于高度的情况下,将其缩放至宽度与区域相同。在高度大于宽度的情况下,将其缩放至高度与区域相同。

当背景图像大于要覆盖的区域时,缩小它直到适合大小(如果高度溢出的比较少,则缩放至相同的高度;如果宽度溢出的比较少,则缩放至相同的宽度)。


0
上述答案缺少一步。
步骤1. 根据画布或图像的纵横比例进行缩放。选择能够覆盖整个画布的最小尺寸。感谢anrtiver的建议。
步骤2. 裁剪图像,否则它将不会居中。
 function ImageCover($img1, $canvass_width, $canvass_height) {
    
      $img1_width = imagesx($img1);
      $img1_height = imagesy($img1);
      $img1_ratio = $img1_height / $img1_width;
      
      $canvass_ratio = $canvass_width / $canvass_height;
    
    //scale 
      if ($canvass_ratio < $img1_ratio) {
        $finalHeight = $canvass_height;
        $scale = $finalHeight / $img1_height;
        $finalWidth = $img1_width * $scale;
      } else {
        $finalWidth = $canvass_width;
        $scale = $finalWidth / $img1_width;
        $finalHeight = $img1_height * $scale;
      }
      
      $img1_scalled = imagescale($img1, $finalWidth, $finalHeight);
    
      $img1_scalled_width = imagesx($img1_scalled);
      $img1_scalled_height = imagesy($img1_scalled);
    
      $img1 = $img1_scalled;
      $img1_width = $img1_scalled_width;
      $img1_height = $img1_scalled_height;
    
    
    //crop
      $x = 0;
      $y = 0;
      if($img1_scalled_width > $canvass_width) {
        $difference_x = $img1_scalled_width - $canvass_width;
        $x = $difference_x / 2;
      } else {
        $difference_y = $img1_scalled_height - $canvass_height;   
        $y = ($difference_y / 2);
      }
    
    
    
      $img1 = imagecrop($img1, ['x' => $x, 'y' => $y, 'width' => $canvass_width, 'height' => $canvass_height]);
      
      return $img1;
    
    
    } //ImageCover

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