10 January 2021- implementing Isotope using ImagesLoaded for synchronisation
The following thoughts are pitched squarely at those who are quite new to Web development, or just interested in learning
more about it. If you are already a seasoned Web Developer, then you probably aren't going to find it very interesting or
tile-based layouts with dynamic sorting and filtering. It is free to use for non-commercial websites under the GNU GPL license
V3. I recently incorporated it, in conjunction with several other libraries, to jazz up my Blog page. You can find it
HERE, where the aforementioned seasoned Web Developers will also find far more
detailed information by David Desandro, the founder and lead developer of the software house that made Isotope
finished my own development, or so I thought, I noticed a bug which arose when I cleared my browser cache and hence viewed the page as
if for the first time. The problem was that rather than all being lined up nicely as they should, some of the tiles were misplaced
or overlapping each other. Viewing the page any number of times subsequently, it worked just fine. Therefore, it might have been
quite easy for me to miss the bug, but all my site users would have seen it the first time they viewed the page, and first
impressions count. So, the moral of the story is, always test your page with an empty cache before pushing any changes to the Prod
As with debugging any software problem, it always pays to start by thinking about the use case i.e. in what
circumstances does the problem manifest itself. If it happens in some circumstances and not others, this can start to give you clues
about the root cause. In this case the starting point was obvious. First view - problem. Second and subsequent views - no problem.
The WWW is basically stateless, so what is the difference between the first view of a page and the second ? The Browser Cache !
For those readers who really are just beginning with Web Development, all Web Browsers cache (make a local copy of) the resources
downloaded by your page. That's things like images, scripts and css, although the precise details vary between different browsers
and browser settings. Doing this means that subsequent views of the page are faster and use less network/internet bandwidth
The next question of course is why that makes a difference. Again a little deduction, what's the practical difference between resources
being pulled from the cache vs downloaded from the Internet ? The former is much faster than the latter (which is the main reason it's
done, of course). So, the issue would appear to be one of TIMING.
So, why would a page or a component thereon behave
differently if resources loaded slowly as opposed to quickly ? To realise that, you start to need to know a bit more about how JScript
thus far involves lines of code that are executed one after the other in a predictable order, JS is not like that. It's event-driven,
meaning that different sequences of code are triggered by events (for example, an image finishes loading, or the user clicks an element
in the page) and run (at least apparently) at the same time in parallel. The order in which they run, or complete, cannot reliably be
predicted in advance. What that means is that in order to have a robust system, those individual sequences of code need to be
independent of each other - function a can run successfully before, after or while function b is running, and vice versa.
The other classic giveaway of a timing/synchronisation issue was that in fact the precise nature of the mis-alignment could actually
vary from test to test.
The way that Isotope works is that you lay out your HTML with the necessary elements - basically a
container containing sub-containers for the Isotope toolbar (the selectable list of categories) and each individual tile item, then set
up your JScript code to "isotope" them, meaning to size and lay everything out nicely. You arrange for the code to redo this each time
the filter selection changes. My problem was arising because of when in the sequence of events that initial layout was happening, which
was basically before all the images had finished loading. Because the size and layout of course depends on what is inside each
tile, if the final content is not actually there yet then obviously it cannot do a proper job of this. I needed to look again at my code
to ensure this happened AFTER I was sure that all the content had loaded. Initially, it was just happening as the last operation of the
"setup" code that was run when the page loaded. When the images were already in the cache, they loaded quickly enough for this to have
completed by the time the layout function ran. When they were not, the layout code was running before some or all of them had loaded.
There is an event fired when the window has finished loading, meaning that every image, JS file, CSS file and so on has completed
loading, which sounds like just what I wanted. However, the Metafizzy guys strongly advise that the window load event should only be used
as a last resort and that actually you should use a more tightly-scoped event. Effectively, one which is triggered when only the components
imagesloaded, which as the name suggests will detect when all images within a specified container have loaded. In the case of this specific
page, it doesn't make much difference because the only things really on the page are the Isotope tiles. However, we should always try to
build our code to be flexible in anticipation of future changes or re-use, so I did just this. The final code for doing the initial layout,
and changing it when a filter is selected, looks like this:
To explain the above, this code runs at the time that the page is loaded. For each container with the class "isotope-wrapper" (most likely
there will only be one, but you could have more than one on a page), it gets references to ($isotope) the container inside it of class "isotope box"
and ($filterCheckboxes) the set of "input" controls with type "radio". These are used within the function "applyFilter" which is then defined. This
function assigns a variable with either "*" meaning all or "[data-type="..."]" depending on which radiobutton is currently checked, then makes a
call to the isotope function via the reference to the "isotope-box" container, thus updating which items are displayed. The function "applyFilter"
is then attached to the change event of the current "isotope-wrapper", meaning the function gets called when the user clicks a radiobutton. The
remaining piece of code that is executed within this "for each isotop-wrapper" is a call to imagesLoaded, passing a callback function, which will get
executed when imagesLoaded determines that all images have been loaded. The callback function makes a call to the isotope function causing it to
lay out the boxes. Because at this point we know all images have been loaded, we can be sure that the layout will be done correctly.