123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- /**
- * 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);
- }
- });
- };
|