279 lines
7.8 KiB
JavaScript
279 lines
7.8 KiB
JavaScript
+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': ''
|
|
})
|
|
|
|
if (!$.support.transition) {
|
|
return this.dispose()
|
|
}
|
|
|
|
$(this._targetImage)
|
|
.one($.support.transition.end, $.proxy(this.dispose, this))
|
|
.emulateTransitionEnd(300)
|
|
}
|
|
|
|
Zoom.prototype.dispose = function () {
|
|
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)
|