import animateMove from '../utils/animation/animateMove';
import customEvent from '../utils/customEvent';
/**
* The ImageViewer module allows article images to render in full screen mode.
*
* @module ImageViewer
* @prop {Object} selectors - DOM element selectors
* @prop {Object} states - different css states
* @prop {String} template - template used for populating the image viewer
* @prop {HTMLElement} $viewer - the current image viewer
* @prop {HTMLElement} $currentImage - the current image selected to be shown in the image viewer
* @prop {HTMLElement} $body - the body element
* @prop {HTMLElement} $html - the root HTML Element
* @prop {function} keyboardListener - keyboard key up listener
* @prop {integer} maxWidth - maximum width for an image in the image viewer
*/
const ImageViewer = {
selectors: {
figure: '.c-figure--expandable:not([data-in-gallery])',
expand: '.c-figure__expand',
image: '.c-figure__image',
viewer: '.c-imageViewer',
fullImage: '.c-imageViewer__image',
},
states: {
noScroll: 'is-no-scroll',
noSmoothScroll: 'is-no-smooth-scroll',
animateOut: 'c-imageViewer--animateOut',
animateIn: 'c-imageViewer--animateIn',
hidden: 'c-imageViewer--hidden',
portrait: 'c-imageViewer__image--portrait',
loading: 'c-imageViewer--loading',
},
template: `
<div class="c-imageViewer__inner">
<div class="c-imageViewer__loader c-loader c-loader--small"></div>
<img src="" class="c-imageViewer__image" />
</div>
`,
$body: false,
$viewer: false,
$currentImage: false,
keyboardListener: false,
maxWidth: 2048,
/* global gn_analytics */
/**
* Select expandable images and set up click listeners to trigger the image viewer.
*
* @method init
*/
init() {
const $figures = document.querySelectorAll( this.selectors.figure );
[].forEach.call( $figures, ( $figure ) => {
const $expand = $figure.querySelector( this.selectors.expand );
const $image = $figure.querySelector( this.selectors.image );
const clickListener = this.initializeFullScreen.bind( this, $image );
$expand.addEventListener( 'click', clickListener );
});
this.$body = document.querySelector( 'body' );
this.$html = document.querySelector( 'html' );
},
/**
* Initialize the image viewer for viewing the selected image in full screen mode.
*
* @param {HTMLElement} $image - the selected image to be shown in full screen mode
* @param {Event} evt - click event
*
* @method initializeFullScreen
*/
initializeFullScreen( $image, evt ) {
evt.preventDefault();
// Bail if no image source is found.
let imageSrc = $image.dataset.src || $image.src;
if ( ! imageSrc ) {
return;
}
// Set maximum width
imageSrc = imageSrc.replace( /\?.+/, '' );
imageSrc = `${imageSrc}?w=${this.maxWidth}`;
// Set current image.
this.$currentImage = $image;
// Create image viewer UI, add click listener.
this.$viewer = document.createElement( 'a' );
this.$viewer.href = '';
this.$viewer.title = 'Close image';
this.$viewer.dataset.trackaction = 'image | close';
this.$viewer.dataset.trackRegion = 'Content';
this.$viewer.classList.add( this.selectors.viewer.replace( '.', '' ) );
this.$viewer.classList.add( this.states.animateIn );
this.$viewer.classList.add( this.states.loading );
this.$viewer.innerHTML = this.template;
this.$viewer.addEventListener( 'click', clickEvt => this.close( clickEvt ) );
// Key listeners.
this.keyboardListener = this.interactWithKeyboard.bind( this );
document.addEventListener( 'keyup', this.keyboardListener );
const $fullImage = this.$viewer.querySelector( this.selectors.fullImage );
$fullImage.addEventListener( 'load', () => this.imageLoad() );
$fullImage.src = imageSrc;
$fullImage.alt = $image.alt;
if ( parseInt( $image.width, 10 ) < parseInt( $image.height, 10 ) ) {
$fullImage.classList.add( this.states.portrait );
}
// Add the image viewer to the page.
this.$body.appendChild( this.$viewer );
this.$body.classList.add( this.states.noScroll );
this.$html.classList.add( this.states.noSmoothScroll );
// Track
this.track( evt.currentTarget );
},
/**
* Flag an image as being loaded
*
* @param {HTMLElement} $image - the image that has been loaded
*
* @method imageLoad
*/
imageLoad() {
if ( this.$viewer ) {
this.$viewer.classList.remove( this.states.loading );
}
},
/**
* Close the image viewer with ESC key.
*
* @param {Event} evt - key up event
*
* @method interactWithKeyboard
*/
interactWithKeyboard( evt ) {
switch ( evt.keyCode ) {
case 27: // ESC
this.$viewer.click();
break;
default:
break;
}
},
/**
* Close the image viewer with an animation.
*
* @param {Event} evt - mouse click event.
*
* @method close
*/
close( evt ) {
evt.preventDefault();
if ( this.$viewer ) {
document.removeEventListener( 'keyup', this.keyboardListener );
// Reset to original states.
this.$viewer.classList.remove( this.states.animateIn );
this.$body.classList.remove( this.states.noScroll );
this.$html.classList.remove( this.states.noSmoothScroll );
this.$viewer.classList.add( this.states.hidden );
// Transition out the gallery, destory the gallery when the transition is completed.
const $fullImage = this.$viewer.querySelector( this.selectors.fullImage );
$fullImage.addEventListener( customEvent.TRANSITION_COMPLETED, () => {
this.destory();
});
animateMove( $fullImage, this.$currentImage, 0.35 );
// Analytics tracking.
this.track( evt.currentTarget );
}
},
/**
* Completely remove the image viewer and the reference to it.
*
* @method destroy
*/
destory() {
if ( this.$viewer ) {
this.$viewer.remove();
this.$viewer = false;
}
},
/**
* Analytics tracking.
*
* @param {HTMLElement} $elem - element triggering click tracking
*
* @method track
*/
track( $elem ) {
/* eslint-disable camelcase */
if ( 'undefined' !== typeof ( gn_analytics ) ) {
gn_analytics.Analytics.track(['adobe'], {
eventType: 'click',
action: $elem.dataset.trackaction,
target: $elem,
});
}
/* eslint-enable camelcase */
},
};
export default ImageViewer;