@naturalcycles/js-lib
Version:
Standard library for universal (browser + Node.js) javascript
129 lines (107 loc) • 3.01 kB
text/typescript
export interface FitImagesCfg {
/**
* Container of the images
*/
containerElement: HTMLElement
/**
* Array of image metadatas (most notably: aspectRatio).
*/
images: FitImage[]
/**
* Will be called on each layout change.
* Should be listened to to update the width/height of the images in your DOM.
*/
onChange: (images: FitImage[]) => any
/**
* Max image height in pixels.
*
* @default 300
*/
maxHeight?: number
/**
* Margin between images.
*
* @default 8
*/
margin?: number
}
export interface FitImage {
src: string
/**
* width divided by height
*/
aspectRatio: number
/**
* Calculated image width to fit the layout.
*/
fitWidth?: number
/**
* Calculated image height to fit the layout.
*/
fitHeight?: number
}
/**
* Calculates the width/height of the images to fit in the layout.
*
* Currently does not mutate the cfg.images array, but DOES mutate individual images with .fitWidth, .fitHeight properties.
*
* @experimental
*/
export class ImageFitter {
constructor(cfg: FitImagesCfg) {
this.cfg = {
maxHeight: 300,
margin: 8,
...cfg,
}
this.resizeObserver = new ResizeObserver(entries => this.update(entries))
this.resizeObserver.observe(cfg.containerElement)
}
cfg!: Required<FitImagesCfg>
resizeObserver: ResizeObserver
containerWidth = -1
stop(): void {
this.resizeObserver.disconnect()
}
private update(entries: ResizeObserverEntry[]): void {
const width = Math.floor(entries[0]!.contentRect.width)
if (width === this.containerWidth) return // we're only interested in width changes
this.containerWidth = width
console.log(`resize ${width}`)
this.doLayout(this.cfg.images)
this.cfg.onChange(this.cfg.images)
}
private doLayout(imgs: readonly FitImage[]): void {
if (imgs.length === 0) return // nothing to do
const { maxHeight } = this.cfg
let imgNodes = imgs.slice(0)
w: while (imgNodes.length > 0) {
let slice: FitImage[]
let h: number
for (let i = 1; i <= imgNodes.length; i++) {
slice = imgNodes.slice(0, i)
h = this.getHeigth(slice)
if (h < maxHeight) {
this.setHeight(slice, h)
imgNodes = imgNodes.slice(i)
continue w
}
}
this.setHeight(slice!, Math.min(maxHeight, h!))
break
}
}
private getHeigth(images: readonly FitImage[]): number {
const width = this.containerWidth - images.length * this.cfg.margin
let r = 0
images.forEach(img => (r += img.aspectRatio))
return width / r // have to round down because Firefox will automatically roundup value with number of decimals > 3
}
// mutates/sets images' fitWidth, fitHeight properties
private setHeight(images: readonly FitImage[], height: number): void {
images.forEach(img => {
img.fitWidth = Math.floor(height * img.aspectRatio)
img.fitHeight = Math.floor(height)
})
}
}