Even-Edge Vertical Masonry in Squarespace Gallery Pages

Skip down to the bottom if you just want the code and the instructions.

When I wanted to redo my website, I did a lot of research into which platform I should use. I think I looked at Smugmug, Pixpa, Squarespace, Weebly, Wix, and Wordpress. It came down to a tough decision between Pixpa and Squarespace, and honestly, for most landscape photographers, I would actually recommend Pixpa (may write about why I think it’s pretty awesome later on). But I ended up choosing Squarespace (SQS) because I though it would give me a little more possibility to improve/modify/hack SQS to behave the way that I want it.

The first thing that I wanted to hack was Squarespace’s uneven vertical masonry edges. By “vertical masonry”, I mean a layout of images where the images are arranged in multiple columns, stacked on top of each other. If you look at any of the Photography pages on my site, the photos always are fit into a perfect rectangle, where both the top and bottom edges are aligned. You can even make your browser window bigger or smaller and it will adapt. Here’s an image of the “Yukon 2018” gallery for example:

Both the bottom and top edges are aligned, so the images form a rectangle.

Both the bottom and top edges are aligned, so the images form a rectangle.

This is not true for SQS by default, or most vertical masonry layouts.

For instance, Ted Gore’s website uses vertical masonry in Squarespace, here’s an example of one of his gallery pages: http://www.tedgorecreative.com/recentwork. Benjamin Everett uses vertical masonry with Smugmug, and same deal: https://www.benjamineverett.com/Galleries/Mountains. If you look at the bottom edge of those galleries, the images don’t line up.

By the way, given that those are two of my favorite photographers in the world, I wouldn’t be surprised if you got lost on their websites and forget about this blog post.

Anyway, this happens because the width of all columns are equal, so depending on what images get put into each column, the heights are usually not equal. So I wrote a little bit of code that alters the width of the columns, in order to make the height of all columns equal, and I’m sharing it here in case anyone else wants to use it.

Installing This On Your Squarespace Site

If you want to install this on your own Squarespace Gallery Page, all you need to do is copy this code and paste it in your Gallery Page. You can either do this in the Page Header Code Injection, or you can add a “Code Block” into the Intro section for the page.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script>
!function(){let e=function(e){let t=function(e){return parseInt($(e).css("left"))},n=function(e){var t=function(e){var t=$(e).find("img").attr("data-image-dimensions").split("x");return[parseInt(t[0]),parseInt(t[1])]}(e);return t[0]/t[1]};var r=function(e){for(var n=e.getElementsByClassName("slide sqs-gallery-design-autocolumns-slide"),r=[],l=[],o=0;o<n.length;o++)if(node=n[o],offset=t(node),0==l.length||offset>l[l.length-1])l.push(offset),r.push([node]);else for(var a=0;a<l.length;a++)if(offset===l[a]){r[a].push(node);break}return r}(e);if(r.length<=1)return;var l=function(e){return t(e[1][0])-(n=e[0][0],parseInt($(n).css("width")))-t(e[0][0]);var n}(r),o=$(".gallery-wrapper").width();const[a,i]=function(e,t,r){for(var l=function(e){for(var t=e.length,r=new Array(t),l=0;l<t;l++){for(var o=0,a=0;a<e[l].length;a++)o+=1/n(e[l][a]);r[l]=1/o}return r}(e),o=e.length,a=0,i=0,s=0;s<o;s++)i+=l[s],a+=l[s]*(e[s].length-1);var d=t-(o-1)*r,f=Math.floor((d+a*r)/i),p=[];for(s=0;s<o;s++){var h=f-r*(e[s].length-1);p.push(Math.round(h*l[s]))}return[f,p]}(r,o,l);for(var s=function(e,t,r,l){for(var o=new Array(t.length),a=0,i=0;i<e.length;i++){o[i]=new Array(e[i].length);for(var s=0,d=0;d<e[i].length;d++){var f=n(e[i][d]),p=t[i]/f;d==e[i].length-1&&(s=r-p),o[i][d]=[s,a,t[i],p],s+=p+l}a+=t[i]+l}return o}(r,i,a,l),d=0;d<r.length;d++)for(var f=0;f<r[d].length;f++)node=$(r[d][f]),nodeVals=s[d][f],node.css("top",nodeVals[0]+"px"),node.css("left",nodeVals[1]+"px"),node.css("width",nodeVals[2]+"px"),node.css("height",nodeVals[3]+"px");$(".sqs-gallery-design-autocolumns").css("height",a+"px")},t=function(t){var n=$(".gallery-wrapper").width();e(window.document),n!=$(".gallery-wrapper").width()&&e(window.document)};Y.SQS.Gallery.ResizeEmitter.prototype._oldHandleResize||(Y.SQS.Gallery.ResizeEmitter.prototype._oldHandleResize=Y.SQS.Gallery.ResizeEmitter.prototype._handleResize),Y.SQS.Gallery.ResizeEmitter.prototype._handleResize=function(){this._oldHandleResize(),t(window.document)},Y.SQS.Gallery.Gallery2.prototype._oldInitializer||(Y.SQS.Gallery.Gallery2.prototype._oldInitializer=Y.SQS.Gallery.Gallery2.prototype.initializer),Y.SQS.Gallery.Gallery2.prototype.initializer=function(){this._oldInitializer(),t(window.document)}}();
</script>

A few caveats worth mentioning:

  • My website is using a Squarespace 7.0 template from the Brine family (the “Pedro” template, specifically). I don’t know if it will work in any other case. But if you’re trying to get it to work and it doesn’t, leave me a comment and I’ll try to take a look!

  • Your Gallery should already be configured as vertical masonry before adding this code. In Squarespace’s Design -> Site Styles Section, the gallery Layout should be “Grid”, and the Aspect Ratio should be “Auto”. If your site isn’t set up like that, I don’t know what the code will do.

  • This is for Gallery Pages, not Gallery Blocks.

  • Custom JavaScript code won’t work if you use AJAX loading on your site.

If you'd like to view the original code, for instance if you're curious how it works, then expand this!
(function() {
let resizeGallery = function(document) {
	let getLeft = function(node) {
		return parseInt($(node).css("left"));
	}

	let getWidth = function(node) {
		return parseInt($(node).css("width"));
	}

	let getImageNodesByCol = function(document) {
		var nodes = document.getElementsByClassName("slide sqs-gallery-design-autocolumns-slide");
		var nodesByCol = [];
		var offsets = [];
		for (var nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
			node = nodes[nodeIndex];
			offset = getLeft(node);
			if (offsets.length == 0 || offset > offsets[offsets.length - 1]) {
				offsets.push(offset);
				nodesByCol.push([node]);
			} else {
				for (var colIdx = 0; colIdx < offsets.length; colIdx++) {
					if (offset === offsets[colIdx]) {
						nodesByCol[colIdx].push(node)
						break;
					}
				}
			}
		}

		return nodesByCol;
	}

	let getPadding = function(nodesByCol) {
		return getLeft(nodesByCol[1][0]) - getWidth(nodesByCol[0][0]) - getLeft(nodesByCol[0][0]);
	}

	let getNodeImgDimensions = function(node) {
		var dimStr = $(node).find("img").attr("data-image-dimensions").split("x");
		return [parseInt(dimStr[0]), parseInt(dimStr[1])];
	}

	let getNodeAspectRatio = function(node) {
		var dimensions = getNodeImgDimensions(node);
		return dimensions[0] / dimensions[1];
	}

	let getAspectRatioByColumn = function(nodesByCol) {
		var numCols = nodesByCol.length;
		var aspectRatioByColumn = new Array(numCols);
		for (var col = 0; col < numCols; col++) {
			var sumNormalizedHeight = 0;
			for (var nodeIndex = 0; nodeIndex < nodesByCol[col].length; nodeIndex++) {
				sumNormalizedHeight += 1.0 / getNodeAspectRatio(nodesByCol[col][nodeIndex]);
			}
			aspectRatioByColumn[col] = 1.0 / sumNormalizedHeight;
		}

		return aspectRatioByColumn;
	}

	let getColumnSizes = function(nodesByCol, totalWidth, padding) {
		var aspectRatioByColumn = getAspectRatioByColumn(nodesByCol);

		var numCols = nodesByCol.length;
		var sumAdjAspectRatio = 0.0;
		var sumAspectRatio = 0.0;
		for (var col = 0; col < numCols; col++) {
			sumAspectRatio += aspectRatioByColumn[col];
			sumAdjAspectRatio += aspectRatioByColumn[col] * (nodesByCol[col].length - 1);
		}

		var adjustedWidth = totalWidth - (numCols - 1)*padding;
		var finalHeight = Math.floor((adjustedWidth + sumAdjAspectRatio*padding) / sumAspectRatio);

		var widthByCol = []
		for (var col = 0; col < numCols; col++) {
			var heightNoPad = finalHeight - padding*(nodesByCol[col].length - 1);
			widthByCol.push(Math.round(heightNoPad * aspectRatioByColumn[col]));
		}
		return [finalHeight, widthByCol];
	}

	let getNodeValuesByColumn = function(nodesByCol, widthByCol, totalHeight, padding) {
		var nodeValsByCol = new Array(widthByCol.length);
		var leftOffset = 0;
		for (var col = 0; col < nodesByCol.length; col++) {
			nodeValsByCol[col] = new Array(nodesByCol[col].length);
			var topOffset = 0.0;
			for (var nodeIdx = 0; nodeIdx < nodesByCol[col].length; nodeIdx++) {
				var aspectRatio = getNodeAspectRatio(nodesByCol[col][nodeIdx]);
				var nodeHeight = widthByCol[col] / aspectRatio;
				if (nodeIdx == nodesByCol[col].length - 1) {
					topOffset = (totalHeight - nodeHeight);
				}
				nodeValsByCol[col][nodeIdx] = [topOffset, leftOffset, widthByCol[col], nodeHeight];

				topOffset += nodeHeight + padding;
			}
			leftOffset += widthByCol[col] + padding;
		}

		return nodeValsByCol;
	}

	var nodesByCol = getImageNodesByCol(document);
	if (nodesByCol.length <= 1) {
		return;
	}
	var padding = getPadding(nodesByCol);
	var totalWidth = $(".gallery-wrapper").width();
	const [totalHeight, widthByCol] = getColumnSizes(nodesByCol, totalWidth, padding);

	var nodeValsByCol = getNodeValuesByColumn(nodesByCol, widthByCol, totalHeight, padding);

	for (var col = 0; col < nodesByCol.length; col++) {
		for (var nodeIdx = 0; nodeIdx < nodesByCol[col].length; nodeIdx++) {
			node = $(nodesByCol[col][nodeIdx]);
			nodeVals = nodeValsByCol[col][nodeIdx];
			node.css("top", nodeVals[0] + "px");
			node.css("left", nodeVals[1] + "px");
			node.css("width", nodeVals[2] + "px");
			node.css("height", nodeVals[3] + "px");
		}
	}

	$(".sqs-gallery-design-autocolumns").css("height", totalHeight + "px");
}

let resizeGalleryWithScrollbar = function(document) {
	var totalWidthStart = $(".gallery-wrapper").width();
	resizeGallery(window.document);
	// Kind of an ugly hack, but if the resizing eliminated the scrollbar, the width changes and we need
	// to recalculate.
	if (totalWidthStart != $(".gallery-wrapper").width()) {
		resizeGallery(window.document);
	}
}

if (!Y.SQS.Gallery.ResizeEmitter.prototype._oldHandleResize) {
	Y.SQS.Gallery.ResizeEmitter.prototype._oldHandleResize = Y.SQS.Gallery.ResizeEmitter.prototype._handleResize;
}
Y.SQS.Gallery.ResizeEmitter.prototype._handleResize = function() {
	this._oldHandleResize();
	resizeGalleryWithScrollbar(window.document);
}
if (!Y.SQS.Gallery.Gallery2.prototype._oldInitializer) {
	Y.SQS.Gallery.Gallery2.prototype._oldInitializer = Y.SQS.Gallery.Gallery2.prototype.initializer;
}
Y.SQS.Gallery.Gallery2.prototype.initializer = function() {
	this._oldInitializer();
	resizeGalleryWithScrollbar(window.document);
}
})();