Search code examples
phpjquerysplidejs

jQuery Splide, responsive height based on image


We got this slide based on Splide, used as article's photo slider, with attached second instance that act as a thumbnail paginator.

At the moment, it doesn't take care of the height of the pics, but just use the height of the taller image, making the thumbnails remain down when a 16/9 image is displayed, leaving a big white space unused.

enter image description here

Even if it is an huge layout swift, we need thumbnails staying glued to the bottom of the image, sliding up or down when an image change, can we achieve this situation?

Currently, my code is:

<!-- Slideshow container -->
<section id="main-carousel" class="splide" role="group" style="margin-top: 30px" >
    <div class="splide__track">
        <ul class="splide__list">
            <?php 
                $i = 0;
                foreach(get_field('gallery') as $image ) {
                    if ($i == 0) { $active = 'active'; } else { $active = '';}
                        echo '<li class="splide__slide" data-splide-interval="3000">';
                           echo '<div class="splide__slide__container" style="max-height: fit-content>';
                                echo '<a href="' . $image .'" data-lightbox="image-' . ($i + 1) . '">';
                                    echo '<img src="' . $image . '" style="width:100%" />';
                                echo '</a>';
                            echo '</div>';
                        echo '</li>';
                        $i++;
                    }
                ?>
            </ul>
        </div>
        <br class="clear" />
    </section>
    <script>
        var splide = new Splide( '.splide' );
        splide.mount();
    </script>
    <section id="thumbnail-carousel" class="splide" style="margin-top: 10px; height: auto">
        <div class="splide__track">
            <ul class="splide__list" style="height:auto!important">
                <?php 
                    $i = 0;
                    foreach(get_field('gallery') as $image ) {
                        if ($i == 0) { $active = 'active'; } else { $active = '';}
                            echo '<li class="splide__slide" style="height:auto or 100%">';
                                echo '<img src="' . $image . '" style="width:100%; height: auto or 100%" />';
                            echo '</li>';
                            $i++;
                    }
                ?>
                    </ul>
              </div>
            
            </section>
            <style>
                .splide__list {
                    align-items: flex-start!important;
                    
                }
                .clear { clear: both; }
            </style>
            <script>
                document.addEventListener( 'DOMContentLoaded', function () {
                var main = new Splide( '#main-carousel', {
                    type      : 'loop',
                    rewind    : true,
                    pagination: false,
                    arrows    : false,
                    autoHeight: true,  
                    autoWidth: true,  
                    //autoplay : true, 
                    autoStart : true,
                    lazyLoad: true,  
                    perPage : 1,
                    perMove: 1,  
                    autoScroll: {
                        speed: 1,
                    },
                  } );

                  var thumbnails = new Splide( '#thumbnail-carousel', {
                    fixedWidth  : 100,
                    fixedHeight : 58,
                    gap         : 8,
                    rewind      : true,
                    pagination  : false,
                    isNavigation: true,
                    //autoHeight: true,
                    breakpoints : {
                      600: {
                        fixedWidth : 60,
                        fixedHeight: 44,
                      },
                    },
                  } );

                  main.sync( thumbnails );
                    
                  main.mount( window.splide.Extensions );
                  thumbnails.mount();

                } );
            </script>
    <?php } ?>              

Solution

  • You will need to dynamically change the slide height using the height of the next image before the carousel moves to keep the thumbnails 'glued' to the slider.

    Use the Splide#on() method to listen to the ready event (to apply the first slide height) and the move event (to apply the slide height when the carousel moves).

    To know which slide the carousel is moving to, use the instance property index. To change the slide height, use the property options.

    var main = new Splide('#main-carousel');
    
    main.on('ready', function() { 
      setHeightCarousel(0);  // index = 0
    })
    
    main.mount();
    
    main.on('move', function() {
      const currentIndex = main.index;
      setHeightCarousel(currentIndex);
    })
    

    And here's how setHeightCarousel(index) could look like. Note I added the class splide__img in the HTML on each <img> tag to target them.

    function setHeightCarousel(index) {
      const image = document.querySelectorAll('.splide__img')[index];
      let imgHeight;
      if (image.complete) {
          imgHeight = image.naturalHeight;
          main.options = {
            height: imgHeight + 'px'
          }        
      } else {
          image.addEventListener('load', function() {
            imgHeight = this.naturalHeight;
            main.options = {
              height: imgHeight + 'px'
            }          
          })
      }
    }
    

    Because the ready event fires before the image is loaded the function first checks whether the image is loaded, and if not adds a load event listener. A callback or promise is purposefully avoided here, but can be used to improve the code depending on the implementation.

    The naturalHeight property is used to get the intrinsic height of the image, in the presumption you might want to (down)scale the image first.

    Here's a working example in a JSFiddle using Splide V4.1.4.

    Or view it in a snippet:

    document.addEventListener('DOMContentLoaded', function() {
    
      var main = new Splide('#main-carousel', {
        type: 'fade',
        rewind: true,
        pagination: false,
        arrows: false,
        lazyLoad: 'sequential'
      })
    
      var thumbnails = new Splide('#thumbnail-carousel', {
        fixedWidth: 100,
        fixedHeight: 60,
        gap: 10,
        rewind: true,
        pagination: false,
        isNavigation: true,
        breakpoints: {
          600: {
            fixedWidth: 60,
            fixedHeight: 44,
          },
        }
      })
    
      main.on('ready', function() {
        setHeightCarousel(0);
      })
    
      main.sync(thumbnails);
      main.mount();
      thumbnails.mount();
    
      main.on('move', function() {
        const currentIndex = main.index;
        setHeightCarousel(currentIndex);
      })
    
      function setHeightCarousel(index) {
        const image = document.querySelectorAll('.splide__img')[index];
        let imgHeight;
        if (image.complete) {
            imgHeight = image.naturalHeight;
            main.options = {
              height: imgHeight + 'px'
            }        
        } else {
            image.addEventListener('load', function() {
              imgHeight = this.naturalHeight;
              main.options = {
                height: imgHeight + 'px'
              }          
            })
        }
      }
    })
    .container {
      margin: 1rem 1rem;
    }
    
    #thumbnail-carousel .splide__track .splide__list .splide__slide img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    
    /* Centers the image */
    /*
    .splide__slide img {
      width: 100%;
      height: 100%;
      object-fit: scale-down;
    } 
    */
    <script src="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/js/splide.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/css/splide.min.css">
    
    <div class="container">
      <section id="main-carousel" class="splide" aria-label="Beautiful Images">
        <div class="splide__track">
          <ul class="splide__list">
            <li class="splide__slide">
              <div class="splide__slide__container">
                <img data-splide-lazy="https://via.placeholder.com/250x140" alt="" class="splide__img">
              </div>
            </li>
            <li class="splide__slide">
              <div class="splide__slide__container">
                <img data-splide-lazy="https://via.placeholder.com/140x250" alt="" class="splide__img">
              </div>
            </li>
            <li class="splide__slide">
            <div class="splide__slide__container">
              <img data-splide-lazy="https://via.placeholder.com/250x140" alt="" class="splide__img">
            </div>
            </li>
          </ul>
        </div>
      </section>
      <section id="thumbnail-carousel" class="splide">
        <div class="splide__track">
          <ul class="splide__list">
            <li class="splide__slide">
              <img src="https://via.placeholder.com/250x140" alt="">
            </li>
            <li class="splide__slide">
              <img src="https://via.placeholder.com/140x250" alt="">
            </li>
            <li class="splide__slide">
              <img src="https://via.placeholder.com/250x140" alt="">
            </li>
          </ul>
        </div>
      </section>
    </div>

    A few notes.

    1. To make lazy load work, the img element in the slide must have the data-splide-lazy attribute that indicates the path or URL to the source file.

    2. By changing the height of the slider and therefore the position of the thumbnails you are shifting the layout. This is generally considered poor UX and can be measured using the Cumulative Layout Shift. Alternatives are to position the thumbnails on top or aside the main carousel, or by centering the 16:9 image (leaving white-space around it versus just below it).