Использование object-fit в <div> с дочерними элементами, включая <canvas>
У меня есть внешний контейнер, который является переменным по размеру и ширине. Предположим, что внутри этого контейнера у меня есть холст, который я хочу вырастить как можно больше, сохраняя пропорции и не обрезая. Для этого я обычно использовал бы object-fit: contain
.
Теперь предположим вместо просто холста, у меня есть холст с другим элементом, расположенным рядом с ним.
HTML:
<div class="outerContainer">
<canvas width="640" height="360"></canvas>
<div class="beside">
</div>
</div>
CSS
.outerContainer {
display: flex;
border: 0.5em solid #444;
margin-bottom: 2em;
object-fit: contain;
}
.outerContainer canvas {
flex-grow: 1;
background: #77a;
}
/* This element has a fixed width, and should be whatever height the <canvas> is */
.outerContainer .beside {
flex-basis: 3em;
flex-grow: 0;
flex-shrink: 0;
background: #7a7;
}
В этом случае я хочу масштабировать весь внешний размер контейнера, как и с холстом. Проблема в том, что object-fit
фактически не масштабирует элемент... он масштабирует его содержимое. Это, похоже, не относится к обычным элементам блока, что приводит к тому, что внутри холста потенциально перекошен, если имеется достаточная ширина.
![Плохо перекошенный холст]()
Если я добавлю object-fit: contain
в элемент canvas
, он будет поддерживать пропорцию, но по-прежнему использует полную ширину, то есть элемент .beside
находится полностью вправо. Это визуализируется фиолетовым фоном на холсте.
![Не перекошено, но не правильно]()
Я бы хотел, чтобы outerContainer
масштабировался с содержимым холста, так что .beside
всегда имеет высоту содержимого холста. .outerContainer
должен быть центрирован в родительском элементе, занимая столько места, сколько может, не искажая холст. Подобным образом, в любом пропорциональном масштабе:
![Желаемое изображение]()
Это можно сделать с помощью современного CSS? Или я должен использовать скриптовое решение?
Скрипт с примерами: https://jsfiddle.net/nufx10zc/
Ответы
Ответ 1
Я не знаю, хорошо ли это для вас, но я думаю, что это можно сделать в основном в css, если вы разрешите немного больше HTML.
Рассмотрим следующий html
<div class="outerContainer">
<div class="canvasContainer">
<canvas width="640" height="360"></canvas>
</div>
<div class="beside">
</div>
</div>
Я просто добавил немного div вокруг холста. Таким образом мы позволяем холсту обрабатывать его, и мы используем div для гибких вещей.
Используя следующий css:
* {
box-sizing: border-box;
}
.outerContainer {
display: flex;
border: 0.5em solid #444;
margin-bottom: 2em;
}
.canvasContainer canvas {
width: 100%;
background: #777;
margin-bottom: -4px
}
.canvasContainer {
flex-grow: 1;
background: #77a;
}
/* This element has a fixed width, and should be whatever height the <canvas> is */
.outerContainer .beside {
flex-basis: 3em;
flex-grow: 0;
flex-shrink: 0;
background: #7a7;
}
У нас есть контейнер для холста, который занимает все свободное пространство. И тогда холст адаптируется соответственно с масштабированием изображения в нем.
Однако я не знаю, почему, на нижней части холста была небольшая разница, поэтому отрицательный запас на нем.
скрипт: https://jsfiddle.net/L8p6xghb/3/
В качестве побочного примечания, если вы хотите использовать это для титров, для него есть элемент html5, например <figure>
и <figcaption>
!
Ответ 2
Я думаю, что чистое решение CSS немного надуманно с учетом необходимого вам решения - и вот предложение с использованием script, которое включает в себя следующие шаги:
-
Найдите соотношение сторон изображения, которое наилучшим образом соответствует доступному пространству для canvas
-
Обновить width
для canvas
и flexbox
height.
-
Нарисуйте изображение на холсте.
Обратите внимание, что для иллюстрации, при изменении размера окна я перезагрузил фрейм - подробнее см. демо ниже (дополнительные пояснения приведены в строке):
// Document.ready
$(() => {
putImageOnCanvas();
});
// Window resize event
((() => {
window.addEventListener("resize", resizeThrottler, false);
var resizeTimeout;
function resizeThrottler() {
if (!resizeTimeout) {
resizeTimeout = setTimeout(function() {
resizeTimeout = null;
actualResizeHandler();
}, 66);
}
}
function actualResizeHandler() {
// handle the resize event - reloading page for illustration
window.location.reload();
}
})());
function putImageOnCanvas() {
$('.outerContainer canvas').each((index, canvas) => {
const ctx = canvas.getContext('2d');
canvas.width = $(canvas).innerWidth();
canvas.height = $(canvas).innerHeight();
const img = new Image;
img.src = 'https://static1.squarespace.com/static/56a1d17905caa7ee9f27e273/t/56a1d56617e4f1177a27178d/1453446712144/Picture7.png';
img.onload = (() => {
// find the aspect ratio that fits the container
let ratio = Math.min(canvas.width / img.width, canvas.height / img.height);
let centerShift_x = (canvas.width - img.width * ratio) / 2;
let centerShift_y = (canvas.height - img.height * ratio) / 2;
canvas.width -= 2 * centerShift_x;
canvas.height -= 2 * centerShift_y;
// reset the flexbox height and canvas flex-basis (adjusting for the 0.5em border too)
$('.outerContainer').css({
'height': 'calc(' + canvas.height + 'px + 1em)'
});
$('.outerContainer canvas').css({
'width': 'calc(' + canvas.width + 'px)'
});
// draw the image in the canvas now
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img.width * ratio, img.height * ratio);
});
});
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.outerContainer {
display: flex;
border: 0.5em solid #444;
margin-bottom: 2em;
/*max available flexbox height*/
height: calc(100vh - 2em);
}
.outerContainer canvas {
background: #77a;
/*max available canvas width*/
width: calc(100vw - 4em);
}
.outerContainer .beside {
flex-basis: 3em;
flex-grow: 0;
flex-shrink: 0;
background: #7a7;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="outerContainer">
<canvas></canvas>
<div class="beside">
</div>
</div>