Trying to eliminate gap between <img> and <figcaption>

Hi,

I would be grateful for any thoughts/help anyone can offer please. I am creating a gallery where the page is generated by PHP from a SQL table - that side of things works but I am having one small and one larger problem with the layout.

The pictures are scans of very old photos (taken and scanned by my recently deceased Father-in-Law) that come in many different sizes, resolutions and aspect ratios. The text for <figcaption> varies a lot in no. of characters - sometimes just a couple of words, sometimes several paragraphs. On a desktop I want to have a 3 x 2 grid. Initially I gave all the pictures the same width, in a grid. But this made the portrait orientated pictures bigger than the landscape orientated ones (by definition) and it looked unbalanced.

So I decided that I would place each picture in a square and keep the original aspect ratio. This gave rise to the first, more minor problem - to keep the frames around pictures even (15 px) I had to use width: calc(100% - 30px); for the container width. I then styled the img as width: 100%; . But defining the image height was problematic. I fudged the issue by using height: calc((95vw - 30px)/3); which gave an acceptable result - the space for the image doesn’t have to be exactly square. I wonder if there is anyway, without using javascript, that I could have made the height equal to the width of width: calc(100% - 30px);?

This Code Pen illustrates the kind of output I am getting:

I have put borders of width 0px so that values can be inserted to see where the boxes are on the page.

But the more important issue is the gap between the bottom of the photo and the . To make the tops of the photos line up with each other I used

img{
object-fit: contain;
object-position: 50% 0%;
}

I could have used

img{
object-fit: contain;
object-position: 50% 0%;
}

which would place the image at the bottom of its box and eliminate the gap but in my opinion this looks horrible because the tops of each picture vary greatly in position.

I wonder if there is something I can do using position: but I don’t think there is without using javascript? Or should I approach it all differently?

To anyone who has read this far, thank you very much for taking an interest. Hope I have explained myself OK.

IMHO, img bottom aligning causing a void above some pictures would be more acceptable so I would use your latter code (typo corrected):

object-position: 50% 100%;

Maybe another solution could be to make the caption expand on hover/focus (…more) with some sort of CSS tool tip technique.

Just my two cents. :slight_smile:

1 Like

I prefer to use object-fit:cover for galleries and then you can have square images) or at least images all the same size. Usually in a gallery you link to the full size photo anyway (probably in a modal) so its not that important if the smaller gallery images are clipped in some way.

The following codepen was in answer to an older question on the forum but I would do something like this.

Adapted to your codepen.

1 Like

PaulOB

Thank you for taking the time and trouble to read my post and to provide suggestions.

The pictures are all of low quality - all were taken many decades ago, when lens quality was much poorer than today, then existed in silver halide form for 30 - 50 years before being scanned about 15 years ago. So there is no point in seeing them any larger than the size in the gallery page. The idea of the gallery is to recreate a photo album that my Father-in-Law printed.

Erik_J - thank you for correcting the typo.

The tool tip idea is a great sideways idea - I may go with that.

1 Like

They could be a full size popup though.

You could just pop them to normal aspect ratio on hover like this:

2 Likes

Nice! Thank you.

2 Likes

For interest - I delved into javascript and got it working with js. I had wanted to avoid that if possible but am now glad I took the time and trouble to learn it.

Thanks for letting us know :slight_smile:

What was it that JS could do that CSS couldn’t achieve in this instance? (Just asking for my own interest.)

Do you have a demo of the final result just so I can satisfy myself that I didn’t miss something obvious :slight_smile:

2 Likes

Glad someone’s interested :grinning: .

I’m afraid I haven’t got a demo set up.

I will post the php that generates the code, the CSS, a ctrl-u of the HTML generated and then a description of how it works.

The php:

<?php
// Access session to keep logged in.
session_start() ; 

//Connect to database
require_once ('databaseconnection/config.php'); 

// Redirect if not logged in.
if ( !isset( $_SESSION[ 'username' ] ) ) {  require ( 'login_tools.php' ) ;   load() ; }

//require a script that creates the function mysql_entities_fix_string which "sanitises" user input, removing & and <> to reduce risk of HTML injection.
require_once ('includes/sanitise.php'); 
?>

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="stylesheets/palettestyle.css" rel="stylesheet"/>
    <link href="stylesheets/style.css" rel="stylesheet"/>
    <link rel="icon" href="images/other/favicon.ico?v=2" type="image/x-icon"/>
    <script src="https://use.fontawesome.com/c192036237.js"></script>
    <!-- "include" javascript file as per Robin Nixon book -->
    <script src='js/OSC.js'></script>
    <title>
      Gallery - A photographic record of Colin &amp; Helene Middleton and family. 
    </title>
  </head>
  <body>
    <?php
    // Define variable that the header.php file uses to determine which menu item to give the class "active" in order for it to be styled differently in CSS
    $thisPage='gallery';

    include('includes/header.php');
    ?>
    
    <main class="gallery">
          <script>
          function findOffset(x)
        { 
          let positionOffset
          
          let width=document.getElementById(x).clientWidth        //determine the width of the image with ID of x, the width as displayed
          let natH=document.getElementById(x).naturalHeight       //determine the ORIGINAL height of the image with ID of x 
          let natW=document.getElementById(x).naturalWidth        //determine the ORIGINAL width of the image with ID of x 

          if (natW>natH){  
          positionOffset = (natH/natW)*width  //if image is in landscape orientation
            }else{
          positionOffset=width     //if image is in portrait orientation
        }
          return positionOffset
        }

      </script>
      
            
      <script>
        function findHeight(image,caption){
          //determine the height of figcaption
          let captionHeight=document.getElementById(caption).offsetHeight
          //determine the height of the image using the function I created, findOffset()
          let imageHeight=findOffset(image)
          //return the sum of these two heights, to be used to fix the height of <figure>
          return captionHeight+imageHeight
        }
      </script>
      
        <h1>
          A photographic record of Colin &amp; Helene Middleton and family.
        </h1>

      <?php
      //dealing with the returned form ie was "Previous" or "Next" or "start" or "end" clicked?
        //Was "Previous" or "Next" or "start" or "end" clicked? 
        if (isset($_POST['newPage'])){
          $newPage= mysql_entities_fix_string($dbc, $_POST['newPage']);
          $pageNo= mysql_entities_fix_string($dbc, $_POST['pageNo']);
          $totalPics= mysql_entities_fix_string($dbc, $_POST['totalPics']);

          //Assign a value to $newPage depending on which button was used to access this page, or if no buttons used (ie $newPage has none of the values) then assign the value of 1 ie the start
          switch ($newPage){
              
              case 'start':
                $pageNo=1;
                break;
              
              case 'previous':
                $pageNo--;
                break;
                                    
              case 'next':
                $pageNo++;
                break;
                            
              case 'end':
                $pageNo=intval((($totalPics-1)/6)+1);               //   (($totalPics - ($totalPics-1)%6)/6);
                break; 
              
              case 'pageByNumberUpper':
                break;
              
              case 'pageByNumberLower':
                break;
          }
    
        }else{
        //if the user did not arrive here via a "previous" or "next" button then $pageNo needs a starting value of 1
        $pageNo=1;
        }

      ?>

        <?php
        //Build an array from the table picIndex that contains the info for each picture eg description, order, alt
        //https://phpdelusions.net/mysqli_examples/prepared_select - using prepared statements for SELECT  
          $stmt=$dbc->prepare("SELECT path, text, alt, number FROM picIndex ORDER BY number") ;
          $stmt->bind_param();
          $stmt->execute();
          if ($stmt->error){echo "something has gone wrong";}  

          $result = $stmt->get_result(); // get the mysqli result

          //One row at a time build an array, $data, that contains the data from the SQL table picIndex. 
          //To each row add an element, "order" that contains a number that reflects the order in which  the pictures should be displayed  
          $data = [];
          $i=0;  //this is the number that will be inserted into the array as "order"
              while ($row = $result->fetch_assoc()) {

              $row['order'] = $i;  //add the number "order" onto this array so it will get added into this row in the two dimensional array "$data"
              $data[] = $row;   // add the newly created $row to the array $data 
              $i++;
              }
        /*
        echo '<pre>';
        print_r($data);
        echo '</pre>';
        */    
        ?> 
          
          <!-- Previous/Next buttons as forms -->
            <form method="post" action="gallery.php" id="gallery">
              <input type="number" name="pageNo" value="<?php echo $pageNo; ?>" hidden>
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="<?php echo $i; ?>" hidden>
            </form>
      
            <form method="post" action="gallery.php" id="start">
              <input type="number" name="pageNo" value="1" hidden>
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="<?php echo $i; ?>" hidden>
            </form>
      
            <form method="post" action="gallery.php" id="end">
              <input type="number" name="pageNo" value="<?php echo $i; ?>" hidden>
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="<?php echo $i; ?>" hidden>
            </form>  
            
            <!-- Because the slideshow nav buttons that involve submission of a page number have a default value then you need a different form for upper and for lower submit buttons or otherwise the lower button overwrites the value submitted by the upper button  --> 
            <form method="post" action="gallery.php" id="numberUpper">
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="<?php echo $i; ?>" hidden>
            </form>  
      
            <form method="post" action="gallery.php" id="numberLower">
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="<?php echo $i; ?>" hidden>
            </form>       
        
          <!-- Previous/Next buttons, "submit" buttons for the above forms.-->
            <div class="buttons">
              <?php
              //media queries in the CSS determine whether the up/down or left/right arrows are displayed by using {display: none;} to blank out the ones not needed, according to class.
              //At start and end create a disabled button with a class that the CSS uses to make a ghosted out button
              if ($pageNo>1){echo '<button type="submit" name="newPage" value="start" class="start" form="start"><span class="fa fa-angle-double-left"></span><span class="fa fa-angle-double-up"></span>Start</button>';}else{echo'<button type="submit" class="startGhost" disabled>Start</button>';}
              if ($pageNo>1){echo '<button type="submit" name="newPage" value="previous" class="previous" form="gallery"><span class="fa fa-angle-left"></span><span class="fa fa-angle-up"></span>Previous</button>';}else{echo'<button type="submit" class="previousGhost" disabled>Previous</button>';}
             
              // "Go to page" button etc
              //$i is the total number of pictures
              echo '
              <span class="goTo">Go to page 
              <input type="number" name="pageNo" form="numberUpper"  value="'.$pageNo.'" min="1" max="'.intval((($i-1)/6)+1).'" id="number">
              of '.intval((($i-1)/6)+1).'
              <button type="submit" form="numberUpper" name="newPage" value="pageByNumberUpper" id="numberSubmit">Go</button>
              </span> 
              ';
      
              if ($pageNo<($i/6)){echo'<button type="submit" name="newPage" value="next" class="next" form="gallery"><span class="fa fa-angle-down"></span>Next<span class="fa fa-angle-right"></span></button>';}else{echo'<button type="submit" class="nextGhost" disabled>Next</button>';}
              if ($pageNo<($i/6)){echo'<button type="submit" name="newPage" value="end" class="end" form="end"><span class="fa fa-angle-double-down"></span>End<span class="fa fa-angle-double-right"></span></button>';}else{echo'<button type="submit" class="endGhost" disabled>End</button>';}
              ?> 
            </div>  

        <article class="gallery">   
            <?php
            $startPicNo=(($pageNo-1)*6);      
            //a loop that will run 6 times, working through the next six photos in the list 
            for ($x=0; $x<6; $x++){
              //Display a photo if one exists (ie if the picture number is less than the total number of pictures)
              if ($startPicNo+$x<$i){
              //replace spaces in file names with %20 to make for correct HTML in <img> tag - spaces are not allowed  
              $data[$startPicNo+$x]['path']=rawurlencode($data[$startPicNo+$x]['path']);    
              echo '
              <figure class="card" id="figure'.$x.'"> 
                <img src="images/photos/'.$data[$startPicNo+$x]['path'].'" class="gallery" alt="'.$data[$startPicNo+$x]['alt'].'" id="image'.$x.'"> 
                <figcaption class="caption" id="caption'.$x.'">'.$data[$startPicNo+$x]['text'].'</figcaption>  
              </figure>
              
              <script>
                //https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript shows a routine I have used here. The next three lines in comment show the routine advised to delay a function until after an image has loaded.
                //var img = new Image();
                //img.onload = function() { alert("Height: " + this.height); }
                //img.src = "http://path/to/image.jpg"; 
              var img = new Image();  
              img.onload = function() {
                //position the caption
                caption'.$x.'.style.top=findOffset("image'.$x.'")+"px"
                //I have created the function findHeight below. This takes as parameters the IDs of the <IMG> and <FIGCAPTION> elements and determines their heights and adds them and returns that result. The next line uses that result to set the height of <FIGURE>. If this is not done then the row below is placed over the caption of this row.
                document.getElementById("figure'.$x.'").style.height=(findHeight("image'.$x.'","caption'.$x.'"))+"px"
              }
              img.src = "images/photos/'.$data[$startPicNo+$x]['path'].'";
            </script>
            
            <script>
              //When window is resized re calculate the positioning of the figcaption

                
                var resizeTimeout'.$x.';
                window.addEventListener("resize", function(event) {
                clearTimeout(resizeTimeout'.$x.');
                resizeTimeout'.$x.' = setTimeout(function(){
                
                
                caption'.$x.'.style.top=findOffset("image'.$x.'")+"px"
                document.getElementById("figure'.$x.'").style.height=(findHeight("image'.$x.'","caption'.$x.'"))+"px"
                
                
                }, 1000);
                  });  
                
                
              
            </script>
            

              ';
              }
            }
            ?> 
          
          
 
          
          
        </article> 
      
            <!-- Previous/Next TEST buttons -->
            <div class="buttons">
              <?php
              //media queries in the CSS determine whether the up/down or left/right arrows are displayed by using {display: none;} to blank out the ones not needed, according to class.
              //At start and end create a disabled button with a class that the CSS uses to make a ghosted out button
              if ($pageNo>1){echo '<button type="submit" name="newPage" value="start" class="start" form="start"><span class="fa fa-angle-double-left"></span><span class="fa fa-angle-double-up"></span>Start</button>';}else{echo'<button type="submit" class="startGhost" disabled>Start</button>';}
              if ($pageNo>1){echo '<button type="submit" name="newPage" value="previous" class="previous" form="gallery"><span class="fa fa-angle-left"></span><span class="fa fa-angle-up"></span>Previous</button>';}else{echo'<button type="submit" class="previousGhost" disabled>Previous</button>';}
              
              
              // "Go to page" button etc
              //$i is the total number of pictures
              echo '
              <span class="goTo">Go to page 
              <input type="number" name="pageNo" form="numberLower"  value="'.$pageNo.'" min="1" max="'.intval((($i-1)/6)+1).'" id="number">
              of '.intval((($i-1)/6)+1).'
              <button type="submit" form="numberLower" name="newPage" value="pageByNumberLower" id="numberSubmit">Go</button>
              </span> 
              ';
              
              if ($pageNo<($i/6)){echo'<button type="submit" name="newPage" value="next" class="next" form="gallery"><span class="fa fa-angle-down"></span>Next<span class="fa fa-angle-right"></span></button>';}else{echo'<button type="submit" class="nextGhost" disabled>Next</button>';}
              if ($pageNo<($i/6)){echo'<button type="submit" name="newPage" value="end" class="end" form="end"><span class="fa fa-angle-double-down"></span>End<span class="fa fa-angle-double-right"></span></button>';}else{echo'<button type="submit" class="endGhost" disabled>End</button>';}
              ?> 
            </div>
    
         <!--when resizing the window the layout gets messed up so this routine checks every second and if the window has been resized it reloads.-->
    <!--    <script>      
          var resizeTimeout;
          window.addEventListener('resize', function(event) {
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(function(){
              window.location.reload();
            }, 1000);
          });  
        </script> -->
      
    </main>
    


    
    <?php 
    include('includes/footer.php');
    ?>
  </body>
       
</html> 

The CSS:

*{
  margin: 0;
  border: 0;
  padding: 0;
  box-sizing: border-box;
}

img,source{
  display:block;
}

p,span,legend,input,a.link,figcaption{
  font-family: roboto, helvetica, arial, sans-serif;
  color: var(--textColor);
  font-size: 1.2rem;
  padding: 5px;
}

a.link{
  color: #900;
}

h1,h2,h3,h4,h5,h6{
  font-family: bree, Times New Roman, serif;
  color: var(--headlineColor);
  padding: 5px;
}

html{
  height: 100%;
}

body{
  min-height: 100%;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background-color: var(--outerBackgroundColor);
}

nav {
  display: flex;
  flex-direction: column;
  text-align: center;
  background-color: var(--headlineColor);
  margin: auto;
}

a.menu{
  display: block;
  padding: 10px 15px;
  font-family: roboto, helvetica, arial, sans-serif;
  font-weight: bold;
  color: white;
  text-decoration: none;
  transition: .3s ease-in-out;
}

.menu:hover:not(.active){
  color: var(--headlineColor);
  background-color: white;
}

a.active{
  color: #111;
  background-color: #ccc;
}

footer.footer p{
  font-size: smaller;
}

main.index{
  max-width: 650px;
  margin: 0 auto;
}

section.index, footer.footer{
 background-color:  var(--backgroundColor); 
}

section.index{  
 height: 100%;
}

.terms{
  display: none;
}

main.contact{
  max-width: 650px;
  margin: 0 auto;
}

section.contact{
   background-color:  var(--backgroundColor); 
   height: 100%;
}

input.send, a.send{
  border: outset 3px #666;
  border-radius: 3px;
  color: black;
  background-color: #ccc;
  margin: 5px;
}

p.required{
  font-size: small;
  color: red;
}

span.required{
  font-size: small;
  color: red;
  padding-left: 0;
}

/*To make the asterisk next too the word 'email' closer than it otherwise would be if it had the default padding of 5px*/
span#email{
  padding-right: 2px;
}

fieldset{
  border: solid 1px black;
  border-radius: 4px;
  padding: 0 5px 2px 5px;
  background-color:  white;
  margin: 0 5px;
}

legend{
  font-size: small;
  padding: 3px;
}

input:not([type="submit"]), textarea{
  width: 100%;
}

a.link{
  text-decoration: none;
}

main.login{
  max-width: 650px;
  margin: auto;
}

section.login{
   background-color:  var(--backgroundColor); 
   min-width: 300px;
   min-height: 300px;
   height: auto;
   border-radius: 4px;
   display: grid;
}

article.logout{
  margin: auto;
}

/* To centre the login button on the goodbye.php page */
p.center{
  display: grid;
}

a.center{
  margin: auto;
}

main.gallery{
  width: 100%;
  display: grid;
  justify-items: center;

}

article.gallery{
  display: grid;
  grid-template-rows: repeat(6, auto);
  gap: 15px;
  margin-bottom: 15px;
}

  img.gallery{
    width: calc(98vw - 15px);
    height: calc(98vw - 15px);
    object-fit: contain;
    object-position: 50% 0%;
} 

  /*The next few items are to do with lifting the figcaption to the bottom of the image*/
  
  figure.card{
    position: relative;
    z-index: -100;  /* On the gallery page if all the pictures on the bottom row are landscape then the bottom of the <img> box sometimes goes over the top of the navigation buttons until this z-index is added to make sure the <IMG> goes behind the navigation buttons.  */
    overflow: hidden;  /*This line is to solve a problem where if the bottom line of pictures is all landscapes of short "height" then the empty bottom bit of the <img> block would protrude below the <figure> block and create extra space BELOW the footer, eg page 27 on full screen.*/
  }
  
  figcaption.caption{
    position: absolute;
    width: 100%;
  }


figcaption{
  height: auto;
  background-color: var(--backgroundColor);
}


button.previous,button.next, button.start,button.end{
  border: outset 3px #666;
  border-radius: 3px;
  color: black;
  background-color: #ccc;
  padding: 10px;
  font-weight: bold;
  transition: .3s ease-in-out;
}

button.previous:hover,button.next:hover, button.start:hover,button.end:hover{
  border: inset 3px #666;
  border-radius: 3px;
  color: white;
  background-color: #333;
  padding: 10px;
  font-weight: bold;
  transition: .3s ease-in-out;
}

/* styling for the buttons that would take the gallery "off the end" if they worked */
  button.previousGhost,button.nextGhost, button.startGhost,button.endGhost{
  border-radius: 3px;
  color: #fff;
  background-color: #ddd;
  padding: 10px;
  transition: .3s ease-in-out;
}


div.buttons{
  width: 100%;
  display: flex;
}

  div.buttons{
    flex-direction: column;
  } 

  span.fa-angle-double-left, span.fa-angle-left, span.fa-angle-double-right, span.fa-angle-right{
    display: none;
}

  span.fa-angle-double-up, span.fa-angle-up, span.fa-angle-down, span.fa-angle-double-down{
    display: inline;
}

input#number{
  width: 4rem;
  font-weight: bold;
  border: inset 3px #666;
  background-color: #ccc;
  color: #333;
  transition: .3s ease-in-out;
}

input#number:hover{
  border: inset 3px #666;
  background-color: #333;
  color: #ccc;
}

button#numberSubmit{
  color: #333;
  background-color: #ccc;
  padding: 5px;
  border: 3px outset #666;
  transition: .3s ease-in-out;
}

button#numberSubmit:hover{
  color: #ccc;
  background-color: #666;
  padding: 5px;
  border: 3px outset #666;
}

span.goTo{
  border: outset 3px #666;
  text-align: center;
}

h1.treeHead{
  text-align: center;
}

figure.tree{
  width: 1200px;
  display: grid;
  margin: auto;
}

figcaption.tree{
  width: auto;
}



/* Border colors for testing     */
/*
main.gallery{
  border: red solid .1vw;
}

article.gallery{
  border: blue solid .1vw;
}

figure.card{
  background-color: #60EFF1;
  border: yellow solid .1vw;
}

img.gallery {  
  border: green solid .1vw;
}

*/



/* *** Tablet *** */
@media screen and (min-width: 600px)
  and (max-width: 991px){
    
p,span,legend,input,a.link,figcaption{
  font-size: 1.1rem;
}    
   
article.gallery{
  width: calc(100% - 30px);
  grid-template-rows: repeat(3, auto);
  grid-template-columns: repeat(2, calc((100% - 15px)/2));  
}
    
  img.gallery{
    width: calc((95vw - 15px)/2);
    height: calc((95vw - 15px)/2);
    object-fit: contain;
    object-position: 50% 0%;
}  
    
  figcaption.caption{
    width: calc((95vw - 15px)/2);
  }    
      
}





/* *** Desktop *** */
@media screen and (min-width: 992px){
  
p,span,legend,input,a.link,figcaption{
  font-size: 1rem;
}  
 
article.gallery{
  width: calc(100% - 30px);
  grid-template-rows: repeat(2, auto);
  grid-template-columns: repeat(3, calc((100% - 30px)/3)); 
}
  
  img.gallery{
    width: calc((95vw - 30px)/3);
    height: calc((95vw - 30px)/3);
    object-fit: contain;
    object-position: 50% 0%;
}
  
   figcaption.caption{
    width: calc((95vw - 30px)/3);
  }  
  
}





/*    *** tablet and desktop  ***     */
@media screen and (min-width: 600px) {
    nav {
      flex-direction: row;
  }
  
  a.right{
  margin-left: auto;
}
  
  div.buttons{
    flex-direction: row;
  }  
  
  button.previous,button.next, button.start,button.end{
  width: 7rem;
  margin: 10px;
  }
  
  button.previous:hover,button.next:hover, button.start:hover,button.end:hover{
   margin: 10px; 
  }
  
  button.start,button.start:hover{
  margin-right: auto;
}

  button.end,button.end:hover{
  margin-left: auto;
}
  
  span.fa-angle-double-left, span.fa-angle-left, span.fa-angle-double-right, span.fa-angle-right{
  display: inline;
}
  
  span.fa-angle-double-up, span.fa-angle-up, span.fa-angle-down, span.fa-angle-double-down{
  display: none;
}
  
  button.previousGhost,button.nextGhost, button.startGhost,button.endGhost{
  width: 7rem;
  margin: 10px;
  }  

  button.startGhost{
    margin-right: auto;
  }  
  
  button.endGhost{
    margin-left: auto;
  }
  
  span.goTo{
  margin: 10px;
}
  

  

      
    }

A ctrl-u of a generated page:


<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="stylesheets/palettestyle.css" rel="stylesheet"/>
    <link href="stylesheets/style.css" rel="stylesheet"/>
    <link rel="icon" href="images/other/favicon.ico?v=2" type="image/x-icon"/>
    <script src="https://use.fontawesome.com/c192036237.js"></script>
    <!-- "include" javascript file as per Robin Nixon book -->
    <script src='js/OSC.js'></script>
    <title>
      Gallery - A photographic record of Colin &amp; Helene Middleton and family. 
    </title>
  </head>
  <body>
    <header>
  <nav> 

  <a href="index.php" class="                         
          menu">Home</a>
  
  <a href="gallery.php" class="                            
      active    menu">Gallery</a>
    
  <a href="tree.php" class="                               
                                     
    menu">Family Tree</a>    
   
  <a href="contact.php" class="                               
                                     
    menu">Contact</a>
      
    
    
      <a href="goodbye.php" class="menu right">Log out</a> 
</nav>
   
</header>    
    <main class="gallery">
          <script>
          function findOffset(x)
        { 
          let positionOffset
          
          let width=document.getElementById(x).clientWidth        //determine the width of the image with ID of x, the width as displayed
          let natH=document.getElementById(x).naturalHeight       //determine the ORIGINAL height of the image with ID of x 
          let natW=document.getElementById(x).naturalWidth        //determine the ORIGINAL width of the image with ID of x 

          if (natW>natH){  
          positionOffset = (natH/natW)*width  //if image is in landscape orientation
            }else{
          positionOffset=width     //if image is in portrait orientation
        }
          return positionOffset
        }

      </script>
      
            
      <script>
        function findHeight(image,caption){
          //determine the height of figcaption
          let captionHeight=document.getElementById(caption).offsetHeight
          //determine the height of the image using the function I created, findOffset()
          let imageHeight=findOffset(image)
          //return the sum of these two heights, to be used to fix the height of <figure>
          return captionHeight+imageHeight
        }
      </script>
      
        <h1>
          A photographic record of Colin &amp; Helene Middleton and family.
        </h1>

      
         
          
          <!-- Previous/Next buttons as forms -->
            <form method="post" action="gallery.php" id="gallery">
              <input type="number" name="pageNo" value="1" hidden>
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="179" hidden>
            </form>
      
            <form method="post" action="gallery.php" id="start">
              <input type="number" name="pageNo" value="1" hidden>
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="179" hidden>
            </form>
      
            <form method="post" action="gallery.php" id="end">
              <input type="number" name="pageNo" value="179" hidden>
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="179" hidden>
            </form>  
            
            <!-- Because the slideshow nav buttons that involve submission of a page number have a default value then you need a different form for upper and for lower submit buttons or otherwise the lower button overwrites the value submitted by the upper button  --> 
            <form method="post" action="gallery.php" id="numberUpper">
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="179" hidden>
            </form>  
      
            <form method="post" action="gallery.php" id="numberLower">
              <!-- In a hidden input field pass the total number of pictures in the table picIndex to $_POST -->
              <input type="number" name="totalPics" value="179" hidden>
            </form>       
        
          <!-- Previous/Next buttons, "submit" buttons for the above forms.-->
            <div class="buttons">
              <button type="submit" class="startGhost" disabled>Start</button><button type="submit" class="previousGhost" disabled>Previous</button>
              <span class="goTo">Go to page 
              <input type="number" name="pageNo" form="numberUpper"  value="1" min="1" max="30" id="number">
              of 30
              <button type="submit" form="numberUpper" name="newPage" value="pageByNumberUpper" id="numberSubmit">Go</button>
              </span> 
              <button type="submit" name="newPage" value="next" class="next" form="gallery"><span class="fa fa-angle-down"></span>Next<span class="fa fa-angle-right"></span></button><button type="submit" name="newPage" value="end" class="end" form="end"><span class="fa fa-angle-double-down"></span>End<span class="fa fa-angle-double-right"></span></button> 
            </div>  

        <article class="gallery">   
            
              <figure class="card" id="figure0"> 
                <img src="images/photos/Col%20School.jpg" class="gallery" alt="" id="image0"> 
                <figcaption class="caption" id="caption0">This was a school photo of myself whilst at Prince's Field School in about 1947.</figcaption>  
              </figure>
              
              <script>
                //https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript shows a routine I have used here. The next three lines in comment show the routine advised to delay a function until after an image has loaded.
                //var img = new Image();
                //img.onload = function() { alert("Height: " + this.height); }
                //img.src = "http://path/to/image.jpg"; 
              var img = new Image();  
              img.onload = function() {
                //position the caption
                caption0.style.top=findOffset("image0")+"px"
                //I have created the function findHeight below. This takes as parameters the IDs of the <IMG> and <FIGCAPTION> elements and determines their heights and adds them and returns that result. The next line uses that result to set the height of <FIGURE>. If this is not done then the row below is placed over the caption of this row.
                document.getElementById("figure0").style.height=(findHeight("image0","caption0"))+"px"
              }
              img.src = "images/photos/Col%20School.jpg";
            </script>
            
            <script>
              //When window is resized re calculate the positioning of the figcaption

                
                var resizeTimeout0;
                window.addEventListener("resize", function(event) {
                clearTimeout(resizeTimeout0);
                resizeTimeout0 = setTimeout(function(){
                
                
                caption0.style.top=findOffset("image0")+"px"
                document.getElementById("figure0").style.height=(findHeight("image0","caption0"))+"px"
                
                
                }, 1000);
                  });  
                
                
              
            </script>
            

              
              <figure class="card" id="figure1"> 
                <img src="images/photos/school%20group.jpg" class="gallery" alt="Formal group photo of 50-60 boys of varying ages, outside, in front of a wooden building." id="image1"> 
                <figcaption class="caption" id="caption1">In 1947 we could not afford to go away for a holiday, along with many of my fellow schoolmates. The Leeds City Council ran a holiday camp at Ilkley. This was the Langbar Camp, close to Beamsley Beacon. Being poor enabled us to go to the camp for one week. The fresh air and walks, sleeping on palliasses filled with straw all made for a great adventure for us kids. I am half circled at the front.</figcaption>  
              </figure>
              
              <script>
                //https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript shows a routine I have used here. The next three lines in comment show the routine advised to delay a function until after an image has loaded.
                //var img = new Image();
                //img.onload = function() { alert("Height: " + this.height); }
                //img.src = "http://path/to/image.jpg"; 
              var img = new Image();  
              img.onload = function() {
                //position the caption
                caption1.style.top=findOffset("image1")+"px"
                //I have created the function findHeight below. This takes as parameters the IDs of the <IMG> and <FIGCAPTION> elements and determines their heights and adds them and returns that result. The next line uses that result to set the height of <FIGURE>. If this is not done then the row below is placed over the caption of this row.
                document.getElementById("figure1").style.height=(findHeight("image1","caption1"))+"px"
              }
              img.src = "images/photos/school%20group.jpg";
            </script>
            
            <script>
              //When window is resized re calculate the positioning of the figcaption

                
                var resizeTimeout1;
                window.addEventListener("resize", function(event) {
                clearTimeout(resizeTimeout1);
                resizeTimeout1 = setTimeout(function(){
                
                
                caption1.style.top=findOffset("image1")+"px"
                document.getElementById("figure1").style.height=(findHeight("image1","caption1"))+"px"
                
                
                }, 1000);
                  });  
                
                
              
            </script>
            

              
              <figure class="card" id="figure2"> 
                <img src="images/photos/RAF%20Dad.jpg" class="gallery" alt="Formal portrait in RAF uniform" id="image2"> 
                <figcaption class="caption" id="caption2">This is my Dad, Herbert Middleton, taken when he was in the R.A.F. during World War II.
Shortly after the war they split up in a very acrimonious way, which ended up in court after a violent assault upon my Father by Mum and her relatives.</figcaption>  
              </figure>
              
              <script>
                //https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript shows a routine I have used here. The next three lines in comment show the routine advised to delay a function until after an image has loaded.
                //var img = new Image();
                //img.onload = function() { alert("Height: " + this.height); }
                //img.src = "http://path/to/image.jpg"; 
              var img = new Image();  
              img.onload = function() {
                //position the caption
                caption2.style.top=findOffset("image2")+"px"
                //I have created the function findHeight below. This takes as parameters the IDs of the <IMG> and <FIGCAPTION> elements and determines their heights and adds them and returns that result. The next line uses that result to set the height of <FIGURE>. If this is not done then the row below is placed over the caption of this row.
                document.getElementById("figure2").style.height=(findHeight("image2","caption2"))+"px"
              }
              img.src = "images/photos/RAF%20Dad.jpg";
            </script>
            
            <script>
              //When window is resized re calculate the positioning of the figcaption

                
                var resizeTimeout2;
                window.addEventListener("resize", function(event) {
                clearTimeout(resizeTimeout2);
                resizeTimeout2 = setTimeout(function(){
                
                
                caption2.style.top=findOffset("image2")+"px"
                document.getElementById("figure2").style.height=(findHeight("image2","caption2"))+"px"
                
                
                }, 1000);
                  });  
                
                
              
            </script>
            

              
              <figure class="card" id="figure3"> 
                <img src="images/photos/Mum%20Fur%20coat.jpg" class="gallery" alt="" id="image3"> 
                <figcaption class="caption" id="caption3">Mum all glammed up.</figcaption>  
              </figure>
              
              <script>
                //https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript shows a routine I have used here. The next three lines in comment show the routine advised to delay a function until after an image has loaded.
                //var img = new Image();
                //img.onload = function() { alert("Height: " + this.height); }
                //img.src = "http://path/to/image.jpg"; 
              var img = new Image();  
              img.onload = function() {
                //position the caption
                caption3.style.top=findOffset("image3")+"px"
                //I have created the function findHeight below. This takes as parameters the IDs of the <IMG> and <FIGCAPTION> elements and determines their heights and adds them and returns that result. The next line uses that result to set the height of <FIGURE>. If this is not done then the row below is placed over the caption of this row.
                document.getElementById("figure3").style.height=(findHeight("image3","caption3"))+"px"
              }
              img.src = "images/photos/Mum%20Fur%20coat.jpg";
            </script>
            
            <script>
              //When window is resized re calculate the positioning of the figcaption

                
                var resizeTimeout3;
                window.addEventListener("resize", function(event) {
                clearTimeout(resizeTimeout3);
                resizeTimeout3 = setTimeout(function(){
                
                
                caption3.style.top=findOffset("image3")+"px"
                document.getElementById("figure3").style.height=(findHeight("image3","caption3"))+"px"
                
                
                }, 1000);
                  });  
                
                
              
            </script>
            

              
              <figure class="card" id="figure4"> 
                <img src="images/photos/Beryl%2BMum.jpg" class="gallery" alt="Head and shoulders, both dressed smartly, facing camera" id="image4"> 
                <figcaption class="caption" id="caption4">Beryl & Mum.&#13;
Beryl about 15 years old here.</figcaption>  
              </figure>
              
              <script>
                //https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript shows a routine I have used here. The next three lines in comment show the routine advised to delay a function until after an image has loaded.
                //var img = new Image();
                //img.onload = function() { alert("Height: " + this.height); }
                //img.src = "http://path/to/image.jpg"; 
              var img = new Image();  
              img.onload = function() {
                //position the caption
                caption4.style.top=findOffset("image4")+"px"
                //I have created the function findHeight below. This takes as parameters the IDs of the <IMG> and <FIGCAPTION> elements and determines their heights and adds them and returns that result. The next line uses that result to set the height of <FIGURE>. If this is not done then the row below is placed over the caption of this row.
                document.getElementById("figure4").style.height=(findHeight("image4","caption4"))+"px"
              }
              img.src = "images/photos/Beryl%2BMum.jpg";
            </script>
            
            <script>
              //When window is resized re calculate the positioning of the figcaption

                
                var resizeTimeout4;
                window.addEventListener("resize", function(event) {
                clearTimeout(resizeTimeout4);
                resizeTimeout4 = setTimeout(function(){
                
                
                caption4.style.top=findOffset("image4")+"px"
                document.getElementById("figure4").style.height=(findHeight("image4","caption4"))+"px"
                
                
                }, 1000);
                  });  
                
                
              
            </script>
            

              
              <figure class="card" id="figure5"> 
                <img src="images/photos/Beryl%2BRex-.jpg" class="gallery" alt="Beryl crouching down, with a black and white dog." id="image5"> 
                <figcaption class="caption" id="caption5">Beryl & Rex, our companion.</figcaption>  
              </figure>
              
              <script>
                //https://stackoverflow.com/questions/2342132/waiting-for-image-to-load-in-javascript shows a routine I have used here. The next three lines in comment show the routine advised to delay a function until after an image has loaded.
                //var img = new Image();
                //img.onload = function() { alert("Height: " + this.height); }
                //img.src = "http://path/to/image.jpg"; 
              var img = new Image();  
              img.onload = function() {
                //position the caption
                caption5.style.top=findOffset("image5")+"px"
                //I have created the function findHeight below. This takes as parameters the IDs of the <IMG> and <FIGCAPTION> elements and determines their heights and adds them and returns that result. The next line uses that result to set the height of <FIGURE>. If this is not done then the row below is placed over the caption of this row.
                document.getElementById("figure5").style.height=(findHeight("image5","caption5"))+"px"
              }
              img.src = "images/photos/Beryl%2BRex-.jpg";
            </script>
            
            <script>
              //When window is resized re calculate the positioning of the figcaption

                
                var resizeTimeout5;
                window.addEventListener("resize", function(event) {
                clearTimeout(resizeTimeout5);
                resizeTimeout5 = setTimeout(function(){
                
                
                caption5.style.top=findOffset("image5")+"px"
                document.getElementById("figure5").style.height=(findHeight("image5","caption5"))+"px"
                
                
                }, 1000);
                  });  
                
                
              
            </script>
            

               
          
          
 
          
          
        </article> 
      
            <!-- Previous/Next TEST buttons -->
            <div class="buttons">
              <button type="submit" class="startGhost" disabled>Start</button><button type="submit" class="previousGhost" disabled>Previous</button>
              <span class="goTo">Go to page 
              <input type="number" name="pageNo" form="numberLower"  value="1" min="1" max="30" id="number">
              of 30
              <button type="submit" form="numberLower" name="newPage" value="pageByNumberLower" id="numberSubmit">Go</button>
              </span> 
              <button type="submit" name="newPage" value="next" class="next" form="gallery"><span class="fa fa-angle-down"></span>Next<span class="fa fa-angle-right"></span></button><button type="submit" name="newPage" value="end" class="end" form="end"><span class="fa fa-angle-double-down"></span>End<span class="fa fa-angle-double-right"></span></button> 
            </div>
    
         <!--when resizing the window the layout gets messed up so this routine checks every second and if the window has been resized it reloads.-->
    <!--    <script>      
          var resizeTimeout;
          window.addEventListener('resize', function(event) {
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(function(){
              window.location.reload();
            }, 1000);
          });  
        </script> -->
      
    </main>
    


    
        <footer class="footer">
      <p >
        Created by Daniel Jeffery
      </p>
    </footer>  </body>
       
</html> 

Explanation to come after tea!

The problem:
I wanted to display 6 pictures on a page. The resolution is very low so the full picture could be displayed on a page. So if I just set the width of the image the portrait format pictures were disproportionately large compared to the landscape ones.

So in CSS I gave the IMG boxes the same width and height eg in desktop, where there are three pics in a row -

width: calc((95vw - 30px)/3);
height: calc((95vw - 30px)/3);

But this made the layout look messy, with neither the tops nor the bottoms of the pics lining up with each other. So to the IMG tags, in CSS I added

object-fit: contain;
object-position: 50% 0%;

So this lined up the tops of the images. But as the heights where all different then there was a gap between the bottom of some images (the landscape format ones) and the figcaption.

From previous help that PaulOB has given me I realised that I could make the box that contains , ie

position: relative; 

and make the

position: absolute; 

The next step was what I don’t think CSS could deal with. I needed to know what value to use to position the in order to make it start immediately below the IMG. This is where the javascript came in.

I created a function as follows:

function findOffset(x)
        { 
          let positionOffset
          
          let width=document.getElementById(x).clientWidth        //determine the width of the image with ID of x, the width as displayed
          let natH=document.getElementById(x).naturalHeight       //determine the ORIGINAL height of the image with ID of x 
          let natW=document.getElementById(x).naturalWidth        //determine the ORIGINAL width of the image with ID of x 

          if (natW>natH){  
          positionOffset = (natH/natW)*width  //if image is in landscape orientation
            }else{
          positionOffset=width     //if image is in portrait orientation
        }
          return positionOffset
        }

I used naturalHeight and naturalWidth to obtain the dimensions of the jpg.

If the height were greater then the actual picture would fill the img box because it was in portrait format so the offset would equal the width (because the img box is square).

clientWidth is the width of the image as displayed on the page. As I had obtained the width and height of the original image then I knew its aspect ratio and could work out the pixel height of the image AS DISPLAYED - this would give me the offset that I would need to use to position the absolute positioned .

So in the HTML, after each image the PHP generates some js. The important line is:

caption0.style.top=findOffset("image0")+"px"

(the figure ‘0’ is for the first image)
This finds the height of the image displayed on the page and uses that to style the top position for the

The rest of the javascript addresses several problems that arose -

  1. The js to assess the dimensions of the image must not run until the image has finished loading
  2. If the page is resized then the offset needs to be recalculated
  3. In some circumstances the covered up the navigation buttons that were placed lower on the page, so you could see them but they didn’t react to any mouse clicks.

Hope I have explained this OK?

I am very pleased with myself!

However, if anyone can point out ways that it could be done better then I would be interested to hear.

Thank you for reading this.

1 Like

Well done and glad you learned something new as that’s how you progress. :slight_smile: It’s always good to test yourself and build things even if you don’t end up using them.

I think you can get that effect without any JS at all and without absolutely positioning the captions. I would do something like this:

https://codepen.io/paulobrien/full/bGgqpRL

It’s pretty close to yours (assuming I copied the code correctly) and in side by side tabs look much the same. I don’t expect you to change your example now but mention it as an alternative method. Indeed I may have missed something important returning to this after the long break :slight_smile:

In your example the JS makes the page very clunky (slow to react) when resizing and you get an overlap of all the content while it adjusts. There is also an issue If I resize my text (as I often do) and the text overlaps or gets cut off due to the js fixing the height of the containers.

I also note that you repeat the same big block of js each time for every image and caption which is ok for testing but you really just need the one routine and then loop through it for every image changing the ids as you go. I realise this isn’t for public consumption but it would greatly reduce the html and make things easier to manage should you add or remove images at a later date.

Anyway thanks for posting your solution :slight_smile:

3 Likes

Wow!

I’m trying to unpick what you have done. Are the only changes the following?

figcaption {
  height: auto;
  background-color: var(--backgroundColor);
  padding: 10px;
  display: table;
  margin: 0 auto 20px;
  background: #eee;
  width: 100%
}

and

img.gallery {
  max-width: 100%;
  max-height: 100%;
  max-height: calc(95vw - 30px);
  width: auto;
  height: auto;
  margin: auto;
}

I can’t work out why it works. I notice that you have two values for max-height - does it use the larger of the two for any given scenario?
I notice also, display: table;.
Could you explain why it works? It certainly seems to. My only concern would be what would happen if there were three short wide landscape pictures in a row? Would there be a large gap between the bottom of the figcaption and whatever came below?
Thank you for your time and thoughts. I am sorely tempted to alter my code, firstly to try out your suggestions for optimising the js and then to switch to your elegant CSS only approach. At least I learned a little js.

It is subtly different to yours in that I am not actually stretching the image at all.

If you take the picture “Mum all glammed” up for example then in your example the image has been stretched to twice its natural size. You have it displayed at 611 x 611 but its natural size was 300 387. That means a greater loss of quality on an already low quality image and was one of the things you were concerned about at the start. I think it looks much neater in my example at a natural size but of course there will be more space around it (which I actually think works well).

My example doesn’t stretch an image if it is smaller than the space available. However it will limit the width and height of the image if the image is greater than the space available. As the space available is basically square we can be sure that when one dimension reaches its max-height the other dimension is still contained within the area.

The first one with 100% is redundant as long as the browser understands calc. I only left it in place so I could remember what it was before. (It can be used as a fallback if required for older browsers but probably better to remove it these days.)

If I use 3 landscape pictures from your example then to my eyes it looks the same as your example. Here are screenshots of my example first and then yours.

They look much the same to me. Of course you will need to test with various sizes just to be sure that we are not missing something glaringly obvious but to me it looks a much better method.

I removed the absolute positioning from the figcaption as the position:relative and z-index on the parent is no longer needed. I also removed some duplication from your media queries as you don’t need to repeat whole blocks of code. You only need the differences and over-rides in the media query code as the existing rules still cascade. Also avoid min and max width queries at the same time because you end up having to say things twice. Effectively with min-width media queries you go up in size and all the code cascaded into the next media query as generally you will be changing the same thing. e.g. going from one column to 2 columns to three columns etc… No need to repeat the whole grid rules each time.

I suggest you fork my codepen (click fork at the bottom of the codepen screen) and then just test it out with various images from your site to make sure it behaves like you want. In that way you won’t have broken anything of yours while testing. (Codepen is free and a useful way to test demos.) Once you have forked the code I will most likely delete the codepens or change the images to random placeholder images (when I have some spare time).

2 Likes

I just used that so I could centre text using margin:auto when it doesn’t fill the whole line. In your example “Mum all Glammed up” is hanging oddly off the left side of the photo.

1 Like