124 lines
3.2 KiB
JavaScript
124 lines
3.2 KiB
JavaScript
export default class Gallery {
|
|
constructor(ele) {
|
|
this.ele = ele;
|
|
|
|
this.gap = 12;
|
|
this.baseHeight = 100;
|
|
this.viewportWidth = 1024;
|
|
|
|
this.grabGap();
|
|
this.grabBaseHeight();
|
|
this.grabViewportWidth();
|
|
|
|
this.registerViewport();
|
|
this.draw();
|
|
}
|
|
|
|
// Extract gap values
|
|
grabGap() {
|
|
// Simple implementation to guess from padding values
|
|
const px = window.getComputedStyle(this.ele).getPropertyValue('padding-left').replace('px', '');
|
|
this.gap = parseInt(px) * 2 || 0;
|
|
}
|
|
|
|
// Extract baseHeight
|
|
grabBaseHeight() {
|
|
const px = window.getComputedStyle(this.ele).getPropertyValue('--gallery-base-height').replace('px', '');
|
|
this.baseHeight = parseInt(px) || 0;
|
|
}
|
|
|
|
// Extract viewport width
|
|
grabViewportWidth() {
|
|
this.viewportWidth = this.ele.clientWidth;
|
|
}
|
|
|
|
// Inform our engine of viewport width
|
|
registerViewport() {
|
|
window.addEventListener('resize', () => {
|
|
if (this.viewportWidth == window.innerWidth) {
|
|
return;
|
|
}
|
|
this.recompute();
|
|
});
|
|
window.addEventListener('load', () => {
|
|
this.recompute();
|
|
});
|
|
setInterval(this.recompute.bind(this), 500);
|
|
}
|
|
|
|
recompute() {
|
|
this.grabGap();
|
|
this.grabBaseHeight();
|
|
this.grabViewportWidth();
|
|
this.draw();
|
|
}
|
|
|
|
|
|
// Calculate dimensions and draw them
|
|
draw() {
|
|
const elements = this.ele.querySelectorAll('.gallery-item');
|
|
const rects = Array.from(elements).map(element => parseFloat(element.dataset.ar));
|
|
const dimensions = this.layout(rects);
|
|
|
|
const galleryItemEles = this.ele.querySelectorAll('.gallery-item');
|
|
for (const [index, ele] of galleryItemEles.entries()) {
|
|
const dimension = dimensions[index];
|
|
if (!dimension) {
|
|
console.error(`Missing dimensions for element at ${index}`);
|
|
return;
|
|
}
|
|
for (const imgEle of ele.querySelectorAll('img')) {
|
|
imgEle.style.height = `${Math.floor(dimension.h)}px`;
|
|
imgEle.style.width = `${dimension.w}px`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform layout
|
|
layout(rects) {
|
|
let dimensions = [];
|
|
|
|
let currentWidth = this.gap;
|
|
let startIndex = 0;
|
|
|
|
// Behave like a browser: try to fit as many in a row with baseHeight
|
|
for (let index = 0; index < rects.length; index++) {
|
|
// Add this rectangle width
|
|
const currentRectWidth = (this.baseHeight * rects[index]);
|
|
currentWidth += currentRectWidth + this.gap;
|
|
|
|
if (currentWidth > this.viewportWidth) {
|
|
console.error("TODO: fix cases where viewport smaller than image");
|
|
break;
|
|
}
|
|
|
|
// Get the next width to add
|
|
const hasNextRect = index + 1 < rects.length;
|
|
const nextRectWidth = hasNextRect ? (this.baseHeight * rects[index+1]) : 0;
|
|
const nextRectOverflow = currentWidth + nextRectWidth + this.gap > this.viewportWidth;
|
|
|
|
// If the next width is too wide, resolve the current rectangles
|
|
if (!hasNextRect || nextRectOverflow) {
|
|
const gapTotal = this.gap * (index - startIndex + 2);
|
|
let scale = (this.viewportWidth - gapTotal) / (currentWidth - gapTotal);
|
|
if (!hasNextRect) {
|
|
scale = Math.min(1.8, scale);
|
|
}
|
|
const rectHeight = this.baseHeight * scale;
|
|
|
|
// Scale up every previous rectangle
|
|
for (; startIndex <= index; startIndex++) {
|
|
dimensions.push({
|
|
h: rectHeight,
|
|
w: rectHeight * rects[startIndex],
|
|
})
|
|
}
|
|
currentWidth = this.gap;
|
|
}
|
|
}
|
|
|
|
return dimensions;
|
|
}
|
|
|
|
}
|