I've been wanting to post something for a while but have never really done it before, it's quite scary. Anyway, let's dive into it!
A while back I created a simple plugin to recreate the google images sliding effect. It turned out to be rather popular (> 450 stars) and evolved quite a bit from its infancy.
Recently, I've been getting messages asking to remove JQUERY as a dependency or if there was a JS version. I thought it would be interesting/challenging to test myself to redo it with the least amount of dependencies possible, and in plain javascript.
You can find the result here: GridderJS
And here is a demo: Demo
Overall features
First I had to plan what features I wanted to keep/update:
- Multiple instances on the same page
- Easy to use and customize with CSS & HTML
- Any number of columns
- Expanding preview with static HTML content or dynamic (fetch) HTML content
- Responsive
- Smooth Scrolling
- Old / New Google Images display (expander underneath grid or as a sidebar on the right-hand side)
- Lightweight
- Work out of the box with zero user CSS needed
- Multiple themes
- Plays well with other plugins
Things I learned/struggled with
Website I used heavily
I'm not very familiar with using javascript for dom manipulation, and this website made it very easy to do just that.
Parcel.js is awesome
Using parcel.js made it a breeze to set up a build process directly in my package.json file as seen in the snippet below. It generates the plugin as a ES6 module & CommonJS module & a standalone js file. Everything is minified and compressed automatically. I barely scratch the surface of what's is capable of doing, and cannot wait to use it again.
{
"source": "src/gridder-js.js",
"main": "dist/gridder-js.js",
"module": "dist/gridder-js.mjs",
"standalone": "dist/gridder-js-min.js",
"targets": {
"main": {
"source": "src/gridder-js.js"
},
"module": {
"source": "src/gridder-js.js"
},
"standalone": {
"source": "tool/gridderjs-global.js",
"outputFormat": "global",
"optimize": true
},
"demo": {
"source": "demo/demo.scss"
}
},
"scripts": {
"watch": "parcel watch",
"build": "parcel build"
},
}
How I dealt with the grid
Google Images now show expander on the right-hand side instead of underneath the current row of the selected image so I had to build in an option to toggle between each layout which was not as simple as I originally thought. But with a minimum amount of if's and else + minimum amount of CSS, it's now working quite well,
Basic columns
Via the columns option, it's very easy to specify how many columns you want. This was easily achieved using CSS grids
display: 'grid';
grid-template-columns: 'repeat('+this.options.columns+', 1fr)';
Display Bottom - Old style
The important thing on this one was to have the below on the parent element
display: 'grid';
grid-auto-flow: 'row dense';
grid-template-rows= 'min-content';
Display Right - New style
For this option, I had to restructure the JS to allow appending a sidebar at 30% while reducing the main content to 70%. Additionally, I added the below to the sidebar to make it sticky:
position: sticky;
min-height: 100vh;
I could not find a way to have a right-hand sidebar within the CSS grid
Private & Public functions
Thanks to the addition of Private class features, I can have a #functionName, and call it with this.#functionName.
I could very easily expose any public function to users, in my case, I added an update(options) & destroy() method.
Adding a responsive option was hard
Previously, I was letting the user use CSS media queries to achieve whatever grid they wanted, but many people wanted a more straightforward option
Finding an easy way to implement responsiveness turned out to be rather difficult, however, I'm pretty happy with the results I came up with.
{
// default options
columns: 4,
gap: 15,
// responsive breakpoints
// must be from highest to lowest width
breakpoints: {
960: {
columns: 4,
gap: 15,
},
700: {
columns: 3,
gap: 5,
},
400: {
columns: 2,
gap: 5,
display: 'bottom',
}
},
}
With the information above I create an array of breakpoints available, then I have a listener that triggers on window resize, finds the closest breakpoint, and call an update() with the provided options.
// Resize Event capture every 500ms
var resizeTimer;
window.addEventListener('resize', function(e){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
GridderJS.resizeGridder();
}, 500);
});
// Resize all gridder instances.
// a bit ugly, but works: to be refactored once I find a better way to make it work. suggestions, anyone?
GridderJS.resizeGridder = function () {
// get window width
let wWidth = window.innerWidth;
// iterate through each instance
GridderJS.instances.forEach((gridder) => {
// if no additional breakpoint is specified, then skip
if(gridder.breakpoints.length < 2) return false;
// else iterate through all breakpoint options
for(const n in gridder.breakpoints) {
// skip first
if(n > 0){
let breakpoint = gridder.breakpoints[n];
// update
if(wWidth <= breakpoint[0]){
gridder.update(breakpoint[1]);
break;
}
}
// if all else fails use the default
gridder.update(gridder.breakpoints[0][1]);
};
});
};
Working with other plugins
I've added a few callbacks which allow users to easily interact with other plugins, the demo link shows how I use lazyload.js to lazy load all images.
// just call the init function passed in the user options
this.call.option.init();
// init callback
var gridder = new GridderJS('.gridder', {
'columns': 8,
'gap': 15,
'init': function(el){
var myLazyLoad = new LazyLoad({
container: this.element
});
},
});
// update callback
gridder.update({ 'columns': 4 });
// destroy callback
gridder.destroy();
Things I'm happy with
- Overall features were all implemented
- Nearly zero dependencies (just-extend)
- Performant
- Lightweight (2.6 kB gzipped)
Ending Notes
That's it, we went through some of the things I struggled with & things I learned along the way.
I could probably improve the plugin massively and refactor it to infinity, but I'm very happy with the current result and performance.
I hope you enjoyed reading this article.
Cheers,
Orion
Top comments (0)