590 lines
20 KiB
JavaScript
590 lines
20 KiB
JavaScript
/**
|
|
* Magnifier.js is a Javascript library enabling magnifying glass effect on an images.
|
|
*
|
|
* Features
|
|
*
|
|
* Zoom in / out functionality using mouse wheel
|
|
* Setting options via Javascript or data attributes
|
|
* Magnified image can be displayed in the lens itself or outside of it in a wrapper
|
|
* Attachment to multiple images with single call
|
|
* Attachment of user defined functions for thumbnail entering, moving and leaving and image zooming events
|
|
* Display loading text while the large image is being loaded, and switch to lens once its loaded
|
|
*
|
|
* Magnifier.js uses Event.js as a cross-browser event handling wrapper, which is available at
|
|
* Github and JSClasses.org:
|
|
*
|
|
* Github - https://github.com/mark-rolich/Event.js
|
|
* JS Classes - http://www.jsclasses.org/package/212-JavaScript-Handle-events-in-a-browser-independent-manner.html
|
|
*
|
|
* Works in Chrome, Firefox, Safari, IE 7, 8, 9 & 10.
|
|
*
|
|
* @author Mark Rolich <mark.rolich@gmail.com>
|
|
*/
|
|
var Magnifier = function (evt, options) {
|
|
"use strict";
|
|
|
|
var gOptions = options || {},
|
|
curThumb = null,
|
|
curData = {
|
|
x: 0,
|
|
y: 0,
|
|
w: 0,
|
|
h: 0,
|
|
lensW: 0,
|
|
lensH: 0,
|
|
lensBgX: 0,
|
|
lensBgY: 0,
|
|
largeW: 0,
|
|
largeH: 0,
|
|
largeL: 0,
|
|
largeT: 0,
|
|
zoom: 2,
|
|
zoomMin: 1.1,
|
|
zoomMax: 5,
|
|
mode: 'outside',
|
|
largeWrapperId: (gOptions.largeWrapper !== undefined)
|
|
? (gOptions.largeWrapper.id || null)
|
|
: null,
|
|
status: 0,
|
|
zoomAttached: false,
|
|
zoomable: (gOptions.zoomable !== undefined)
|
|
? gOptions.zoomable
|
|
: false,
|
|
onthumbenter: (gOptions.onthumbenter !== undefined)
|
|
? gOptions.onthumbenter
|
|
: null,
|
|
onthumbmove: (gOptions.onthumbmove !== undefined)
|
|
? gOptions.onthumbmove
|
|
: null,
|
|
onthumbleave: (gOptions.onthumbleave !== undefined)
|
|
? gOptions.onthumbleave
|
|
: null,
|
|
onzoom: (gOptions.onzoom !== undefined)
|
|
? gOptions.onzoom
|
|
: null
|
|
},
|
|
pos = {
|
|
t: 0,
|
|
l: 0,
|
|
x: 0,
|
|
y: 0
|
|
},
|
|
gId = 0,
|
|
status = 0,
|
|
curIdx = '',
|
|
curLens = null,
|
|
curLarge = null,
|
|
gZoom = (gOptions.zoom !== undefined)
|
|
? gOptions.zoom
|
|
: curData.zoom,
|
|
gZoomMin = (gOptions.zoomMin !== undefined)
|
|
? gOptions.zoomMin
|
|
: curData.zoomMin,
|
|
gZoomMax = (gOptions.zoomMax !== undefined)
|
|
? gOptions.zoomMax
|
|
: curData.zoomMax,
|
|
gMode = gOptions.mode || curData.mode,
|
|
data = {},
|
|
inBounds = false,
|
|
isOverThumb = 0,
|
|
getElementsByClass = function (className) {
|
|
var list = [],
|
|
elements = null,
|
|
len = 0,
|
|
pattern = '',
|
|
i = 0,
|
|
j = 0;
|
|
|
|
if (document.getElementsByClassName) {
|
|
list = document.getElementsByClassName(className);
|
|
} else {
|
|
elements = document.getElementsByTagName('*');
|
|
len = elements.length;
|
|
pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
|
|
|
|
for (i, j; i < len; i += 1) {
|
|
if (pattern.test(elements[i].className)) {
|
|
list[j] = elements[i];
|
|
j += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
},
|
|
$ = function (selector) {
|
|
var idx = '',
|
|
type = selector.charAt(0),
|
|
result = null;
|
|
|
|
if (type === '#' || type === '.') {
|
|
idx = selector.substr(1, selector.length);
|
|
}
|
|
|
|
if (idx !== '') {
|
|
switch (type) {
|
|
case '#':
|
|
result = document.getElementById(idx);
|
|
break;
|
|
case '.':
|
|
result = getElementsByClass(idx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
createLens = function (thumb, idx) {
|
|
var lens = document.createElement('div');
|
|
|
|
lens.id = idx + '-lens';
|
|
lens.className = 'magnifier-loader';
|
|
|
|
thumb.parentNode.appendChild(lens);
|
|
},
|
|
updateLensOnZoom = function () {
|
|
curLens.style.left = pos.l + 'px';
|
|
curLens.style.top = pos.t + 'px';
|
|
curLens.style.width = curData.lensW + 'px';
|
|
curLens.style.height = curData.lensH + 'px';
|
|
curLens.style.backgroundPosition = '-' + curData.lensBgX + 'px -' +
|
|
curData.lensBgY + 'px';
|
|
|
|
curLarge.style.left = '-' + curData.largeL + 'px';
|
|
curLarge.style.top = '-' + curData.largeT + 'px';
|
|
curLarge.style.width = curData.largeW + 'px';
|
|
curLarge.style.height = curData.largeH + 'px';
|
|
},
|
|
updateLensOnLoad = function (idx, thumb, large, largeWrapper) {
|
|
var lens = $('#' + idx + '-lens'),
|
|
textWrapper = null;
|
|
|
|
if (data[idx].status === 1) {
|
|
textWrapper = document.createElement('div');
|
|
textWrapper.className = 'magnifier-loader-text';
|
|
lens.className = 'magnifier-loader hidden';
|
|
|
|
textWrapper.appendChild(document.createTextNode('Loading...'));
|
|
lens.appendChild(textWrapper);
|
|
} else if (data[idx].status === 2) {
|
|
lens.className = 'magnifier-lens hidden';
|
|
lens.removeChild(lens.childNodes[0]);
|
|
lens.style.background = 'url(' + thumb.src + ') no-repeat 0 0 scroll';
|
|
|
|
large.id = idx + '-large';
|
|
large.style.width = data[idx].largeW + 'px';
|
|
large.style.height = data[idx].largeH + 'px';
|
|
large.className = 'magnifier-large hidden';
|
|
|
|
if (data[idx].mode === 'inside') {
|
|
lens.appendChild(large);
|
|
} else {
|
|
largeWrapper.appendChild(large);
|
|
}
|
|
}
|
|
|
|
lens.style.width = data[idx].lensW + 'px';
|
|
lens.style.height = data[idx].lensH + 'px';
|
|
},
|
|
getMousePos = function () {
|
|
var xPos = pos.x - curData.x,
|
|
yPos = pos.y - curData.y,
|
|
t = 0,
|
|
l = 0;
|
|
|
|
inBounds = (
|
|
xPos < 0 ||
|
|
yPos < 0 ||
|
|
xPos > curData.w ||
|
|
yPos > curData.h
|
|
)
|
|
? false
|
|
: true;
|
|
|
|
l = xPos - (curData.lensW / 2);
|
|
t = yPos - (curData.lensH / 2);
|
|
|
|
if (curData.mode !== 'inside') {
|
|
if (xPos < curData.lensW / 2) {
|
|
l = 0;
|
|
}
|
|
|
|
if (yPos < curData.lensH / 2) {
|
|
t = 0;
|
|
}
|
|
|
|
if (xPos - curData.w + (curData.lensW / 2) > 0) {
|
|
l = curData.w - (curData.lensW + 2);
|
|
}
|
|
|
|
if (yPos - curData.h + (curData.lensH / 2) > 0) {
|
|
t = curData.h - (curData.lensH + 2);
|
|
}
|
|
}
|
|
|
|
pos.l = Math.round(l);
|
|
pos.t = Math.round(t);
|
|
|
|
curData.lensBgX = pos.l + 1;
|
|
curData.lensBgY = pos.t + 1;
|
|
|
|
if (curData.mode === 'inside') {
|
|
curData.largeL = Math.round(xPos * (curData.zoom - (curData.lensW / curData.w)));
|
|
curData.largeT = Math.round(yPos * (curData.zoom - (curData.lensH / curData.h)));
|
|
} else {
|
|
curData.largeL = Math.round(curData.lensBgX * curData.zoom * (curData.largeWrapperW / curData.w));
|
|
curData.largeT = Math.round(curData.lensBgY * curData.zoom * (curData.largeWrapperH / curData.h));
|
|
}
|
|
},
|
|
zoomInOut = function (e) {
|
|
var delta = (e.wheelDelta > 0 || e.detail < 0) ? 0.1 : -0.1,
|
|
handler = curData.onzoom,
|
|
multiplier = 1,
|
|
w = 0,
|
|
h = 0;
|
|
|
|
if (e.preventDefault) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
e.returnValue = false;
|
|
|
|
curData.zoom = Math.round((curData.zoom + delta) * 10) / 10;
|
|
|
|
if (curData.zoom >= curData.zoomMax) {
|
|
curData.zoom = curData.zoomMax;
|
|
} else if (curData.zoom >= curData.zoomMin) {
|
|
curData.lensW = Math.round(curData.w / curData.zoom);
|
|
curData.lensH = Math.round(curData.h / curData.zoom);
|
|
|
|
if (curData.mode === 'inside') {
|
|
w = curData.w;
|
|
h = curData.h;
|
|
} else {
|
|
w = curData.largeWrapperW;
|
|
h = curData.largeWrapperH;
|
|
multiplier = curData.largeWrapperW / curData.w;
|
|
}
|
|
|
|
curData.largeW = Math.round(curData.zoom * w);
|
|
curData.largeH = Math.round(curData.zoom * h);
|
|
|
|
getMousePos();
|
|
updateLensOnZoom();
|
|
|
|
if (handler !== null) {
|
|
handler({
|
|
thumb: curThumb,
|
|
lens: curLens,
|
|
large: curLarge,
|
|
x: pos.x,
|
|
y: pos.y,
|
|
zoom: Math.round(curData.zoom * multiplier * 10) / 10,
|
|
w: curData.lensW,
|
|
h: curData.lensH
|
|
});
|
|
}
|
|
} else {
|
|
curData.zoom = curData.zoomMin;
|
|
}
|
|
},
|
|
onThumbEnter = function () {
|
|
curData = data[curIdx];
|
|
curLens = $('#' + curIdx + '-lens');
|
|
|
|
if (curData.status === 2) {
|
|
curLens.className = 'magnifier-lens';
|
|
|
|
if (curData.zoomAttached === false) {
|
|
if (curData.zoomable !== undefined && curData.zoomable === true) {
|
|
evt.attach('mousewheel', curLens, zoomInOut);
|
|
|
|
if (window.addEventListener) {
|
|
curLens.addEventListener('DOMMouseScroll', function (e) {
|
|
zoomInOut(e);
|
|
});
|
|
}
|
|
}
|
|
|
|
curData.zoomAttached = true;
|
|
}
|
|
|
|
curLarge = $('#' + curIdx + '-large');
|
|
curLarge.className = 'magnifier-large';
|
|
} else if (curData.status === 1) {
|
|
curLens.className = 'magnifier-loader';
|
|
}
|
|
},
|
|
onThumbLeave = function () {
|
|
if (curData.status > 0) {
|
|
var handler = curData.onthumbleave;
|
|
|
|
if (handler !== null) {
|
|
handler({
|
|
thumb: curThumb,
|
|
lens: curLens,
|
|
large: curLarge,
|
|
x: pos.x,
|
|
y: pos.y
|
|
});
|
|
}
|
|
|
|
if (curLens.className.indexOf('hidden') === -1) {
|
|
curLens.className += ' hidden';
|
|
curThumb.className = curData.thumbCssClass;
|
|
|
|
if (curLarge !== null) {
|
|
curLarge.className += ' hidden';
|
|
}
|
|
}
|
|
}
|
|
},
|
|
move = function () {
|
|
if (status !== curData.status) {
|
|
onThumbEnter();
|
|
}
|
|
|
|
if (curData.status > 0) {
|
|
curThumb.className = curData.thumbCssClass + ' opaque';
|
|
|
|
if (curData.status === 1) {
|
|
curLens.className = 'magnifier-loader';
|
|
} else if (curData.status === 2) {
|
|
curLens.className = 'magnifier-lens';
|
|
curLarge.className = 'magnifier-large';
|
|
curLarge.style.left = '-' + curData.largeL + 'px';
|
|
curLarge.style.top = '-' + curData.largeT + 'px';
|
|
}
|
|
|
|
curLens.style.left = pos.l + 'px';
|
|
curLens.style.top = pos.t + 'px';
|
|
curLens.style.backgroundPosition = '-' +
|
|
curData.lensBgX + 'px -' +
|
|
curData.lensBgY + 'px';
|
|
|
|
var handler = curData.onthumbmove;
|
|
|
|
if (handler !== null) {
|
|
handler({
|
|
thumb: curThumb,
|
|
lens: curLens,
|
|
large: curLarge,
|
|
x: pos.x,
|
|
y: pos.y
|
|
});
|
|
}
|
|
}
|
|
|
|
status = curData.status;
|
|
},
|
|
setThumbData = function (thumb, thumbData) {
|
|
var thumbBounds = thumb.getBoundingClientRect(),
|
|
w = 0,
|
|
h = 0;
|
|
|
|
thumbData.x = thumbBounds.left;
|
|
thumbData.y = thumbBounds.top;
|
|
thumbData.w = Math.round(thumbBounds.right - thumbData.x);
|
|
thumbData.h = Math.round(thumbBounds.bottom - thumbData.y);
|
|
|
|
thumbData.lensW = Math.round(thumbData.w / thumbData.zoom);
|
|
thumbData.lensH = Math.round(thumbData.h / thumbData.zoom);
|
|
|
|
if (thumbData.mode === 'inside') {
|
|
w = thumbData.w;
|
|
h = thumbData.h;
|
|
} else {
|
|
w = thumbData.largeWrapperW;
|
|
h = thumbData.largeWrapperH;
|
|
}
|
|
|
|
thumbData.largeW = Math.round(thumbData.zoom * w);
|
|
thumbData.largeH = Math.round(thumbData.zoom * h);
|
|
};
|
|
|
|
this.attach = function (options) {
|
|
if (options.thumb === undefined) {
|
|
throw {
|
|
name: 'Magnifier error',
|
|
message: 'Please set thumbnail',
|
|
toString: function () {return this.name + ": " + this.message; }
|
|
};
|
|
}
|
|
|
|
var thumb = $(options.thumb),
|
|
i = 0;
|
|
|
|
if (thumb.length !== undefined) {
|
|
for (i; i < thumb.length; i += 1) {
|
|
options.thumb = thumb[i];
|
|
this.set(options);
|
|
}
|
|
} else {
|
|
options.thumb = thumb;
|
|
this.set(options);
|
|
}
|
|
};
|
|
|
|
this.setThumb = function (thumb) {
|
|
curThumb = thumb;
|
|
};
|
|
|
|
this.set = function (options) {
|
|
if (data[options.thumb.id] !== undefined) {
|
|
curThumb = options.thumb;
|
|
return false;
|
|
}
|
|
|
|
var thumbObj = new Image(),
|
|
largeObj = new Image(),
|
|
thumb = options.thumb,
|
|
idx = thumb.id,
|
|
zoomable = null,
|
|
largeUrl = null,
|
|
largeWrapper = (
|
|
$('#' + options.largeWrapper) ||
|
|
$('#' + thumb.getAttribute('data-large-img-wrapper')) ||
|
|
$('#' + curData.largeWrapperId)
|
|
),
|
|
zoom = options.zoom || thumb.getAttribute('data-zoom') || gZoom,
|
|
zoomMin = options.zoomMin || thumb.getAttribute('data-zoom-min') || gZoomMin,
|
|
zoomMax = options.zoomMax || thumb.getAttribute('data-zoom-max') || gZoomMax,
|
|
mode = options.mode || thumb.getAttribute('data-mode') || gMode,
|
|
onthumbenter = (options.onthumbenter !== undefined)
|
|
? options.onthumbenter
|
|
: curData.onthumbenter,
|
|
onthumbleave = (options.onthumbleave !== undefined)
|
|
? options.onthumbleave
|
|
: curData.onthumbleave,
|
|
onthumbmove = (options.onthumbmove !== undefined)
|
|
? options.onthumbmove
|
|
: curData.onthumbmove,
|
|
onzoom = (options.onzoom !== undefined)
|
|
? options.onzoom
|
|
: curData.onzoom;
|
|
|
|
if (options.large === undefined) {
|
|
largeUrl = (options.thumb.getAttribute('data-large-img-url') !== null)
|
|
? options.thumb.getAttribute('data-large-img-url')
|
|
: options.thumb.src;
|
|
} else {
|
|
largeUrl = options.large;
|
|
}
|
|
|
|
if (largeWrapper === null && mode !== 'inside') {
|
|
throw {
|
|
name: 'Magnifier error',
|
|
message: 'Please specify large image wrapper DOM element',
|
|
toString: function () {return this.name + ": " + this.message; }
|
|
};
|
|
}
|
|
|
|
if (options.zoomable !== undefined) {
|
|
zoomable = options.zoomable;
|
|
} else if (thumb.getAttribute('data-zoomable') !== null) {
|
|
zoomable = (thumb.getAttribute('data-zoomable') === 'true');
|
|
} else if (curData.zoomable !== undefined) {
|
|
zoomable = curData.zoomable;
|
|
}
|
|
|
|
if (thumb.id === '') {
|
|
idx = thumb.id = 'magnifier-item-' + gId;
|
|
gId += 1;
|
|
}
|
|
|
|
createLens(thumb, idx);
|
|
|
|
data[idx] = {
|
|
zoom: zoom,
|
|
zoomMin: zoomMin,
|
|
zoomMax: zoomMax,
|
|
mode: mode,
|
|
zoomable: zoomable,
|
|
thumbCssClass: thumb.className,
|
|
zoomAttached: false,
|
|
status: 0,
|
|
largeUrl: largeUrl,
|
|
largeWrapperId: mode === 'outside' ? largeWrapper.id : null,
|
|
largeWrapperW: mode === 'outside' ? largeWrapper.offsetWidth : null,
|
|
largeWrapperH: mode === 'outside' ? largeWrapper.offsetHeight : null,
|
|
onzoom: onzoom,
|
|
onthumbenter: onthumbenter,
|
|
onthumbleave: onthumbleave,
|
|
onthumbmove: onthumbmove
|
|
};
|
|
|
|
evt.attach('mouseover', thumb, function (e, src) {
|
|
if (curData.status !== 0) {
|
|
onThumbLeave();
|
|
}
|
|
|
|
curIdx = src.id;
|
|
curThumb = src;
|
|
|
|
onThumbEnter(src);
|
|
|
|
setThumbData(curThumb, curData);
|
|
|
|
pos.x = e.clientX;
|
|
pos.y = e.clientY;
|
|
|
|
getMousePos();
|
|
move();
|
|
|
|
var handler = curData.onthumbenter;
|
|
|
|
if (handler !== null) {
|
|
handler({
|
|
thumb: curThumb,
|
|
lens: curLens,
|
|
large: curLarge,
|
|
x: pos.x,
|
|
y: pos.y
|
|
});
|
|
}
|
|
}, false);
|
|
|
|
evt.attach('mousemove', thumb, function (e, src) {
|
|
isOverThumb = 1;
|
|
});
|
|
|
|
evt.attach('load', thumbObj, function () {
|
|
data[idx].status = 1;
|
|
|
|
setThumbData(thumb, data[idx]);
|
|
updateLensOnLoad(idx);
|
|
|
|
evt.attach('load', largeObj, function () {
|
|
data[idx].status = 2;
|
|
updateLensOnLoad(idx, thumb, largeObj, largeWrapper);
|
|
});
|
|
|
|
largeObj.src = data[idx].largeUrl;
|
|
});
|
|
|
|
thumbObj.src = thumb.src;
|
|
};
|
|
|
|
evt.attach('mousemove', document, function (e) {
|
|
pos.x = e.clientX;
|
|
pos.y = e.clientY;
|
|
|
|
getMousePos();
|
|
|
|
if (inBounds === true) {
|
|
move();
|
|
} else {
|
|
if (isOverThumb !== 0) {
|
|
onThumbLeave();
|
|
}
|
|
|
|
isOverThumb = 0;
|
|
}
|
|
}, false);
|
|
|
|
evt.attach('scroll', window, function () {
|
|
if (curThumb !== null) {
|
|
setThumbData(curThumb, curData);
|
|
}
|
|
});
|
|
}; |