1
0
Fork 0
photos/web/shared/js/gallery.js

173 lines
3.9 KiB
JavaScript

class LayoutEngine {
/*
rects = [];
gap = 12;
baseHeight = 100;
// maxHeight = 320;
viewportWidth = 1024;
*/
constructor() {
this.rects = [];
this.gap = 12;
this.baseHeight = 100;
this.viewportWidth = 1024;
}
// Register a new rectangle with an aspect ratio of ar at index
insert(ar=1, index=-1) {
const rect = {
ar: ar,
};
if (index == -1) {
this.rects.push(rect);
} else {
this.rects.splice(index, 0, rect);
}
}
// Unregister a new rectangle at index
pop(index=-1) {
// TODO
}
// Generate a list of dimensions for each rectangle
calculate() {
let dimensions = [];
let currentWidth = this.gap;
let currentIndex = 0;
let lastHeight = this.baseHeight * 1.2;
// Behave like a browser: try to fit as many in a row with baseHeight
for (let index = 0; index < this.rects.length; index++) {
// Get the next width to add
const rectWidth = (this.baseHeight * this.rects[index].ar) + this.gap;
// If the next width is too wide, resolve the current rectangles
if (currentWidth + rectWidth > this.viewportWidth) {
const gapTotal = this.gap * (index - currentIndex + 1);
const scale = (this.viewportWidth - gapTotal) / (currentWidth - gapTotal);
const rectHeight = this.baseHeight * scale;
lastHeight = rectHeight;
// Scale up every previous rectangle
for (; currentIndex < index; currentIndex++) {
dimensions.push({
h: rectHeight,
w: rectHeight * this.rects[currentIndex].ar,
})
}
currentWidth = this.gap;
}
currentWidth += rectWidth;
}
// Set remainder to a decent height
for (; currentIndex < this.rects.length; currentIndex++) {
dimensions.push({
h: lastHeight,
w: lastHeight * this.rects[currentIndex].ar,
})
}
return dimensions;
}
/*
* Code for a future implementation that emits events
update() {
console.debug('updating layout');
}
// Schedule an update at maximum 10Hz
scheduleUpdate() {
if (this.scheduledUpdate) {
return;
}
this.scheduledUpdate = setTimeout(() => {
this.scheduledUpdate = null;
this.update();
}, 100);
}
*/
}
export default class Gallery {
constructor(ele) {
this.ele = ele;
this.layoutEngine = new LayoutEngine();
this.grabGap();
this.grabBaseHeight();
this.grabViewportWidth();
this.registerViewport();
this.registerInitial();
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.layoutEngine.gap = parseInt(px) * 2 || 0;
}
// Extract baseHeight
grabBaseHeight() {
const px = window.getComputedStyle(this.ele).getPropertyValue('--gallery-base-height').replace('px', '');
this.layoutEngine.baseHeight = parseInt(px) || 0;
}
// Extract viewport width
grabViewportWidth() {
this.layoutEngine.viewportWidth = window.innerWidth;
}
// Inform our engine of viewport width
registerViewport() {
window.addEventListener('resize', () => {
if (this.layoutEngine.viewportWidth == window.innerWidth) {
return;
}
this.grabBaseHeight();
this.grabViewportWidth();
//this.layoutEngine.scheduleUpdate();
this.draw();
});
}
// Register initial elements
registerInitial() {
const galleryItemEles = this.ele.querySelectorAll('.gallery-item');
for (const ele of galleryItemEles) {
const ar = ele.dataset.ar;
this.layoutEngine.insert(ar);
}
}
// Calculate dimensions and draw them
draw() {
const dimensions = this.layoutEngine.calculate();
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 = `${dimension.h}px`;
imgEle.style.width = `${dimension.w}px`;
}
}
}
}