如何获取使用 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>
“如何获取 HTML5 元素的渲染大小?”
您可以尝试使用 clientWidth 和 clientHeight 来获取屏幕显示尺寸。
优点是您不需要等待任何元数据加载事件。
它已经在那里了。
为了进行测试,您可以向视频标签添加
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>
您可以收听 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()
给出的其他答案是正确的,您关于
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>