UNPKG

@naturalcycles/js-lib

Version:

Standard library for universal (browser + Node.js) javascript

129 lines (107 loc) 3.01 kB
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) }) } }