如何从 HTML 视频元素获取视频的渲染大小

问题描述 投票:0回答:3

如何获取使用 HTML5 <video> 元素显示的视频的

rendered
尺寸?

videoWidth
videoHeight
属性,建议作为其他类似(但不同!)问题的解决方案,返回视频的内在宽度和高度,因此它们不是解决方案。

width
height
属性应该返回显示区域的宽度和高度,由于某种原因返回0。

const video = document.querySelector("video");
console.log(video.width); //0
console.log(video.height); //0
<video src="https://upload.wikimedia.org/wikipedia/commons/4/48/Fine-tuned-Bee-Flower-Coevolutionary-State-Hidden-within-Multiple-Pollination-Interactions-srep03988-s6.ogv" controls></video>

有什么建议吗?

更新

当前答案建议使用

getBoundingClientRect()
clientWidth
clientHeight
。据我了解,问题是这些返回的是
<video>
元素的大小,而不是屏幕上渲染的实际视频像素的大小。

例如,假设视频的 CSS

height
属性设置为
100%
,并且其
max-width
设置为
100%
。该视频位于
height
1000px
的 div 内。
getBoundingClientRect().height
clientHeight
返回的值都是div的大小(
1000px
),而不是视频的实际高度:

const video = document.querySelector("video");
video.addEventListener('loadedmetadata', () => {
  const rect = video.getBoundingClientRect();
  console.log(rect.height); // ~1000
  console.log(video.clientHeight); // 1000
});
#container {
  outline: 3px solid red;
  height: 1000px;
}

video {
  height: 100%;
  max-width: 100%;
}
<div id="container">
  <video src="https://upload.wikimedia.org/wikipedia/commons/4/48/Fine-tuned-Bee-Flower-Coevolutionary-State-Hidden-within-Multiple-Pollination-Interactions-srep03988-s6.ogv" controls></video>
</div>

javascript html canvas video
3个回答
1
投票

“如何获取 HTML5 元素的渲染大小?”

您可以尝试使用 clientWidthclientHeight 来获取屏幕显示尺寸。
优点是您不需要等待任何元数据加载事件。
它已经在那里了。

为了进行测试,您可以向视频标签添加

width=""
属性,并在渲染的宽度/高度也发生更改时查看 clientWidth / clientHeight 是否执行您想要的操作。

请确认这是否符合您的预期结果..?

const video = document.querySelector("video");
console.log("video DISPLAY SIZE:");
console.log("clientWidth: " + video.clientWidth); //300
console.log("clientHeight: " + video.clientHeight); //150
<video width="300" src="https://upload.wikimedia.org/wikipedia/commons/4/48/Fine-tuned-Bee-Flower-Coevolutionary-State-Hidden-within-Multiple-Pollination-Interactions-srep03988-s6.ogv" controls></video>


1
投票

您可以收听 loadedmetadata

HTMLMediaElement

事件

const video = document.querySelector("video");
video.addEventListener('loadedmetadata', () => {
  const rect = video.getBoundingClientRect();
  console.log(rect.width, rect.height);
  console.log(video.videoWidth, video.videoHeight);
  console.log(video.clientWidth, video.clientHeight);
});
<video src="https://upload.wikimedia.org/wikipedia/commons/4/48/Fine-tuned-Bee-Flower-Coevolutionary-State-Hidden-within-Multiple-Pollination-Interactions-srep03988-s6.ogv" controls></video>

在此示例中,

getBoundingClientRect()
将考虑转换、填充。
clientWidth/Height
不会考虑变换

const video = document.querySelector("video");
video.addEventListener('loadedmetadata', () => {
  const rect = video.getBoundingClientRect();
  console.log(rect.width, rect.height);
  console.log(video.videoWidth, video.videoHeight);
  console.log(video.clientWidth, video.clientHeight);
});
video {
  border: 5px solid red;
  padding: 5px;
  transform: scale(0.5); /* 0.5 times */
}
<video src="https://upload.wikimedia.org/wikipedia/commons/4/48/Fine-tuned-Bee-Flower-Coevolutionary-State-Hidden-within-Multiple-Pollination-Interactions-srep03988-s6.ogv" controls></video>

这就是

getBoundingClientRect()
给出的
250 = (480 x 比例(0.5)) + 左内边距(5) + 右内边距(5)
146 = (272 x 比例(0.5)) + 顶部填充(5) + 底部填充(5)


0
投票

其他答案是正确的,您关于

videoWidth
videoHeight
返回
0
的最初问题是由于当您调用吸气剂时媒体尚未加载。您至少需要等待 loadedmetadata 事件才能访问这些值。

然后,实际视频内容的渲染大小将取决于其

<video>
容器的大小及其
object-fit
object-position
规则。
没有内置方法可以获取此信息,因此您必须自己编写并处理这些值的所有组合。对于另一个相关的问答,我编写了一个代码,它可以检索鼠标光标相对于视频帧坐标系的位置。我们可以重用其中的一些来获取渲染视频相对于
<video>
元素的边界框。
请注意,在下面的示例中,我使用
<canvas>
元素,因为它更容易操纵其固有大小,但
<video>
的工作原理相同。

// Some helpers
/**
 * Returns the intrinsic* width & height of most media sources in the Web API
 * (at least the closest we can get to it)
 */
function getResourceDimensions(source) {
  if (source.videoWidth) {
    return { width: source.videoWidth, height: source.videoHeight };
  }
  if (source.naturalWidth) {
    return { width: source.naturalWidth, height: source.naturalHeight };
  }
  if (source.width) {
    return { width: source.width, height: source.height };
  }
  return null;
}
function isRelative(length) {
  return length?.match?.(/%$/);
}
/**
 * Parses the component values of "object-position"
 * Returns the position in px
 */
function parsePositionAsPx(str, bboxSize, objectSize) {
  const num = parseFloat(str);
  if (isRelative(str)) {
    const ratio = num / 100;
    return (bboxSize * ratio) - (objectSize * ratio);
  }
  return num;
}
function parseObjectPosition(position, bbox, object) {
  const [left, top] = position.split(" ");
  return {
    left: parsePositionAsPx(left, bbox.width, object.width),
    top:  parsePositionAsPx(top, bbox.height, object.height)
   };
}

/**
 * Returns the BBox of the rendered object content 
 * relative to the element's box.
 */
function getRenderedBox(elem) {
  let { objectFit, objectPosition } = getComputedStyle(elem);
  const bbox   = elem.getBoundingClientRect();
  const object = getResourceDimensions(elem);
  
  if (objectFit === "scale-down") {
    objectFit = (bbox.width < object.width || bbox.height < object.height)
      ? "contain" : "none";
  }
  if (objectFit === "none") {
    const { left, top } = parseObjectPosition(objectPosition, bbox, object);
    return { left, top, ...object }
  }
  if (objectFit === "contain") {
    const objectRatio = object.height / object.width;
    const bboxRatio   = bbox.height / bbox.width;
    const width  = bboxRatio > objectRatio ? bbox.width : bbox.height / objectRatio;
    const height = bboxRatio > objectRatio ? bbox.width * objectRatio : bbox.height;
    const {left, top} = parseObjectPosition(objectPosition, bbox, {width, height});
    return { left, top, width, height };
  }
  if (objectFit === "fill") {
    // Relative positioning is discarded with `object-fit: fill`,
    // so we need to check here if it's relative or not.
    const [posLeft, posTop] = objectPosition.split(" ");
    const {left, top} = parseObjectPosition(objectPosition, bbox, object);
    return {
      left: isRelative(posLeft) ? 0 : left,
      top: isRelative(posTop) ? 0 : top,
      width: bbox.width,
      height: bbox.height,
    };
  }
  if (objectFit === "cover") {
    const minRatio = Math.min(bbox.width / object.width, bbox.height / object.height);
    let width  = object.width  * minRatio;
    let height = object.height * minRatio;
    let outRatio  = 1;
    if (width < bbox.width) {
      outRatio = bbox.width / width;
    }
    if (Math.abs(outRatio - 1) < 1e-14 && height < bbox.height) {
      outRatio = bbox.height / height;
    }
    width  *= outRatio;
    height *= outRatio;
    
    const { left, top } = parseObjectPosition(objectPosition, bbox, {width, height});
    return { left, top, width, height };
  }
};
//----------------------------------------------------------------
// Usage example - Move a box around the rendered area
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
function init() {
  ctx.fillStyle = ctx.createRadialGradient(canvas.width/2, canvas.height/2, 0, canvas.width/2, canvas.height/2, Math.hypot(canvas.width/2, canvas.height/2));
  ctx.fillStyle.addColorStop(0, "red");
  ctx.fillStyle.addColorStop(1, "green");
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function updateWitness() {
  /**
   * `getRenderedBox()` returns the position relative to the source element's box
   * Since our witness element is absolutely positionned, relative to the <body>
   * we need to add the source element's offsetLeft and offsetTop to the returned values.
   */
  const { width, height, ...relativeToElem } = getRenderedBox(canvas);
  const bbox = canvas.getBoundingClientRect();
  const left = relativeToElem.left + canvas.offsetLeft;
  const top  = relativeToElem.top  + canvas.offsetTop;

  const witness = document.querySelector(".witness");
  witness.style.setProperty("left",   `${left}px`);
  witness.style.setProperty("top",    `${top}px`);
  witness.style.setProperty("width",  `${width}px`);
  witness.style.setProperty("height", `${height}px`);
};
const observer = new ResizeObserver(([entry]) => {
  updateWitness();
});
observer.observe(canvas);

// To update our test settings
document.getElementById("object_fit_select").onchange = (evt) => {
  canvas.style.setProperty("--object-fit", evt.target.value);
  updateWitness();
};
document.getElementById("object_position_x").oninput = (evt) => {
  canvas.style.setProperty("--object-position-x", evt.target.value);
  updateWitness();
};
document.getElementById("object_position_y").oninput = (evt) => {
  canvas.style.setProperty("--object-position-y", evt.target.value);
  updateWitness();
};
document.getElementById("canvas_width").oninput = (evt) => {
  canvas.width = evt.target.value;
  init();
  updateWitness();
};
document.getElementById("canvas_height").oninput = (evt) => {
  canvas.height = evt.target.value;
  init();
  updateWitness();
};
init();
canvas {
  --object-fit: contain;
  --object-position-x: center;
  --object-position-y: center;
  width: 500px;
  height: 500px;
  object-fit: var(--object-fit);
  object-position: var(--object-position-x) var(--object-position-y);
  outline: 1px solid;
  width: 100%;
  height: 100%;
  background: pink;
}
.resizable {
  resize: both;
  overflow: hidden;
  width: 500px;
  height: 500px;
  outline: 1px solid;
}
.witness {
  position: absolute;
  outline: 2px solid red;
  pointer-events: none;
}
<div class="witness"></div>
<label>object-fit: <select id="object_fit_select">
  <option>contain</option>
  <option>cover</option>
  <option>fill</option>
  <option>none</option>
  <option>scale-down</option>
  </select></label><br>
<label>x-position: <input id="object_position_x" value="center"></label><br>
<label>y-position: <input id="object_position_y" value="center"></label><br>
<label>canvas width: <input id="canvas_width" value="600"></label><br>
<label>canvas height: <input id="canvas_height" value="150"></label><br>

<div class="resizable">
  <canvas id="canvas" width="600" height="150"></canvas>
</div>

这是一个

<video>
元素:

// Some helpers
/**
 * Returns the intrinsic* width & height of most media sources in the Web API
 * (at least the closest we can get to it)
 */
function getResourceDimensions(source) {
  if (source.videoWidth) {
    return { width: source.videoWidth, height: source.videoHeight };
  }
  if (source.naturalWidth) {
    return { width: source.naturalWidth, height: source.naturalHeight };
  }
  if (source.width) {
    return { width: source.width, height: source.height };
  }
  return null;
}
function isRelative(length) {
  return length?.match?.(/%$/);
}
/**
 * Parses the component values of "object-position"
 * Returns the position in px
 */
function parsePositionAsPx(str, bboxSize, objectSize) {
  const num = parseFloat(str);
  if (isRelative(str)) {
    const ratio = num / 100;
    return (bboxSize * ratio) - (objectSize * ratio);
  }
  return num;
}
function parseObjectPosition(position, bbox, object) {
  const [left, top] = position.split(" ");
  return {
    left: parsePositionAsPx(left, bbox.width, object.width),
    top:  parsePositionAsPx(top, bbox.height, object.height)
   };
}

/**
 * Returns the BBox of the rendered object content 
 * relative to the element's box.
 */
function getRenderedBox(elem) {
  let { objectFit, objectPosition } = getComputedStyle(elem);
  const bbox   = elem.getBoundingClientRect();
  const object = getResourceDimensions(elem);
  
  if (objectFit === "scale-down") {
    objectFit = (bbox.width < object.width || bbox.height < object.height)
      ? "contain" : "none";
  }
  if (objectFit === "none") {
    const { left, top } = parseObjectPosition(objectPosition, bbox, object);
    return { left, top, ...object }
  }
  if (objectFit === "contain") {
    const objectRatio = object.height / object.width;
    const bboxRatio   = bbox.height / bbox.width;
    const width  = bboxRatio > objectRatio ? bbox.width : bbox.height / objectRatio;
    const height = bboxRatio > objectRatio ? bbox.width * objectRatio : bbox.height;
    const {left, top} = parseObjectPosition(objectPosition, bbox, {width, height});
    return { left, top, width, height };
  }
  if (objectFit === "fill") {
    // Relative positioning is discarded with `object-fit: fill`,
    // so we need to check here if it's relative or not
    const [posLeft, posTop] = objectPosition.split(" ");
    const {left, top} = parseObjectPosition(objectPosition, bbox, object);
    return {
      left: isRelative(posLeft) ? 0 : left,
      top: isRelative(posTop) ? 0 : top,
      width: bbox.width,
      height: bbox.height,
    };
  }
  if (objectFit === "cover") {
    const minRatio = Math.min(bbox.width / object.width, bbox.height / object.height);
    let width  = object.width  * minRatio;
    let height = object.height * minRatio;
    let outRatio  = 1;
    if (width < bbox.width) {
      outRatio = bbox.width / width;
    }
    if (Math.abs(outRatio - 1) < 1e-14 && height < bbox.height) {
      outRatio = bbox.height / height;
    }
    width  *= outRatio;
    height *= outRatio;
    
    const { left, top } = parseObjectPosition(objectPosition, bbox, {width, height});
    return { left, top, width, height };
  }
};
//----------------------------------------------------------------
// Usage example - Move a box around the rendered area
const video = document.querySelector("video");

function updateWitness() {
  /**
   * `getRenderedBox()` returns the position relative to the source element's box
   * Since our witness element is absolutely positionned, relative to the <body>
   * we need to add the source element's offsetLeft and offsetTop to the returned values.
   */
  const { width, height, ...relativeToElem } = getRenderedBox(video);
  const bbox = video.getBoundingClientRect();
  const left = relativeToElem.left + video.offsetLeft;
  const top  = relativeToElem.top  + video.offsetTop;

  const witness = document.querySelector(".witness");
  witness.style.setProperty("left",   `${left}px`);
  witness.style.setProperty("top",    `${top}px`);
  witness.style.setProperty("width",  `${width}px`);
  witness.style.setProperty("height", `${height}px`);
};
const observer = new ResizeObserver(([entry]) => {
  updateWitness();
});
video.addEventListener("loadedmetadata", () => observer.observe(video), { once: true });

// To update our test settings
document.getElementById("object_fit_select").onchange = (evt) => {
  video.style.setProperty("--object-fit", evt.target.value);
  updateWitness();
};
document.getElementById("object_position_x").oninput = (evt) => {
  video.style.setProperty("--object-position-x", evt.target.value);
  updateWitness();
};
document.getElementById("object_position_y").oninput = (evt) => {
  video.style.setProperty("--object-position-y", evt.target.value);
  updateWitness();
};
video {
  --object-fit: contain;
  --object-position-x: center;
  --object-position-y: center;
  width: 500px;
  height: 500px;
  object-fit: var(--object-fit);
  object-position: var(--object-position-x) var(--object-position-y);
  outline: 1px solid;
  width: 100%;
  height: 100%;
  background: pink;
}
.resizable {
  resize: both;
  overflow: hidden;
  width: 500px;
  height: 500px;
  outline: 1px solid;
}
.witness {
  position: absolute;
  outline: 2px solid red;
  pointer-events: none;
}
<div class="witness"></div>
<label>object-fit: <select id="object_fit_select">
  <option>contain</option>
  <option>cover</option>
  <option>fill</option>
  <option>none</option>
  <option>scale-down</option>
  </select></label><br>
<label>x-position: <input id="object_position_x" value="center"></label><br>
<label>y-position: <input id="object_position_y" value="center"></label><br>

<div class="resizable">
  <video
    muted autoplay controls loop
    src="https://upload.wikimedia.org/wikipedia/commons/4/48/Fine-tuned-Bee-Flower-Coevolutionary-State-Hidden-within-Multiple-Pollination-Interactions-srep03988-s6.ogv"
    ></video>
</div>

© www.soinside.com 2019 - 2024. All rights reserved.