Использование подгонки объекта в <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;
}

В этом случае я хочу масштабировать весь размер externalContainer, как я делал это с canvas. Проблема в том, что object-fit на самом деле не масштабирует элемент... он масштабирует его содержимое. Похоже, что это не относится к нормальным элементам блока, что приводит к тому, что холст внутри может быть перекошен при достаточной ширине.

Плохо перекошенный холст

Если я добавлю object-fit: contain к canvas элемент, он сохраняет пропорции, но по-прежнему использует всю ширину, то есть .beside Элемент полностью направо. Это визуализируется на фиолетовом фоне на холсте.

Не перекошено, но не правильно

То, что я хотел бы, это outerContainer масштабироваться с содержанием холста, так что .beside всегда имеет высоту содержимого холста. .outerContainer должен быть центрирован в родительском элементе, занимая как можно больше места, не искажая холста. Вот так, в любой пропорциональной шкале:

Желаемое изображение

Это выполнимо с современным CSS? Или я должен использовать решение на основе сценариев?

Возиться с примерами: https://jsfiddle.net/nufx10zc/

2 ответа

Я не знаю, нормально ли это для вас, но я думаю, что это можно сделать в основном в 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>!

Я думаю, что чистое решение CSS немного надумано, учитывая решение, которое вам требуется - и вот предложение с использованием скрипта, которое включает в себя следующие шаги:

  1. Найти соотношение сторон изображения, которое наилучшим образом соответствует доступному пространству для canvas

  2. Обновить width за canvas и flexbox высота

  3. Нарисуйте изображение сейчас на холсте.

Обратите внимание, что для иллюстрации при изменении размера окна я перезагрузил фрейм - подробности см. В демоверсии ниже (дополнительные пояснения приведены в строке):

// 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>

РЕДАКТИРОВАТЬ:

  1. Я устанавливал flex-basis за canvas Первоначально - но Firefox не вел себя хорошо - так что теперь я использую width вместо.

  2. У Snippet есть некоторые проблемы с изменением размера экрана (перезагрузка страницы) в Firefox, поэтому также включили скрипку.

UPDATED FIDDLE

Другие вопросы по тегам