173 lines
3.9 KiB
JavaScript
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`;
|
|
}
|
|
}
|
|
}
|
|
}
|