main/ImageContainer.js

import customEvent from '../utils/customEvent';
import InView from '../utils/classes/InView';

/**
 * Image Container that loads a background element to pad white space if image does not match
 * container aspect ratio
 *
 * @module ImageContainer
 * @prop {string} selector - selector for HTML elements for loading background image to pad
 * @prop {string} imageCss - image css class
 * @prop {string} backgroundCss - background css class
 */
const ImageContainer = {
	selector: '.c-imageContainer',

	backgroundCss: 'c-imageContainer__bg',

	imageCss: 'c-imageContainer__image',

	fitByWidthCss: 'c-imageContainer--fitWidth',

	fitByHeightCss: 'c-imageContainer--fitHeight',

	watcher: false,

	/**
	 * Initialize InView watcher for handling lazy load image / iframes
	 *
	 * @method init
	 */
	init( $elems = false ) {
		const $targets = $elems || document.querySelectorAll( this.selector );

		// Set up in view for loading a background image
		if ( ! this.watcher ) {
			this.watcher = new InView({
				threshold: 0.1,
				rootMargin: `${window.innerHeight * 0.4}px`,
			});
			this.watcher.init();
		}

		[].forEach.call( $targets, ( $elem ) => {
			const $image = $elem.querySelector( 'img' );
			if ( $image ) {
				$image.classList.add( this.imageCss );
				const fitted = $elem.classList.contains( this.fitByWidthCss )
					|| $elem.classList.contains( this.fitByHeightCss );
				if ( ! fitted && $image.dataset.height < $image.dataset.width ) {
					// Landscape image with a ratio not matching container,
					// render image width top and bottom cropped
					const imageRatio = parseFloat( $image.dataset.height / $image.dataset.width );
					if ( imageRatio > parseFloat( $image.dataset.ratio ) ) {
						$elem.classList.add( this.fitByWidthCss );
					}
				} else {
					// Pad with a blurred background image to fill empty space in container
					const src = $image.getAttribute( 'data-src' ) || $image.getAttribute( 'src' );
					if ( src ) {
						const $background = document.createElement( 'div' );
						$background.classList.add( this.backgroundCss );
						$background.dataset.background = 'true';
						$elem.appendChild( $background );

						$background.addEventListener( customEvent.IN_VIEW, ( event ) => {
							if ( event && event.detail && event.detail.isInView ) {
								this.setBackground( event.currentTarget, src );
								this.watcher.stopWatching( event.currentTarget );
							}
						});
						this.watcher.startWatching( $background );
					}
				}
			}
		});
	},

	/**
	 * Set background image of an html element
	 *
	 * @method setBackground
	 * @param {element} $target
	 * @param {string} src
	 */
	setBackground( $target, src ) {
		$target.setAttribute( 'style', `background-image: url(${src});` );
	},
};

export default ImageContainer;