Masonry / ImagesLoaded Problems

Hi all,

this is my first post, I’ve been stuck with this issue for about a year now, not getting anywhere.
I’m trying to create a personal portfolio page using the ‘Masonry gallery cards responsive’ component by Diego Meg. I’m not using the CSS only approach because I want to have newer items in the top row.

portfolio site (WIP)

After encountering the classic image overlap issue (the layout is calculated before the image sizes are known, when initially loading) I found out about imagesloaded.js and tried to integrate it.

I can’t get it to work.
The script seems to get executed (it writes the log messages) but “$grid.masonry(‘layout’);” doesn’t seem to recalculate the layout. Only reloading or resizing fixes the layout.

Is it maybe a problem that I’m using carousels instead of images in my cards?

I’d appreciate a heads-up,
thanks!

Here’s what I put in the custom code area in BSS:

<!-- Include jQuery -->
<script 
src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<!-- Include imagesLoaded script -->
<script src="https://unpkg.com/imagesloaded@5/imagesloaded.pkgd.min.js"></script>

<!-- Include Masonry script -->
<script src="https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js" integrity="sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D" crossorigin="anonymous" sync></script>

<script>
  $(document).ready(function() {
    console.log('Document is ready');

    // Initialize Masonry
    var $grid = $('.grid');
    console.log('Grid:', $grid);

    $grid.masonry({
      itemSelector: '.grid-item',
      percentPosition: true,
      columnWidth: '.grid-sizer'
    });

    console.log('Masonry initialized');

    // Ensure Masonry layout is triggered after each image loads
    $grid.imagesLoaded().progress(function() {
      console.log('Image loaded');
      $grid.masonry('layout');
      $grid.masonry();
      console.log('Masonry layout triggered');
    });
    // Safari still has overlaps, can I trigger after some time?  
    setTimeout(function() {
    $grid.masonry('layout');
    console.log('Masonry layout triggered after 2sec');
}, 2000);
    setTimeout(function() {
    $grid.masonry('layout');
    console.log('Masonry layout triggered after 4sec');
}, 4000);
    setTimeout(function() {
    $grid.masonry('layout');
    console.log('Masonry layout triggered after 6sec');
}, 6000);
    setTimeout(function() {
    $grid.masonry('layout');
    console.log('Masonry layout triggered after 8sec');
}, 8000);
    setTimeout(function() {
    $grid.masonry('layout');
    $grid.masonry();
    console.log('Masonry layout triggered after 12sec');
}, 12000);
      
      
  });
</script>

You can do something like this

2 Likes

That looks excellent, thank you @kuligaposten :sparkles:

I imported the .html into BSS and will try to replicate it!

This is the script in the example above

document.addEventListener('DOMContentLoaded', () => {
  const photoGrid = document.querySelector('.photo-grid');
  const articleGrid = document.querySelector('.article-grid');
  const gytter = document.getElementById('gytter');
  const carouselOne = document.getElementById('carousel-1');

  if (photoGrid && gytter) {
    // using bootstrat's col as item-sizes
    const photos = new Masonry(photoGrid, {
      percentPosition: true,
    });

    // There are images in the photo-grid imagesLoaded needed
    imagesLoaded(photoGrid).on('progress', () => photos.layout());

    // if carouselOne's images has different sizes
    carouselOne.addEventListener('slid.bs.carousel', () => photos.layout());

    gytter.addEventListener('click', () => {
      photoGrid.classList.toggle('gy-4', gytter.checked);
      photoGrid.classList.toggle('gx-4', gytter.checked);
      photos.layout();
    });

    window.addEventListener('resize', () => photos.layout());
  }

  if (articleGrid) {
    // no images in the article grid imagesLoaded not needed
    const articles = new Masonry(articleGrid, {
      percentPosition: true,
    });
    window.addEventListener('resize', () => articles.layout());
  }
});

You know you can do it without javascript

OMG it’s working!!

Thank you for your efforts, @kuligaposten ,
I played around with your file and had some insights,
but I would have had to restructure everything.

The CSS approach doesn’t have the item sorting ability of the javascript masonry.

So after researching for some more hours,
I found out that I was just calling the wrong elements in my existing script.
And I have to init the masonry before the imagesLoaded function.

<script> 
// init Masonry first?
$('.row').masonry();

// layout Masonry after each image loads
$('#projects-section').imagesLoaded().progress( function() {
  $('.row').masonry('layout');
});
</script>

Most solutions I found tried to call the masonry via “$grid.masonry({…”
which doesn’t seem to work the way it is set up in this particular BSS component.

Whew!

You have many errors in your HTML, check errors

In your project section, you initialize Masonry both with a data attribute and with JavaScript. Masonry and imagesLoaded do not depend on jQuery; only your inline script relies on jQuery. It should be easy to convert this to vanilla JavaScript and eliminate the need for jQuery.

You are loading imagesLoaded twice: once in a JavaScript file and again in your inline script. Additionally, you have images that are 1.5 MB in size. You could optimize these images and set a proper size for the webpage.

You have two Masonry grids on your page, which means there should normally be two global listeners from masonry.js. However, the way you set it up, you have 18 global listeners from masonry.js.

Oh man, that’s a ton of errors.
I’ll try to improve that.

That HTML check website is quite handy.
I was like, BSS check says ‘no issues’, we’re good :grinning:

Thanks for clearing up the masonry magic,
I had no idea that the data attribute is sufficient.

Some images are quite heavy right now,
I’ll be converting those PNGs to WEBP down the road,
because I need transparency.

Thank you for your time!