diff --git a/src/index.ts b/src/index.ts index 60d5732..6a6af8c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,57 +1,71 @@ import { Cloudinary, Configuration, Transformation } from 'cloudinary-core' -let cl: Cloudinary | null = null +type ElementOrString = Element | string +type Size = { width: number; height: number } +type BindType = ElementOrString | true +type BindObject = BindType | { width?: BindType; height?: BindType } -export function initialize(options: Configuration.Options) { - cl = Cloudinary.new(options) +export type InitParameters = { debug?: boolean } +export type ImageParameters = { + src: string + options?: Transformation | Transformation.Options + bind?: BindObject + lazy?: boolean | IntersectionObserverInit + step?: number } -type ElementOrString = Element | string +let cl: Cloudinary | null = null +let DEBUG: boolean = false + +function log(...msg: any[]) { + if (DEBUG) console.debug(...msg) +} + +export function initialize(options: Configuration.Options, { debug }: InitParameters = {}) { + DEBUG = debug || false + cl = Cloudinary.new(options) +} const defaults: Transformation | Transformation.Options = { fetchFormat: 'auto', quality: 'auto:good', } -function calculateApproxRealSize(size: string, step = 200) { +function calculateApproxRealSize(size: string, step: number): number { const withRatio = (parseInt(size) * window.devicePixelRatio) | 0 - return withRatio - (withRatio % step) + step + const approx = withRatio - (withRatio % step) + step + log('Size', withRatio, approx, step) + return approx } -function getSizeOfElement(el: Element) { +function getSizeOfElement(el: Element, step: number): Size { const styles = window.getComputedStyle(el) + log('GetSizeOfElement', el, styles) return { - width: calculateApproxRealSize(styles.width), - height: calculateApproxRealSize(styles.height), + width: calculateApproxRealSize(styles.width, step), + height: calculateApproxRealSize(styles.height, step), } } -function getSizeOfElementOrSelector(node: ElementOrString, elOrString: ElementOrString) { +function getSizeOfElementOrSelector(node: ElementOrString, elOrString: ElementOrString, step: number): Size { if (typeof elOrString === 'string') { const search = typeof node === 'string' ? window.document.querySelector(node) : node if (!search) throw new Error('Could not find element: ' + node) const closest = search.closest(elOrString) - if (closest) return getSizeOfElement(closest) + if (closest) return getSizeOfElement(closest, step) else throw new Error('Could not find element: ' + elOrString) } else { - return getSizeOfElement(elOrString) + return getSizeOfElement(elOrString, step) } } -type BindType = ElementOrString | true -type BindObject = BindType | { width?: BindType; height?: BindType } +export function image(node: HTMLImageElement, parameters: ImageParameters) { + if (!parameters || !parameters.src) throw new Error('No url provided for cloudinary') -export type ImageParameters = { - src: string - options?: Transformation | Transformation.Options - bind?: BindObject -} - -export function image(node: HTMLImageElement, parameters?: ImageParameters) { - if (!parameters) throw new Error('No url provided for cloudinary') - - let { src, options, bind } = parameters + let { src, options, bind, lazy, step } = parameters + log('Image Declared', parameters) options = options || {} + step = step ?? 200 if (!cl) throw new Error('Cloudinary not initialized') if (!src) throw new Error('Src must be set in use:image') @@ -62,20 +76,32 @@ export function image(node: HTMLImageElement, parameters?: ImageParameters) { } if (!options.crop) options.crop = 'fill' - if (bind instanceof Element) Object.assign(options, getSizeOfElement(bind)) + if (bind instanceof Element) Object.assign(options, getSizeOfElement(bind, step)) else if (typeof bind === 'string') { - Object.assign(options, getSizeOfElementOrSelector(node, bind)) + Object.assign(options, getSizeOfElementOrSelector(node, bind, step)) } else if (typeof bind === 'object') { if (bind.width) { - options.width = getSizeOfElementOrSelector(node, bind.width === true ? node : bind.width).width + options.width = getSizeOfElementOrSelector(node, bind.width === true ? node : bind.width, step).width } if (bind.height) { - options.height = getSizeOfElementOrSelector(node, bind.height === true ? node : bind.height).height + options.height = getSizeOfElementOrSelector(node, bind.height === true ? node : bind.height, step).height } } } const all: Transformation | Transformation.Options = { ...defaults, ...options } const attrs: any = cl.imageTag(parameters.src, all).attributes() - node.src = attrs.src + const replace = () => (node.src = attrs.src) + + if (lazy && typeof IntersectionObserver !== 'undefined') { + const options: IntersectionObserverInit = lazy === true ? { rootMargin: '25%', threshold: 0 } : lazy + new IntersectionObserver((entries, observer) => { + if (entries[0].isIntersecting) { + observer.disconnect() + replace() + } + }, options).observe(node) + } else { + replace() + } }