0

I want to create a "dashboard" page that uses all the available space in the browser window. The browser window will be divided into two equal-width sides. The right side space will contain a slideshow. Images that are larger than the available space should be scaled down to fit and be centered within the space.

As illustrated in the snippet, the webpage is styled to prevent body scrolling. The body contains a two-column gridbox whose dimensions are 1fr & 1fr. The righthand griditem <right-panel> is a flexbox. This in turn contains a flexbox <picture-box> containing an <img>.

Be sure to click "Full-page" link for snippet and enlarge browser window if necessary.

I can't figure out what I need to do to allow the <picture-box> to expand to the size of <right-panel>. The image does shrink correctly in both height and width to fit when the picture box dimensions are specified in absolute units.

/* Slideshow generates rectangles of various dimensions */
const duration = 1500; // time (msec) to display each slide
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  [4000, 500, "Oversize horizontal"],
  [1000, 4000, "Oversize vertical"],
  [400, 300, "Should fit"],
  [100, 200, "Very small"],
  [4000, 4000, "Oversize square"]
];

async function show_slide_test(duration, min = 0, max = 0) {
  let n = 0;
  const my_img = document.querySelector('#slide-div-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];
    let desc = sizes[size_index][2];
    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=${desc}\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  width: 100%;
}

body {
  outline: 4px dashed red;
  outline-offset: -4px;
  height: 100%;
  /* limit to height of html */
  width: 100%;
  margin: 0;
  /* prevent body from shifting */
  padding: 0;
  overflow: hidden;
  /* prevents any body scroll */
}

/* Divide body into a grid of 2 columns */
panels {
  background: blue;
  min-height: 100%;
  display: grid;
  grid-template-columns: 50fr 50fr;
  gap: 2em;
}

/* Left-hand panel */
left-panel {
  background: pink;
}

/* Right-hand panel */
right-panel {
  background: yellow;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* Flex item inside right panel flexbox */
picture-box {
  background: green;
  display: flex;
  justify-content: center;
  align-items: center;
  /* what magic values will make picture box grow to size of right-panel? */
  width: 600px;
  height: 400px;
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  max-height: 100%;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

<body onload="show_slide_test(duration)">
  <panels>
    <left-panel></left-panel>
    <right-panel>
      <picture-box>
      <!-- This is a placeholder for the image -->
      <img class="scaled-picture"
                 id="slide-div-img">
      </picture-box>
    </right-panel>
  </panels>
</body>

</html>

Update In order to present MRE's in my questions, I tried to break my dashboard issues into two smaller questions. The earlier left panel issues were solved here, and I naively assumed that this question, concerning a slideshow in the right panel, could be solved independently and then added to the other one. Not so. Therefore I'm going to have to provide another snippet that includes the details of the left panel.

The right panel still fails to shrink large images sufficiently for both width and height to fit within the space. Instead, it merely accommodates the width, allowing tall images to extend vertically below the bottom edge of the panel. I don't know which of the two approaches used in the answers is "better"; they both work for my original MRE, but not in the larger context of my dashboard.

/* This function populates the scrollable-container grid with variable
       numbers of rows of dummy data */
let my_limit;
const my_perday = 3;

function load_scrollable_table(limit) {
  my_limit = limit;
  if (my_limit < 1) {
    return;
  }
  const my_div = document.querySelector("#whatsnew");
  for (let day = 1;; day++) {
    if (!insert_row(my_div, `3/${day}/2025`, 'Foobar etc.')) {
      return;
    }
    for (let thisday = 1; thisday < my_perday; ++thisday) {
      let v = "foo ".repeat(thisday * 4);
      if (!insert_row(my_div, '', 'Foobar more etc.' + v)) {
        return;
      }
    }
  }
}

function insert_row(my_div, col1, col2) {
  let my_row = `<scrollable-table-col1>${col1}</scrollable-table-col1>
<scrollable-table-col2>${col2}</scrollable-table-col2>`;
  my_div.innerHTML += my_row;
  return --my_limit;
}

/*
 * Slideshow_test generates a sequence of five images that are
 * inserted into the right panel, scaled proportionally to fit
 * the available space.
 *
 * - 4000x500px exceeds the width of the container
 * - 1000x4000px exceeds the height of the container
 * - 4000x4000px exceeds both the width and the height of the container.
 * - 600x400px should fit within the container without any scaling
 * - 100x200px should also fit. It's included to prove that it isn't scaled up.
 */
const duration = 2000; // time (msec) to display each slide
const min_size = 0; // min file size to select
const max_size = 0; // max file size (0 means no limit)
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  /* wd, ht */
  [4000, 500],
  [1000, 4000],
  [4000, 4000],
  [600, 400],
  [100, 200]
];

async function slideshow_test(duration, min = 0, max = 0) {
  let n = 0;

  const my_img = document.querySelector('#slide-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];

    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=Pre-scaled+size\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
:root {
  /*
         * I don't know of any way to give <body> the margins I want without
         * somehow destroying the "dashboard" appearance, so I set variables
         * here and use them in several places to fake it.
         */
  --body-margin-sides: 2em;
  --body-margin-top: 2em;
  --body-margin-bottom: 2em;
  --body-background: #dbd2c3;
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

html {
  background: violet;
}

/* BODY and LEFT PANEL are taken from Brett Donald's solution,
       https://stackoverflow.com/a/79453218/522385 */
body {
  outline: 4px dashed red;
  outline-offset: -4px;
  background: var(--body-background);
  margin: 0 var(--body-margin-sides);
  /* side margins only */
  display: grid;
  overflow: hidden;
  /* prevents any body scroll -- good for dashboards */
  /* height: 100%; */
  /* width: 100% */
  /* padding: 0; */
  grid-template-rows: var(--body-margin-top)
    /* fake top margin */
    auto
    /* header */
    1fr
    /* widget area */
    var(--body-margin-bottom);
  /* fake bottom margin */
}

fake-body-margin {
  background: violet;
}

header {
  background: lightgreen;
  text-align: center;
}

hr {
  display: block;
  margin: var(--body-margin-top) auto;
  width: 100%;
  border: 0;
  border-top: 1px dashed black;
}

panels {
  display: grid;
  gap: 5em;
  grid-template-columns: 45fr 55fr;
}

left-panel {
  outline: 3px dotted green;
  outline-offset: -3px;
  display: grid;
  grid-template-rows: auto 1fr;
}

left-panel-top {
  margin-top: 15px;
  /* Gap above Welcome blurb */
  line-height: 1.4;
  /* line separation is 1.4 x normal */
  margin-bottom: 0px;
  /* Gap beneath Welcome blurb */
}

left-panel-bottom {
  position: relative;
}

left-panel-bottom-content {
  background: orange;
  border: 0;
  border-radius: 5px;
  /* border roundedness */
  column-gap: 10px;
  display: grid;
  grid-template-columns: auto auto;
  max-height: 100%;
  overflow: auto;
  /* scroll the overflow */
  padding: 0 1em;
  position: absolute;
  /* prevents content height from affecting the layout*/
  width: fit-content;
  /* I changed from 100% */
}


/* Hide scrollbar for Webkit browsers */
left-panel-bottom-content::-webkit-scrollbar {
  display: none;
}

/* Hide scrollbar for IE, Edge and Firefox */
left-panel-bottom-content {
  -ms-overflow-style: none;
  /* IE and Edge */
  scrollbar-width: none;
  /* Firefox */
}

scrollable-table-col1 {
  height: fit-content;
}

scrollable-table-col2 {
  height: fit-content;
}

scrollable-table-spacer {
  padding-bottom: 6px;
}

/* RIGHT-PANEL is taken from imh's solution */
right-panel {
  outline: 4px dotted black;
  outline-offset: -4px;
  background: yellow;
  display: flex;
  flex-direction: column;
  /* this appears to have no effect */
  overflow: hidden;
}

picture-box {
  outline: 4px dotted orange;
  outline-offset: -4px;
  display: flex;
  flex: auto;
  min-height: 0;
  justify-content: center;
  /* Centers horizontally */
  align-items: center;
  /* Centers vertically */
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  /* leave a little breathing room */
  max-height: 100%;
}
<body onload="load_scrollable_table(40);   slideshow_test(duration)">
  <fake-body-margin></fake-body-margin>
  <header>
    <h1>Banner</h1>
    <hr>
  </header>
  <!-- END OF BOILERPLATE -->
  <panels>
    <left-panel>
      <left-panel-top>
        Lorem ipsum odor amet, consectetuer adipiscing elit. Maecenas tempor
        proin amet ad consequat tempor praesent facilisis. Tempus potenti
        torquent nulla nullam elit class malesuada. Ut platea id ornare,
        convallis fusce eros. Fringilla pretium porta phasellus consectetur
        fermentum semper mollis. Est imperdiet euismod placerat et venenatis
        mattis; magna dictumst integer. Turpis fusce malesuada venenatis diam
        nisl quis tempus nostra. In nulla mollis ac nisi turpis consequat arcu
        potenti? Inceptos congue potenti at montes erat ac ultricies vel
        maximus. Ridiculus nunc porttitor tortor vulputate; posuere quisque.
        <hr>
        <h3>Recent site updates:</h3>
      </left-panel-top>
      <left-panel-bottom>
        <left-panel-bottom-content id="whatsnew">
          <!-- Javascript inserts table here -->
        </left-panel-bottom-content>
      </left-panel-bottom>
    </left-panel>

    <!-- <panel-gap></panel-gap> -->

    <right-panel>
      <!-- This div will hold the <img> -->
      <picture-box>
        <!-- This is a placeholder for the image -->
        <img class="scaled-picture"
               id="slide-img">
      </picture-box> <!-- END picture -->
    </right-panel>
  </panels>
  <fake-body-margin></fake-body-margin>
</body>

</html>

Update 2: I wanted to see if I could add a second scrollable list to <left-panel-bottom>, to the right of the first one. I turned <left-panel-bottom> into a grid with two equal-width columns. In the HTML I added a second <left-panel-bottom-content>. But since <left-panel-bottom-content> is defined as position: absolute, the two grid items were superimposed. And of course, position:absolute is essential to getting the items to stay within their containers as I want.

Is what's needed some kind of grid element, say <left-panel-bottom-container>, to sit within left-panel-bottom and contain the two content boxes?

/* This function populates the scrollable-container grid with variable
       numbers of rows of dummy data */
let my_limit;
const my_perday = 3;

function load_scrollable_table(limit, id) {
  my_limit = limit;
  if (my_limit < 1) {
    return;
  }
  const my_div = document.querySelector(`#${id}`);
  for (let day = 1;; day++) {
    if (!insert_row(my_div, `3/${day}/2025`, `${id} etc.`)) {
      return;
    }
    for (let thisday = 1; thisday < my_perday; ++thisday) {
      let v = "foo ".repeat(thisday * 4);
      if (!insert_row(my_div, '', `${id} more etc.` + v)) {
        return;
      }
    }
  }
}

function insert_row(my_div, col1, col2) {
  let my_row =
      `<scrollable-table-col1>${col1}</scrollable-table-col1>`
      + `<scrollable-table-col2>${col2}</scrollable-table-col2>`;
  my_div.innerHTML += my_row;
  return --my_limit;
}

/*
 * Slideshow_test generates a sequence of five images that are
 * inserted into the right panel, scaled proportionally to fit
 * the available space.
 *
 * - 4000x500px exceeds the width of the container
 * - 1000x4000px exceeds the height of the container
 * - 4000x4000px exceeds both the width and the height of the container.
 * - 600x400px should fit within the container without any scaling
 * - 100x200px should also fit. It's included to prove that it isn't scaled up.
 */
const duration = 2000; // time (msec) to display each slide
const min_size = 0; // min file size to select
const max_size = 0; // max file size (0 means no limit)
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  /* wd, ht */
  [4000, 500],
  [1000, 4000],
  [4000, 4000],
  [600, 400],
  [100, 200]
];

async function slideshow_test(duration, min = 0, max = 0) {
  let n = 0;

  const my_img = document.querySelector('#slide-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];

    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?`
                      + `text=Pre-scaled+size\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
:root {
    --body-margin-sides: 2em;
    --body-margin-top: 2em;
    --body-margin-bottom: 2em;
    --body-margin-color: violet;
    --body-background: #dbd2c3;
    --body-fontsize: 80%;
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

html {
  background: var(--body-margin-color);
}

/* BODY and LEFT PANEL are taken from Brett Donald's solution,
       https://stackoverflow.com/a/79453218/522385 */
body {
  outline: 4px dashed red;
  outline-offset: -4px;
  background: var(--body-background);
  /* side margins only */
  margin: 0 var(--body-margin-sides);
  display: grid;
  /* prevents any body scroll -- good for dashboards */
  overflow: hidden;
  font-size: var(--body-fontsize);
  grid-template-rows:
    /* fake top margin */
    var(--body-margin-top)
    /* header */
    auto
    /* widget area */
    1fr
    /* fake bottom margin */
    var(--body-margin-bottom);
}

fake-body-margin {
  background: var(--body-margin-color);
}

header {
  background: lightgreen;
  text-align: center;
}

hr {
  display: block;
  margin: var(--body-margin-top) auto;
  width: 100%;
  border: 0;
  border-top: 1px dashed black;
}

panels {
  display: grid;
  gap: 2em;
  grid-template-columns: 60fr 40fr;
}

left-panel {
  outline: 3px dotted green;
  outline-offset: -3px;
  display: grid;
  grid-template-rows: auto 1fr;
}

left-panel-top {
  /* Gap above Welcome blurb */
  margin-top: 15px;
  /* line separation is 1.4 x normal */
  line-height: 1.4;
  /* Gap beneath Welcome blurb */
  margin-bottom: 0px;
}

left-panel-bottom {
    position: relative;
    display: grid;                    /* new */
    gap: 1em;                         /* new */
    grid-template-columns: 40fr 60fr; /* new */
}

left-panel-bottom-content-1 {
  background: orange;
  border: 0;
  border-radius: 5px;
  column-gap: 10px;
  display: grid;
  grid-template-columns: auto auto;
  max-height: 100%;
  /* scroll the overflow */
  overflow: auto;
  padding: 0 0.5em;
  /* prevents content height from affecting the layout*/
  position: absolute;
  width: fit-content;
}

/* NEW: a second scrollable list next to the first in the left panel */
left-panel-bottom-content-2 {
  background: cyan;
  border: 0;
  border-radius: 5px;
  column-gap: 10px;
  display: grid;
  grid-template-columns: auto auto;
  max-height: 100%;
  /* scroll the overflow */
  overflow: auto;
  padding: 0 0.5em;
  /* prevents content height from affecting the layout*/
  /* absolute is obviously wrong since it overlays content-1 */
  position: absolute;
  width: fit-content;
}

/* Hide scrollbar for Webkit browsers */
left-panel-bottom-content-1::-webkit-scrollbar {
  display: none;
}

/* Hide scrollbar for IE, Edge and Firefox */
left-panel-bottom-content-1 {
  -ms-overflow-style: none;
  /* IE and Edge */
  scrollbar-width: none;
  /* Firefox */
}

scrollable-table-col1 {
  height: fit-content;
}

scrollable-table-col2 {
  height: fit-content;
}

scrollable-table-spacer {
  padding-bottom: 6px;
}

/* Right-hand panel */
right-panel {
  background: yellow;
  border: 4px solid black;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}

/* Flex item inside right panel flexbox */
figure {
  position: absolute;
  width: 100%;
  height: 100%;
  background: transparent;
  display: flex;
  justify-content: center;
  align-items: center;
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  max-height: 100%;
}
<html>
<body onload="load_scrollable_table(40, 'whatsnew');
              load_scrollable_table(10, 'tigdh');
              slideshow_test(duration)">
  <fake-body-margin></fake-body-margin>
  <header>
    <h1>Banner</h1>
    <hr>
  </header>
  <!-- END OF BOILERPLATE -->
  <panels>
    <left-panel>
      <left-panel-top>
        Lorem ipsum odor amet, consectetuer adipiscing elit. Maecenas tempor
        proin amet ad consequat tempor praesent facilisis. Tempus potenti
        torquent nulla nullam elit class malesuada. Ut platea id ornare,
        convallis fusce eros. Fringilla pretium porta phasellus consectetur
        fermentum semper mollis. Est imperdiet euismod placerat et venenatis
        mattis; magna dictumst integer.
        <hr>
        <h3>Recent site updates:</h3>
      </left-panel-top>
      <left-panel-bottom>
        <left-panel-bottom-content-1 id="whatsnew">
          <!-- Javascript inserts table here -->
        </left-panel-bottom-content-1>
        <left-panel-bottom-content-2 id="tigdh">
          <!-- Javascript inserts table here -->
        </left-panel-bottom-content-2>
      </left-panel-bottom>
    </left-panel>

    <!-- <panel-gap></panel-gap> -->

    <right-panel>
      <!-- This div will hold the <img> -->
      <figure>
        <!-- This is a placeholder for the image -->
        <img class="scaled-picture"
               id="slide-img">
      </figure> <!-- END picture -->
    </right-panel>
  </panels>
  <fake-body-margin></fake-body-margin>
</body>

3 Answers 3

0

My solutions work fine as long as the viewport has room for <left-panel-top>. On viewports which are too small for <left-panel-top>, this element will be bigger than the viewport, causing its parent <left-panel> to therefore also <right-panel> to also be bigger than the viewport.

If you are trying to design for viewports which are too small to accommodate <left-panel-top>, then specify what you would like to happen in this case and we can help you solve it.

/* This function populates the scrollable-container grid with variable
       numbers of rows of dummy data */
let my_limit;
const my_perday = 3;

function load_scrollable_table(limit) {
  my_limit = limit;
  if (my_limit < 1) {
    return;
  }
  const my_div = document.querySelector("#whatsnew");
  for (let day = 1;; day++) {
    if (!insert_row(my_div, `3/${day}/2025`, 'Foobar etc.')) {
      return;
    }
    for (let thisday = 1; thisday < my_perday; ++thisday) {
      let v = "foo ".repeat(thisday * 4);
      if (!insert_row(my_div, '', 'Foobar more etc.' + v)) {
        return;
      }
    }
  }
}

function insert_row(my_div, col1, col2) {
  let my_row = `<scrollable-table-col1>${col1}</scrollable-table-col1>
<scrollable-table-col2>${col2}</scrollable-table-col2>`;
  my_div.innerHTML += my_row;
  return --my_limit;
}

/*
 * Slideshow_test generates a sequence of five images that are
 * inserted into the right panel, scaled proportionally to fit
 * the available space.
 *
 * - 4000x500px exceeds the width of the container
 * - 1000x4000px exceeds the height of the container
 * - 4000x4000px exceeds both the width and the height of the container.
 * - 600x400px should fit within the container without any scaling
 * - 100x200px should also fit. It's included to prove that it isn't scaled up.
 */
const duration = 2000; // time (msec) to display each slide
const min_size = 0; // min file size to select
const max_size = 0; // max file size (0 means no limit)
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  /* wd, ht */
  [4000, 500],
  [1000, 4000],
  [4000, 4000],
  [600, 400],
  [100, 200]
];

async function slideshow_test(duration, min = 0, max = 0) {
  let n = 0;

  const my_img = document.querySelector('#slide-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];

    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=Pre-scaled+size\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
:root {
  /*
         * I don't know of any way to give <body> the margins I want without
         * somehow destroying the "dashboard" appearance, so I set variables
         * here and use them in several places to fake it.
         */
  --body-margin-sides: 2em;
  --body-margin-top: 2em;
  --body-margin-bottom: 2em;
  --body-background: #dbd2c3;
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

html {
  background: violet;
}

/* BODY and LEFT PANEL are taken from Brett Donald's solution,
       https://stackoverflow.com/a/79453218/522385 */
body {
  outline: 4px dashed red;
  outline-offset: -4px;
  background: var(--body-background);
  margin: 0 var(--body-margin-sides);
  /* side margins only */
  display: grid;
  overflow: hidden;
  /* prevents any body scroll -- good for dashboards */
  /* height: 100%; */
  /* width: 100% */
  /* padding: 0; */
  grid-template-rows: var(--body-margin-top)
    /* fake top margin */
    auto
    /* header */
    1fr
    /* widget area */
    var(--body-margin-bottom);
  /* fake bottom margin */
}

fake-body-margin {
  background: violet;
}

header {
  background: lightgreen;
  text-align: center;
}

hr {
  display: block;
  margin: var(--body-margin-top) auto;
  width: 100%;
  border: 0;
  border-top: 1px dashed black;
}

panels {
  display: grid;
  gap: 5em;
  grid-template-columns: 45fr 55fr;
}

left-panel {
  outline: 3px dotted green;
  outline-offset: -3px;
  display: grid;
  grid-template-rows: auto 1fr;
}

left-panel-top {
  margin-top: 15px;
  /* Gap above Welcome blurb */
  line-height: 1.4;
  /* line separation is 1.4 x normal */
  margin-bottom: 0px;
  /* Gap beneath Welcome blurb */
}

left-panel-bottom {
  position: relative;
}

left-panel-bottom-content {
  background: orange;
  border: 0;
  border-radius: 5px;
  /* border roundedness */
  column-gap: 10px;
  display: grid;
  grid-template-columns: auto auto;
  max-height: 100%;
  overflow: auto;
  /* scroll the overflow */
  padding: 0 1em;
  position: absolute;
  /* prevents content height from affecting the layout*/
  width: fit-content;
  /* I changed from 100% */
}


/* Hide scrollbar for Webkit browsers */
left-panel-bottom-content::-webkit-scrollbar {
  display: none;
}

/* Hide scrollbar for IE, Edge and Firefox */
left-panel-bottom-content {
  -ms-overflow-style: none;
  /* IE and Edge */
  scrollbar-width: none;
  /* Firefox */
}

scrollable-table-col1 {
  height: fit-content;
}

scrollable-table-col2 {
  height: fit-content;
}

scrollable-table-spacer {
  padding-bottom: 6px;
}

/* Right-hand panel */
right-panel {
  background: yellow;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  border: 4px solid black;
}

/* Flex item inside right panel flexbox */
figure {
  position: absolute;
  width: 100%;
  height: 100%;
  background: transparent;
  display: flex;
  justify-content: center;
  align-items: center;
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  max-height: 100%;
}
<body onload="load_scrollable_table(40);   slideshow_test(duration)">
  <fake-body-margin></fake-body-margin>
  <header>
    <h1>Banner</h1>
    <hr>
  </header>
  <!-- END OF BOILERPLATE -->
  <panels>
    <left-panel>
      <left-panel-top>
        Lorem ipsum odor amet, consectetuer adipiscing elit. Maecenas tempor
        proin amet ad consequat tempor praesent facilisis. Tempus potenti
        torquent nulla nullam elit class malesuada. Ut platea id ornare,
        convallis fusce eros. Fringilla pretium porta phasellus consectetur
        fermentum semper mollis. Est imperdiet euismod placerat et venenatis
        mattis; magna dictumst integer.
        <hr>
        <h3>Recent site updates:</h3>
      </left-panel-top>
      <left-panel-bottom>
        <left-panel-bottom-content id="whatsnew">
          <!-- Javascript inserts table here -->
        </left-panel-bottom-content>
      </left-panel-bottom>
    </left-panel>

    <!-- <panel-gap></panel-gap> -->

    <right-panel>
      <!-- This div will hold the <img> -->
      <figure>
        <!-- This is a placeholder for the image -->
        <img class="scaled-picture"
               id="slide-img">
      </figure> <!-- END picture -->
    </right-panel>
  </panels>
  <fake-body-margin></fake-body-margin>
</body>

</html>

Regarding a solution to Update 2 of your question ... I wouldn’t use a grid here, I would build on our initial good work and use two absolutely-positioned panels.

* {
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  background: #f2f2f2;
  margin: 0 1em;            /* side margins only */
  overflow: hidden;         /* prevents any body scroll -- good for dashboards */
  display: grid;
/*
  GRID TEMPLATE ROWS
  - first row is auto -- this allows the header to take as much space as it needs
  - last row is 1em -- we will leave this empty to simulate a bottom margin
  - middle row is 1fr -- whatever space is left over 
*/  
  grid-template-rows: auto 1fr 1em;  
}

header {
  text-align: center;
}

panels {
  display: grid;
/*
  GRID TEMPLATE COLUMNS
  - two equal width columns
*/
  grid-template-columns: 1fr 1fr;
  gap: 1em;                   /* with a gap between */
}

left-panel {
  display: grid;
/*
  GRID TEMPLATE ROWS
  - first row is auto -- this allows the welcome, the hr and the h2 to take as much space as they need
  - last row is 1fr -- whatever space is left over 
*/  
  grid-template-rows: auto 1fr;
  background: pink;
}

hr {
  display: block;
  margin: 1em auto;
  width: 80%;
  border: 0;
  border-top: 1px dashed black;
}

left-panel-bottom {
  background: tan;
  position: relative;
}

left-panel-bottom-left-content {
  position: absolute;             /* prevents the height of the content from affecting the layout*/
  width: 47%;
  max-height: 100%;
  overflow: auto;                 /* scroll the overflow */
  border: 3px solid purple;
  padding: 0 1em;
}

left-panel-bottom-right-content {
  position: absolute;             /* prevents the height of the content from affecting the layout*/
  left: 53%;
  width: 47%;
  max-height: 100%;
  overflow: auto;                 /* scroll the overflow */
  border: 3px solid navy;
  padding: 0 1em;
}

right-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  background: cyan;
}
<header>
  <h1>This is the page header with logo etc.</h1>
</header>

<panels>

  <left-panel>
  
    <left-panel-top>
    
      <welcome>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
        eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
        ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
        aliquip ex ea commodo consequat.
      </welcome>

      <hr>

      <h2 style="margin-top: 0;">Recent site updates:</h2>

    </left-panel-top>

    <left-panel-bottom>
      <left-panel-bottom-left-content>
        <p>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <p>
          Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus.
        </p>
        <p>
          Nam lectus eros, maximus ac magna vel, congue consequat eros.
        </p>
        <p>
          Fusce id pretium diam. Cras sit amet pharetra ante.
        </p>
        <p>
          Sed quis commodo quam, vel facilisis ipsum.
        </p>
        <p>
          Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed.
        </p>
        <p>
          Donec interdum sit amet est non accumsan.
        </p>
        <p>
          Donec non augue feugiat, fermentum nunc non, convallis est.
        </p>
        <p>
          Cras vel ligula nec odio faucibus ultricies.
        </p>
        <p>
          Sed vulputate tortor eget pretium convallis.
        </p>
        <p>
          Cras interdum elit eget mi porta suscipit. Morbi ut velit diam.
        </p>
        <p>
          Etiam finibus eros et efficitur rutrum.
        </p>
        <p>
          Quisque viverra metus ac eleifend imperdiet.
        </p>
        <p>
          Quisque pretium ut purus vitae tempus.
        </p>
        <p>
          Duis varius risus congue velit faucibus, sed interdum purus consectetur.
        </p>
      </left-panel-bottom-left-content>
      <left-panel-bottom-right-content>
        <p>
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </p>
        <p>
          Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus.
        </p>
        <p>
          Nam lectus eros, maximus ac magna vel, congue consequat eros.
        </p>
        <p>
          Fusce id pretium diam. Cras sit amet pharetra ante.
        </p>
        <p>
          Sed quis commodo quam, vel facilisis ipsum.
        </p>
        <p>
          Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed.
        </p>
        <p>
          Donec interdum sit amet est non accumsan.
        </p>
        <p>
          Donec non augue feugiat, fermentum nunc non, convallis est.
        </p>
        <p>
          Cras vel ligula nec odio faucibus ultricies.
        </p>
        <p>
          Sed vulputate tortor eget pretium convallis.
        </p>
        <p>
          Cras interdum elit eget mi porta suscipit. Morbi ut velit diam.
        </p>
        <p>
          Etiam finibus eros et efficitur rutrum.
        </p>
      </left-panel-bottom-right-content>
    </left-panel-bottom>
  </left-panel>

  <right-panel>
    Slideshow
  </right-panel>
</panels>

Sign up to request clarification or add additional context in comments.

3 Comments

This appears to work correctly! My dashboard concept is limited to desktops and large landscape-oriented tablets, but I'll keep in mind how the vertical space of <left-panel-top> can influence (i.e. break) the bottom of the design. One question: I've been requested to add to the left panel another scrollable text window to the right of the first one. Should it be straightforward to redefine <left-panel-bottom> as a 2-column grid containing two <left-panel-bottom-content>s without breaking everything again?
Please see Update 2 in OP. I tried to implement the above, with only partial success. Thanks!
I have updated this answer to address update 2.
0

Absolute positioning to the rescue again. (I also recommend using a <figure> element to wrap an image, instead of a custom <picture-box> element. It doesn’t make an actual difference in this case, but a <figure> has additional semantics for working with images.)

figure {
  position: absolute;
  width: 100%;
  height: 100%;
}

/* Slideshow generates rectangles of various dimensions */
const duration = 1500; // time (msec) to display each slide
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  [4000, 500, "Oversize horizontal"],
  [1000, 4000, "Oversize vertical"],
  [400, 300, "Should fit"],
  [100, 200, "Very small"],
  [4000, 4000, "Oversize square"]
];

async function show_slide_test(duration, min = 0, max = 0) {
  let n = 0;
  const my_img = document.querySelector('#slide-div-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];
    let desc = sizes[size_index][2];
    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=${desc}\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  width: 100%;
}

body {
  outline: 4px dashed red;
  outline-offset: -4px;
  height: 100%;
  /* limit to height of html */
  width: 100%;
  margin: 0;
  /* prevent body from shifting */
  padding: 0;
  overflow: hidden;
  /* prevents any body scroll */
}

/* Divide body into a grid of 2 columns */
panels {
  background: blue;
  min-height: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2em;
}

/* Left-hand panel */
left-panel {
  background: pink;
}

/* Right-hand panel */
right-panel {
  background: yellow;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}

/* Flex item inside right panel flexbox */
figure {
  position: absolute;
  width: 100%;
  height: 100%;
  background: green;
  display: flex;
  justify-content: center;
  align-items: center;
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  max-height: 100%;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

<body onload="show_slide_test(duration)">
  <panels>
    <left-panel></left-panel>
    <right-panel>
      <figure>
        <img class="scaled-picture" id="slide-div-img">
      </figure>
    </right-panel>
  </panels>
</body>

</html>

5 Comments

YES! THIS! EXACTLY! Sorry to shout but I can't count how many different guesses I made before posting this Q. I did try using position: absolute at one point, as you did in stackoverflow.com/a/79453218/522385, but to which element I don't remember. It moved the slideshow to the upper left of the window, and I wasn't sure why. I'm still not clear how to employ position:absolute but it obviously was the missing ingredient.
Ah - I notice you also needed to set position:relative in the parent element.
By default, all elements are styled with position: static. An absolutely positioned element is positioned relative to its nearest positioned ancestor, which means an ancestor whose position is something other than static. If there is no such ancestor, an absolutely positioned element is positioned relative to the body. That’s where position: relative comes in handy ... it turns an element into a positioned element, becoming the positioning reference for any of its absolutely positioned decendants.
I've had to reject your solution - please see my update in OP.
0

I hope I understood your question correctly - you want the images in the right panel to shrink. So you need the parent elements also shrink as well. Here's an example:

/* Slideshow generates rectangles of various dimensions */
const duration = 1500; // time (msec) to display each slide
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  [4000, 500, "Oversize horizontal"],
  [1000, 4000, "Oversize vertical"],
  [400, 300, "Should fit"],
  [100, 200, "Very small"],
  [4000, 4000, "Oversize square"]
];

async function show_slide_test(duration, min = 0, max = 0) {
  let n = 0;
  const my_img = document.querySelector('#slide-div-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];
    let desc = sizes[size_index][2];
    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=${desc}\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
}

body {
  outline: 4px dashed red;
  outline-offset: -4px;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Divide body into a grid of 2 columns */
panels {
  background: blue;
  flex: auto;
  overflow:hidden;
  display: grid;
  grid-template-columns: 50fr 50fr;
  gap: 2em;
}

/* Left-hand panel */
left-panel {
  background: pink;
  overflow: hidden;
}

/* Right-hand panel */
right-panel {
  background: yellow;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Flex item inside right panel flexbox */
picture-box {
  background: green;
  display: flex;
  justify-content: center;
  align-items: center;
  flex: auto;
  min-height: 0;
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  max-height: 100%;
}
<body onload="show_slide_test(duration)">
  <panels>
    <left-panel></left-panel>
    <right-panel>
      <picture-box>
      <!-- This is a placeholder for the image -->
      <img class="scaled-picture"
                 id="slide-div-img">
      </picture-box>
    </right-panel>
  </panels>
</body>

5 Comments

You have provided a snippet, but you have not explained what adjustments you have made and why. Your answer could be improved by adding this.
What I want is for the images to be centered in the right panel (whose background is yellow). If the image is wider or taller than the available space, I want it to be shrunk just enough to (maintaining aspect ratio) to fit.
No problem. I updated my answer.
@Brett Donald, But you also don't explain in your answer why position: absolute compresses images, and why you resorted to it. )) And you probably know that position: absolute is not a good solution. Because if, for example, the image card will have a dynamic caption, try to solve it using position: absolute: codepen.io
Please see my update to the OP. Your solution seems to work well in isolation, but unfortunately not in the larger context.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.