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

135 lines
3.0 KiB
JavaScript

class LayoutEngine {
rects = [];
gap = 6.28333 * 2;
baseHeight = 200;
// maxHeight = 320;
viewportWidth = 1024;
constructor() {
}
// 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 heights for each rectangle
calculate() {
let heights = [];
let currentWidth = this.gap;
let currentIndex = 0;
// 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 = Math.ceil(this.baseHeight * this.rects[index].ar) + this.gap;
// If the next width is too wide, resolve the current rectangles
console.debug(currentWidth, rectWidth);
if (currentWidth + rectWidth > this.viewportWidth) {
const gapTotal = this.gap * (index - currentIndex + 1);
const widthScale = (this.viewportWidth - gapTotal) / (currentWidth - gapTotal);
const heightScale = widthScale;
// Scale up every previous rectangle
const rectHeight = this.baseHeight * widthScale;
for (; currentIndex < index; currentIndex++) {
heights.push(rectHeight);
}
currentWidth = this.gap;
}
currentWidth += rectWidth;
}
// Set remainder to a decent height
for (; currentIndex < this.rects.length; currentIndex++) {
heights.push(this.baseHeight)
}
return heights;
}
/*
* 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.registerViewport();
this.registerInitial();
this.draw();
}
// Inform our engine of viewport width
registerViewport() {
window.addEventListener('resize', () => {
if (this.layoutEngine.viewportWidth == window.innerWidth) {
return;
}
this.layoutEngine.viewportWidth = window.innerWidth;
//this.layoutEngine.scheduleUpdate();
});
}
// Register initial elements
registerInitial() {
const galleryItemEles = this.ele.querySelectorAll('.gallery-item');
galleryItemEles.forEach(ele => {
const ar = ele.dataset.ar;
this.layoutEngine.insert(ar);
});
}
// Calculate heights and draw them
draw() {
const heights = this.layoutEngine.calculate();
const galleryItemEles = this.ele.querySelectorAll('.gallery-item');
galleryItemEles.forEach((ele, index) => {
const height = heights[index];
if (!height) {
console.error(`Missing height for element at ${index}`);
return;
}
ele.querySelectorAll("img").forEach(ele => {
ele.style.height = `${height}px`;
});
});
}
}