/** * zoom.js - It's the best way to zoom an image * @version v0.0.2 * @link https://github.com/fat/zoom.js * @license MIT */ +function ($) { "use strict"; /** * The zoom service */ function ZoomService () { this._activeZoom = this._initialScrollPosition = this._initialTouchPosition = this._touchMoveListener = null this._$document = $(document) this._$window = $(window) this._$body = $(document.body) this._boundClick = $.proxy(this._clickHandler, this) } ZoomService.prototype.listen = function () { this._$body.on('click', '[data-action="zoom"]', $.proxy(this._zoom, this)) } ZoomService.prototype._zoom = function (e) { var target = e.target if (!target || target.tagName != 'IMG') return if (this._$body.hasClass('zoom-overlay-open')) return if (e.metaKey || e.ctrlKey) { return window.open((e.target.getAttribute('data-original') || e.target.src), '_blank') } //if (target.width >= ($(window).width() - Zoom.OFFSET)) return this._activeZoomClose(true) this._activeZoom = new Zoom(target) this._activeZoom.zoomImage() // todo(fat): probably worth throttling this this._$window.on('scroll.zoom', $.proxy(this._scrollHandler, this)) this._$document.on('keyup.zoom', $.proxy(this._keyHandler, this)) this._$document.on('touchstart.zoom', $.proxy(this._touchStart, this)) // we use a capturing phase here to prevent unintended js events // sadly no useCapture in jquery api (http://bugs.jquery.com/ticket/14953) if (document.addEventListener) { document.addEventListener('click', this._boundClick, true) } else { document.attachEvent('onclick', this._boundClick, true) } if ('bubbles' in e) { if (e.bubbles) e.stopPropagation() } else { // Internet Explorer before version 9 e.cancelBubble = true } } ZoomService.prototype._activeZoomClose = function (forceDispose) { if (!this._activeZoom) return if (forceDispose) { this._activeZoom.dispose() } else { this._activeZoom.close() } this._$window.off('.zoom') this._$document.off('.zoom') document.removeEventListener('click', this._boundClick, true) this._activeZoom = null } ZoomService.prototype._scrollHandler = function (e) { if (this._initialScrollPosition === null) this._initialScrollPosition = $(window).scrollTop() var deltaY = this._initialScrollPosition - $(window).scrollTop() if (Math.abs(deltaY) >= 40) this._activeZoomClose() } ZoomService.prototype._keyHandler = function (e) { if (e.keyCode == 27) this._activeZoomClose() } ZoomService.prototype._clickHandler = function (e) { if (e.preventDefault) e.preventDefault() else event.returnValue = false if ('bubbles' in e) { if (e.bubbles) e.stopPropagation() } else { // Internet Explorer before version 9 e.cancelBubble = true } this._activeZoomClose() } ZoomService.prototype._touchStart = function (e) { this._initialTouchPosition = e.touches[0].pageY $(e.target).on('touchmove.zoom', $.proxy(this._touchMove, this)) } ZoomService.prototype._touchMove = function (e) { if (Math.abs(e.touches[0].pageY - this._initialTouchPosition) > 10) { this._activeZoomClose() $(e.target).off('touchmove.zoom') } } /** * The zoom object */ function Zoom (img) { this._fullHeight = this._fullWidth = this._overlay = this._targetImageWrap = null this._targetImage = img this._$body = $(document.body) } Zoom.OFFSET = 80 Zoom._MAX_WIDTH = 2560 Zoom._MAX_HEIGHT = 4096 Zoom.prototype.zoomImage = function () { var img = document.createElement('img') img.onload = $.proxy(function () { this._fullHeight = Number(img.height) this._fullWidth = Number(img.width) this._zoomOriginal() }, this) img.src = this._targetImage.src } Zoom.prototype._zoomOriginal = function () { this._targetImageWrap = document.createElement('div') this._targetImageWrap.className = 'zoom-img-wrap' this._targetImage.parentNode.insertBefore(this._targetImageWrap, this._targetImage) this._targetImageWrap.appendChild(this._targetImage) $(this._targetImage) .addClass('zoom-img') .attr('data-action', 'zoom-out') this._overlay = document.createElement('div') this._overlay.className = 'zoom-overlay' document.body.appendChild(this._overlay) this._calculateZoom() this._triggerAnimation() } Zoom.prototype._calculateZoom = function () { this._targetImage.offsetWidth // repaint before animating var originalFullImageWidth = this._fullWidth var originalFullImageHeight = this._fullHeight var scrollTop = $(window).scrollTop() var maxScaleFactor = originalFullImageWidth / this._targetImage.width var viewportHeight = ($(window).height() - Zoom.OFFSET) var viewportWidth = ($(window).width() - Zoom.OFFSET) var imageAspectRatio = originalFullImageWidth / originalFullImageHeight var viewportAspectRatio = viewportWidth / viewportHeight if (originalFullImageWidth < viewportWidth && originalFullImageHeight < viewportHeight) { this._imgScaleFactor = maxScaleFactor } else if (imageAspectRatio < viewportAspectRatio) { this._imgScaleFactor = (viewportHeight / originalFullImageHeight) * maxScaleFactor } else { this._imgScaleFactor = (viewportWidth / originalFullImageWidth) * maxScaleFactor } } Zoom.prototype._triggerAnimation = function () { this._targetImage.offsetWidth // repaint before animating var imageOffset = $(this._targetImage).offset() var scrollTop = $(window).scrollTop() var viewportY = scrollTop + ($(window).height() / 2) var viewportX = ($(window).width() / 2) var imageCenterY = imageOffset.top + (this._targetImage.height / 2) var imageCenterX = imageOffset.left + (this._targetImage.width / 2) this._translateY = viewportY - imageCenterY this._translateX = viewportX - imageCenterX var targetTransform = 'scale(' + this._imgScaleFactor + ')' var imageWrapTransform = 'translate(' + this._translateX + 'px, ' + this._translateY + 'px)' if ($.support.transition) { imageWrapTransform += ' translateZ(0)' } $(this._targetImage) .css({ '-webkit-transform': targetTransform, '-ms-transform': targetTransform, 'transform': targetTransform }) $(this._targetImageWrap) .css({ '-webkit-transform': imageWrapTransform, '-ms-transform': imageWrapTransform, 'transform': imageWrapTransform }) this._$body.addClass('zoom-overlay-open') } Zoom.prototype.close = function () { this._$body .removeClass('zoom-overlay-open') .addClass('zoom-overlay-transitioning') // we use setStyle here so that the correct vender prefix for transform is used $(this._targetImage) .css({ '-webkit-transform': '', '-ms-transform': '', 'transform': '' }) $(this._targetImageWrap) .css({ '-webkit-transform': '', '-ms-transform': '', 'transform': '' }) $(this._targetImage) .one("transitionend", $.proxy(this.dispose, this)) } Zoom.prototype.dispose = function (e) { if (this._targetImageWrap && this._targetImageWrap.parentNode) { $(this._targetImage) .removeClass('zoom-img') .attr('data-action', 'zoom') this._targetImageWrap.parentNode.replaceChild(this._targetImage, this._targetImageWrap) this._overlay.parentNode.removeChild(this._overlay) this._$body.removeClass('zoom-overlay-transitioning') } } // wait for dom ready (incase script included before body) $(function () { new ZoomService().listen() }) }(jQuery);