(function($, anim) { 'use strict'; let _defaults = { inDuration: 275, outDuration: 200, onOpenStart: null, onOpenEnd: null, onCloseStart: null, onCloseEnd: null }; /** * @class * */ class Materialbox extends Component { /** * Construct Materialbox instance * @constructor * @param {Element} el * @param {Object} options */ constructor(el, options) { super(Materialbox, el, options); this.el.M_Materialbox = this; /** * Options for the modal * @member Materialbox#options * @prop {Number} [inDuration=275] - Length in ms of enter transition * @prop {Number} [outDuration=200] - Length in ms of exit transition * @prop {Function} onOpenStart - Callback function called before materialbox is opened * @prop {Function} onOpenEnd - Callback function called after materialbox is opened * @prop {Function} onCloseStart - Callback function called before materialbox is closed * @prop {Function} onCloseEnd - Callback function called after materialbox is closed */ this.options = $.extend({}, Materialbox.defaults, options); this.overlayActive = false; this.doneAnimating = true; this.placeholder = $('
').addClass('material-placeholder'); this.originalWidth = 0; this.originalHeight = 0; this.originInlineStyles = this.$el.attr('style'); this.caption = this.el.getAttribute('data-caption') || ''; // Wrap this.$el.before(this.placeholder); this.placeholder.append(this.$el); this._setupEventHandlers(); } static get defaults() { return _defaults; } static init(els, options) { return super.init(this, els, options); } /** * Get Instance */ static getInstance(el) { let domElem = !!el.jquery ? el[0] : el; return domElem.M_Materialbox; } /** * Teardown component */ destroy() { this._removeEventHandlers(); this.el.M_Materialbox = undefined; // Unwrap image $(this.placeholder) .after(this.el) .remove(); this.$el.removeAttr('style'); } /** * Setup Event Handlers */ _setupEventHandlers() { this._handleMaterialboxClickBound = this._handleMaterialboxClick.bind(this); this.el.addEventListener('click', this._handleMaterialboxClickBound); } /** * Remove Event Handlers */ _removeEventHandlers() { this.el.removeEventListener('click', this._handleMaterialboxClickBound); } /** * Handle Materialbox Click * @param {Event} e */ _handleMaterialboxClick(e) { // If already modal, return to original if (this.doneAnimating === false || (this.overlayActive && this.doneAnimating)) { this.close(); } else { this.open(); } } /** * Handle Window Scroll */ _handleWindowScroll() { if (this.overlayActive) { this.close(); } } /** * Handle Window Resize */ _handleWindowResize() { if (this.overlayActive) { this.close(); } } /** * Handle Window Resize * @param {Event} e */ _handleWindowEscape(e) { // ESC key if (e.keyCode === 27 && this.doneAnimating && this.overlayActive) { this.close(); } } /** * Find ancestors with overflow: hidden; and make visible */ _makeAncestorsOverflowVisible() { this.ancestorsChanged = $(); let ancestor = this.placeholder[0].parentNode; while (ancestor !== null && !$(ancestor).is(document)) { let curr = $(ancestor); if (curr.css('overflow') !== 'visible') { curr.css('overflow', 'visible'); if (this.ancestorsChanged === undefined) { this.ancestorsChanged = curr; } else { this.ancestorsChanged = this.ancestorsChanged.add(curr); } } ancestor = ancestor.parentNode; } } /** * Animate image in */ _animateImageIn() { let animOptions = { targets: this.el, height: [this.originalHeight, this.newHeight], width: [this.originalWidth, this.newWidth], left: M.getDocumentScrollLeft() + this.windowWidth / 2 - this.placeholder.offset().left - this.newWidth / 2, top: M.getDocumentScrollTop() + this.windowHeight / 2 - this.placeholder.offset().top - this.newHeight / 2, duration: this.options.inDuration, easing: 'easeOutQuad', complete: () => { this.doneAnimating = true; // onOpenEnd callback if (typeof this.options.onOpenEnd === 'function') { this.options.onOpenEnd.call(this, this.el); } } }; // Override max-width or max-height if needed this.maxWidth = this.$el.css('max-width'); this.maxHeight = this.$el.css('max-height'); if (this.maxWidth !== 'none') { animOptions.maxWidth = this.newWidth; } if (this.maxHeight !== 'none') { animOptions.maxHeight = this.newHeight; } anim(animOptions); } /** * Animate image out */ _animateImageOut() { let animOptions = { targets: this.el, width: this.originalWidth, height: this.originalHeight, left: 0, top: 0, duration: this.options.outDuration, easing: 'easeOutQuad', complete: () => { this.placeholder.css({ height: '', width: '', position: '', top: '', left: '' }); // Revert to width or height attribute if (this.attrWidth) { this.$el.attr('width', this.attrWidth); } if (this.attrHeight) { this.$el.attr('height', this.attrHeight); } this.$el.removeAttr('style'); this.originInlineStyles && this.$el.attr('style', this.originInlineStyles); // Remove class this.$el.removeClass('active'); this.doneAnimating = true; // Remove overflow overrides on ancestors if (this.ancestorsChanged.length) { this.ancestorsChanged.css('overflow', ''); } // onCloseEnd callback if (typeof this.options.onCloseEnd === 'function') { this.options.onCloseEnd.call(this, this.el); } } }; anim(animOptions); } /** * Update open and close vars */ _updateVars() { this.windowWidth = window.innerWidth; this.windowHeight = window.innerHeight; this.caption = this.el.getAttribute('data-caption') || ''; } /** * Open Materialbox */ open() { this._updateVars(); this.originalWidth = this.el.getBoundingClientRect().width; this.originalHeight = this.el.getBoundingClientRect().height; // Set states this.doneAnimating = false; this.$el.addClass('active'); this.overlayActive = true; // onOpenStart callback if (typeof this.options.onOpenStart === 'function') { this.options.onOpenStart.call(this, this.el); } // Set positioning for placeholder this.placeholder.css({ width: this.placeholder[0].getBoundingClientRect().width + 'px', height: this.placeholder[0].getBoundingClientRect().height + 'px', position: 'relative', top: 0, left: 0 }); this._makeAncestorsOverflowVisible(); // Set css on origin this.$el.css({ position: 'absolute', 'z-index': 1000, 'will-change': 'left, top, width, height' }); // Change from width or height attribute to css this.attrWidth = this.$el.attr('width'); this.attrHeight = this.$el.attr('height'); if (this.attrWidth) { this.$el.css('width', this.attrWidth + 'px'); this.$el.removeAttr('width'); } if (this.attrHeight) { this.$el.css('width', this.attrHeight + 'px'); this.$el.removeAttr('height'); } // Add overlay this.$overlay = $('
') .css({ opacity: 0 }) .one('click', () => { if (this.doneAnimating) { this.close(); } }); // Put before in origin image to preserve z-index layering. this.$el.before(this.$overlay); // Set dimensions if needed let overlayOffset = this.$overlay[0].getBoundingClientRect(); this.$overlay.css({ width: this.windowWidth + 'px', height: this.windowHeight + 'px', left: -1 * overlayOffset.left + 'px', top: -1 * overlayOffset.top + 'px' }); anim.remove(this.el); anim.remove(this.$overlay[0]); // Animate Overlay anim({ targets: this.$overlay[0], opacity: 1, duration: this.options.inDuration, easing: 'easeOutQuad' }); // Add and animate caption if it exists if (this.caption !== '') { if (this.$photocaption) { anim.remove(this.$photoCaption[0]); } this.$photoCaption = $('
'); this.$photoCaption.text(this.caption); $('body').append(this.$photoCaption); this.$photoCaption.css({ display: 'inline' }); anim({ targets: this.$photoCaption[0], opacity: 1, duration: this.options.inDuration, easing: 'easeOutQuad' }); } // Resize Image let ratio = 0; let widthPercent = this.originalWidth / this.windowWidth; let heightPercent = this.originalHeight / this.windowHeight; this.newWidth = 0; this.newHeight = 0; if (widthPercent > heightPercent) { ratio = this.originalHeight / this.originalWidth; this.newWidth = this.windowWidth * 0.9; this.newHeight = this.windowWidth * 0.9 * ratio; } else { ratio = this.originalWidth / this.originalHeight; this.newWidth = this.windowHeight * 0.9 * ratio; this.newHeight = this.windowHeight * 0.9; } this._animateImageIn(); // Handle Exit triggers this._handleWindowScrollBound = this._handleWindowScroll.bind(this); this._handleWindowResizeBound = this._handleWindowResize.bind(this); this._handleWindowEscapeBound = this._handleWindowEscape.bind(this); window.addEventListener('scroll', this._handleWindowScrollBound); window.addEventListener('resize', this._handleWindowResizeBound); window.addEventListener('keyup', this._handleWindowEscapeBound); } /** * Close Materialbox */ close() { this._updateVars(); this.doneAnimating = false; // onCloseStart callback if (typeof this.options.onCloseStart === 'function') { this.options.onCloseStart.call(this, this.el); } anim.remove(this.el); anim.remove(this.$overlay[0]); if (this.caption !== '') { anim.remove(this.$photoCaption[0]); } // disable exit handlers window.removeEventListener('scroll', this._handleWindowScrollBound); window.removeEventListener('resize', this._handleWindowResizeBound); window.removeEventListener('keyup', this._handleWindowEscapeBound); anim({ targets: this.$overlay[0], opacity: 0, duration: this.options.outDuration, easing: 'easeOutQuad', complete: () => { this.overlayActive = false; this.$overlay.remove(); } }); this._animateImageOut(); // Remove Caption + reset css settings on image if (this.caption !== '') { anim({ targets: this.$photoCaption[0], opacity: 0, duration: this.options.outDuration, easing: 'easeOutQuad', complete: () => { this.$photoCaption.remove(); } }); } } } M.Materialbox = Materialbox; if (M.jQueryLoaded) { M.initializeJqueryWrapper(Materialbox, 'materialbox', 'M_Materialbox'); } })(cash, M.anime);