对象拟合:获取结果尺寸

object-fit: get resulting dimensions

本文关键字:结果 获取 拟合 对象      更新时间:2023-09-26

使用新的CSS功能object-fit时,如何访问浏览器通过JavaScript选择的结果维度?

因此,让我们假设foo.jpg是100x200像素。浏览器页面/视口宽400像素,高300像素。然后给出这个CSS代码:

img.foo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 25% 0;
}

浏览器现在会在最上面显示图像,正确的纵横比从左起第二个季度一直延伸到最下面。这导致了这些图像维度:

  • 宽度:150px
  • 高度:300px
  • 左:62.5px
  • 右:212.5px

那么,什么JavaScript调用(jQuery允许)会给我那些我手动计算的数字呢?(注意:JavaScript不知道CSS信息本身,因为用户可能会覆盖它们,甚至添加min-width之类的内容)

为了使用我创建的小提琴代码:https://jsfiddle.net/sydeo244/

多亏了@bfred,我不必制作初始方法。

这里是他的扩展(和重写)版本,它也计算object-position值。

function getRenderedSize(contains, cWidth, cHeight, width, height, pos){
  var oRatio = width / height,
      cRatio = cWidth / cHeight;
  return function() {
    if (contains ? (oRatio > cRatio) : (oRatio < cRatio)) {
      this.width = cWidth;
      this.height = cWidth / oRatio;
    } else {
      this.width = cHeight * oRatio;
      this.height = cHeight;
    }      
    this.left = (cWidth - this.width)*(pos/100);
    this.right = this.width + this.left;
    return this;
  }.call({});
}
function getImgSizeInfo(img) {
  var pos = window.getComputedStyle(img).getPropertyValue('object-position').split(' ');
  return getRenderedSize(true,
                         img.width,
                         img.height,
                         img.naturalWidth,
                         img.naturalHeight,
                         parseInt(pos[0]));
}
document.querySelector('#foo').addEventListener('load', function(e) {
  console.log(getImgSizeInfo(e.target));
});
#container {
  width: 400px;
  height: 300px;
  border: 1px solid blue;
}
#foo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: 25% 0;
}
<div id="container">
  <img id="foo" src="http://dummyimage.com/100x200/000/fff.jpg"/>
</div>

旁注

似乎object-position可以有两个以上的值,当需要调整(或添加)哪个参数返回左位置值时,

有一个名为intrinsic-scale的npm包可以为您计算,但它不支持等效的object-position:https://www.npmjs.com/package/intrinsic-scale

这是整个代码:

// adapted from: https://www.npmjs.com/package/intrinsic-scale
function getObjectFitSize(contains /* true = contain, false = cover */, containerWidth, containerHeight, width, height){
    var doRatio = width / height;
    var cRatio = containerWidth / containerHeight;
    var targetWidth = 0;
    var targetHeight = 0;
    var test = contains ? (doRatio > cRatio) : (doRatio < cRatio);
    if (test) {
        targetWidth = containerWidth;
        targetHeight = targetWidth / doRatio;
    } else {
        targetHeight = containerHeight;
        targetWidth = targetHeight * doRatio;
    }
    return {
        width: targetWidth,
        height: targetHeight,
        x: (containerWidth - targetWidth) / 2,
        y: (containerHeight - targetHeight) / 2
    };
}

其用法是:

getObjectFitSize(true, img.width, img.height, img.naturalWidth, img.naturalHeight);

这里有一个经过测试的更全面的算法,用于确定图像在屏幕上的显示方式。

var imageComputedStyle = window.getComputedStyle(image);
var imageObjectFit = imageComputedStyle.getPropertyValue("object-fit");
coordinates = {};
var imagePositions = imageComputedStyle.getPropertyValue("object-position").split(" ");
var horizontalPercentage = parseInt(imagePositions[0]) / 100;
var verticalPercentage = parseInt(imagePositions[1]) / 100;
var naturalRatio = image.naturalWidth / image.naturalHeight;
var visibleRatio = image.width / image.height;
if (imageObjectFit === "none")
{
  coordinates.sourceWidth = image.width;
  coordinates.sourceHeight = image.height;
  coordinates.sourceX = (image.naturalWidth - image.width) * horizontalPercentage;
  coordinates.sourceY = (image.naturalHeight - image.height) * verticalPercentage;
  coordinates.destinationWidthPercentage = 1;
  coordinates.destinationHeightPercentage = 1;
  coordinates.destinationXPercentage = 0;
  coordinates.destinationYPercentage = 0;
}
else if (imageObjectFit === "contain" || imageObjectFit === "scale-down")
{
  // TODO: handle the "scale-down" appropriately, once its meaning will be clear
  coordinates.sourceWidth = image.naturalWidth;
  coordinates.sourceHeight = image.naturalHeight;
  coordinates.sourceX = 0;
  coordinates.sourceY = 0;
  if (naturalRatio > visibleRatio)
  {
    coordinates.destinationWidthPercentage = 1;
    coordinates.destinationHeightPercentage = (image.naturalHeight / image.height) / (image.naturalWidth / image.width);
    coordinates.destinationXPercentage = 0;
    coordinates.destinationYPercentage = (1 - coordinates.destinationHeightPercentage) * verticalPercentage;
  }
  else
  {
    coordinates.destinationWidthPercentage = (image.naturalWidth / image.width) / (image.naturalHeight / image.height);
    coordinates.destinationHeightPercentage = 1;
    coordinates.destinationXPercentage = (1 - coordinates.destinationWidthPercentage) * horizontalPercentage;
    coordinates.destinationYPercentage = 0;
  }
}
else if (imageObjectFit === "cover")
{
  if (naturalRatio > visibleRatio)
  {
    coordinates.sourceWidth = image.naturalHeight * visibleRatio;
    coordinates.sourceHeight = image.naturalHeight;
    coordinates.sourceX = (image.naturalWidth - coordinates.sourceWidth) * horizontalPercentage;
    coordinates.sourceY = 0;
  }
  else
  {
    coordinates.sourceWidth = image.naturalWidth;
    coordinates.sourceHeight = image.naturalWidth / visibleRatio;
    coordinates.sourceX = 0;
    coordinates.sourceY = (image.naturalHeight - coordinates.sourceHeight) * verticalPercentage;
  }
  coordinates.destinationWidthPercentage = 1;
  coordinates.destinationHeightPercentage = 1;
  coordinates.destinationXPercentage = 0;
  coordinates.destinationYPercentage = 0;
}
else
{
  if (imageObjectFit !== "fill")
  {
    console.error("unexpected 'object-fit' attribute with value '" + imageObjectFit + "' relative to");
  }
  coordinates.sourceWidth = image.naturalWidth;
  coordinates.sourceHeight = image.naturalHeight;
  coordinates.sourceX = 0;
  coordinates.sourceY = 0;
  coordinates.destinationWidthPercentage = 1;
  coordinates.destinationHeightPercentage = 1;
  coordinates.destinationXPercentage = 0;
  coordinates.destinationYPercentage = 0;
}

其中image是HTML <img>元素,coordinates包含以下属性,假设我们认为sourceFrame是图像完全打印时定义的矩形,即其自然尺寸,printFrame是实际显示的区域,即printFrame.width = image.widthprintFrame.height = image.height:

  • CCD_ 15:CCD_
  • CCD_ 17:CCD_
  • CCD_ 19:CCD_
  • CCD_ 21:CCD_
  • destinationXPercentage:将打印图像的printFrame上左上点的水平位置相对于printFrame宽度的百分比
  • destinationYPercentage:将打印图像的printFrame上左上点的垂直位置相对于printFrame高度的百分比
  • destinationWidthPercentage:将在其上打印图像的printFrame宽度相对于printFrame宽度的百分比
  • destinationHeightPercentage:将在其上打印图像的printFrame高度相对于printFrame高度的百分比

很抱歉,scale-down案件没有得到处理,因为它的定义不太清楚。

下面是一段更新的TypeScript代码,它处理所有值,包括object-fit: scale-downobject-position,它们都具有相对值、绝对值和关键字值:

type Rect = {
  x: number;
  y: number;
  width: number;
  height: number;
};
const dom2rect = (rect: DOMRect): Rect => {
  const { x, y, width, height } = rect;
  return { x, y, width, height };
};
const intersectRects = (a: Rect, b: Rect): Rect | null => {
  const x = Math.max(a.x, b.x);
  const y = Math.max(a.y, b.y);
  const width = Math.min(a.x + a.width, b.x + b.width) - x;
  const height = Math.min(a.y + a.height, b.y + b.height) - y;
  if (width <= 0 || height <= 0) return null;
  return { x, y, width, height };
};
type ObjectRects = {
  container: Rect; // client-space size of container element
  content: Rect; // natural size of content
  positioned: Rect; // scaled rect of content relative to container element (may overlap out of container)
  visible: Rect | null; // intersection of container & positioned rect
};
const parsePos = (str: string, ref: number): number => {
  switch (str) {
    case "left":
    case "top":
      return 0;
    case "center":
      return ref / 2;
    case "right":
    case "bottom":
      return ref;
    default:
      const num = parseFloat(str);
      if (str.endsWith("%")) return (num / 100) * ref;
      else if (str.endsWith("px")) return num;
      else
        throw new Error(`unexpected unit object-position unit/value: '${str}'`);
  }
};
const getObjectRects = (
  image: HTMLImageElement | HTMLVideoElement
): ObjectRects => {
  const style = window.getComputedStyle(image);
  const objectFit = style.getPropertyValue("object-fit");
  const naturalWidth =
    image instanceof HTMLImageElement ? image.naturalWidth : image.videoWidth;
  const naturalHeight =
    image instanceof HTMLImageElement ? image.naturalHeight : image.videoHeight;
  const content = { x: 0, y: 0, width: naturalWidth, height: naturalHeight };
  const container = dom2rect(image.getBoundingClientRect());
  let scaleX = 1;
  let scaleY = 1;
  switch (objectFit) {
    case "none":
      break;
    case "fill":
      scaleX = container.width / naturalWidth;
      scaleY = container.height / naturalHeight;
      break;
    case "contain":
    case "scale-down": {
      let scale = Math.min(
        container.width / naturalWidth,
        container.height / naturalHeight
      );
      if (objectFit === "scale-down") scale = Math.min(1, scale);
      scaleX = scale;
      scaleY = scale;
      break;
    }
    case "cover": {
      const scale = Math.max(
        container.width / naturalWidth,
        container.height / naturalHeight
      );
      scaleX = scale;
      scaleY = scale;
      break;
    }
    default:
      throw new Error(`unexpected 'object-fit' value ${objectFit}`);
  }
  const positioned = {
    x: 0,
    y: 0,
    width: naturalWidth * scaleX,
    height: naturalHeight * scaleY,
  };
  const objectPos = style.getPropertyValue("object-position").split(" ");
  positioned.x = parsePos(objectPos[0], container.width - positioned.width);
  positioned.y = parsePos(objectPos[1], container.height - positioned.height);
  const containerInner = { x: 0, y: 0, width: container.width, height: container.height };
  return {
    container,
    content,
    positioned,
    visible: intersectRects(containerInner, positioned),
  };
};

您可以调整返回值以仅输出您需要的内容。