1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
|
/*!
* jQuery Magnify Plugin v2.1.0 by T. H. Doan (http://thdoan.github.io/magnify/)
* Based on http://thecodeplayer.com/walkthrough/magnifying-glass-for-images-using-jquery-and-css3
*
* jQuery Magnify by T. H. Doan is licensed under the MIT License.
* Read a copy of the license in the LICENSE file or at http://choosealicense.com/licenses/mit
*/
(function($) {
$.fn.magnify = function(oOptions) {
// Default options
oOptions = $.extend({
'src': '',
'speed': 100,
'timeout': -1,
'finalWidth': null,
'finalHeight': null,
'magnifiedWidth': null,
'magnifiedHeight': null,
'limitBounds': false,
'afterLoad': function(){}
}, oOptions);
var $that = this, // Preserve scope
$html = $('html'),
// Initiate
init = function(el) {
var $image = $(el),
$anchor = $image.closest('a'),
oDataAttr = {};
// Get data attributes
for (var i in oOptions) {
oDataAttr[i] = $image.attr('data-magnify-' + i.toLowerCase());
}
// Disable zooming if no valid large image source
var sZoomSrc = oDataAttr['src'] || oOptions['src'] || $anchor.attr('href') || '';
if (!sZoomSrc) return;
var $container,
$lens,
nImageWidth,
nImageHeight,
nMagnifiedWidth,
nMagnifiedHeight,
nLensWidth,
nLensHeight,
nBoundX = 0,
nBoundY = 0,
oContainerOffset, // Relative to document
oImageOffset, // Relative to container
// Get true offsets
getOffset = function() {
var o = $container.offset();
// Store offsets from container border to image inside
// NOTE: .offset() does NOT take into consideration image border and padding.
oImageOffset = {
'top': ($image.offset().top-o.top) + parseInt($image.css('border-top-width')) + parseInt($image.css('padding-top')),
'left': ($image.offset().left-o.left) + parseInt($image.css('border-left-width')) + parseInt($image.css('padding-left'))
};
o.top += oImageOffset['top'];
o.left += oImageOffset['left'];
return o;
},
// Hide the lens
hideLens = function() {
if ($lens.is(':visible')) $lens.fadeOut(oOptions['speed'], function() {
$html.removeClass('magnifying').trigger('magnifyend'); // Reset overflow-x
});
};
// Data attributes have precedence over options object
if (!isNaN(+oDataAttr['speed'])) oOptions['speed'] = +oDataAttr['speed'];
if (!isNaN(+oDataAttr['timeout'])) oOptions['timeout'] = +oDataAttr['timeout'];
if (!isNaN(+oDataAttr['finalWidth'])) oOptions['finalWidth'] = +oDataAttr['finalWidth'];
if (!isNaN(+oDataAttr['finalHeight'])) oOptions['finalHeight'] = +oDataAttr['finalHeight'];
if (!isNaN(+oDataAttr['magnifiedWidth'])) oOptions['magnifiedWidth'] = +oDataAttr['magnifiedWidth'];
if (!isNaN(+oDataAttr['magnifiedHeight'])) oOptions['magnifiedHeight'] = +oDataAttr['magnifiedHeight'];
if (oDataAttr['limitBounds']==='true') oOptions['limitBounds'] = true;
if (typeof window[oDataAttr['afterLoad']]==='function') oOptions.afterLoad = window[oDataAttr['afterLoad']];
// Save any inline styles for resetting
$image.data('originalStyle', $image.attr('style'));
// Activate magnification:
// 1. Try to get large image dimensions
// 2. Proceed only if able to get large image dimensions OK
// [1] Calculate the native (magnified) image dimensions. The zoomed version is only shown
// after the native dimensions are available. To get the actual dimensions we have to create
// this image object.
var elZoomImage = new Image();
$(elZoomImage).on({
'load': function() {
// [2] Got image dimensions OK.
var nX, nY;
// Fix overlap bug at the edges during magnification
$image.css('display', 'block');
// Create container div if necessary
if (!$image.parent('.magnify').length) {
$image.wrap('<div class="magnify"></div>');
}
$container = $image.parent('.magnify');
// Create the magnifying lens div if necessary
if ($image.prev('.magnify-lens').length) {
$container.children('.magnify-lens').css('background-image', 'url(\'' + sZoomSrc + '\')');
} else {
$image.before('<div class="magnify-lens loading" style="background:url(\'' + sZoomSrc + '\') 0 0 no-repeat"></div>');
}
$lens = $container.children('.magnify-lens');
// Remove the "Loading..." text
$lens.removeClass('loading');
// Cache dimensions and offsets for improved performance
// NOTE: This code is inside the load() function, which is important. The width and
// height of the object would return 0 if accessed before the image is fully loaded.
nImageWidth = oOptions['finalWidth'] || $image.width();
nImageHeight = oOptions['finalHeight'] || $image.height();
nMagnifiedWidth = oOptions['magnifiedWidth'] || elZoomImage.width;
nMagnifiedHeight = oOptions['magnifiedHeight'] || elZoomImage.height;
nLensWidth = $lens.width();
nLensHeight = $lens.height();
oContainerOffset = getOffset(); // Required by refresh()
// Set zoom boundaries
if (oOptions['limitBounds']) {
nBoundX = (nLensWidth/2) / (nMagnifiedWidth/nImageWidth);
nBoundY = (nLensHeight/2) / (nMagnifiedHeight/nImageHeight);
}
// Enforce non-native large image size?
if (nMagnifiedWidth!==elZoomImage.width || nMagnifiedHeight!==elZoomImage.height) {
$lens.css('background-size', nMagnifiedWidth + 'px ' + nMagnifiedHeight + 'px');
}
// Store zoom dimensions for mobile plugin
$image.data('zoomSize', {
'width': nMagnifiedWidth,
'height': nMagnifiedHeight
});
// Clean up
elZoomImage = null;
// Execute callback
oOptions.afterLoad();
// Handle mouse movements
$container.off().on({
'mousemove touchmove': function(e) {
e.preventDefault();
// Reinitialize if image initially hidden
if (!nImageHeight) {
refresh();
return;
}
// x/y coordinates of the mouse pointer or touch point. This is the position of
// .magnify relative to the document.
//
// We deduct the positions of .magnify from the mouse or touch positions relative to
// the document to get the mouse or touch positions relative to the container.
nX = (e.pageX || e.originalEvent.touches[0].pageX) - oContainerOffset['left'],
nY = (e.pageY || e.originalEvent.touches[0].pageY) - oContainerOffset['top'];
// Toggle magnifying lens
if (!$lens.is(':animated')) {
if (nX>nBoundX && nX<nImageWidth-nBoundX && nY>nBoundY && nY<nImageHeight-nBoundY) {
if ($lens.is(':hidden')) {
$html.addClass('magnifying').trigger('magnifystart'); // Hide overflow-x while zooming
$lens.fadeIn(oOptions['speed']);
}
} else {
hideLens();
}
}
if ($lens.is(':visible')) {
// Move the magnifying lens with the mouse
var sBgPos = '';
if (nMagnifiedWidth && nMagnifiedHeight) {
// Change the background position of .magnify-lens according to the position of
// the mouse over the .magnify-image image. This allows us to get the ratio of
// the pixel under the mouse pointer with respect to the image and use that to
// position the large image inside the magnifying lens.
var nRatioX = -Math.round(nX/nImageWidth*nMagnifiedWidth-nLensWidth/2),
nRatioY = -Math.round(nY/nImageHeight*nMagnifiedHeight-nLensHeight/2);
if (oOptions['limitBounds']) {
// Enforce bounds to ensure only image is visible in lens
var nBoundRight = -Math.round((nImageWidth-nBoundX)/nImageWidth*nMagnifiedWidth-nLensWidth/2),
nBoundBottom = -Math.round((nImageHeight-nBoundY)/nImageHeight*nMagnifiedHeight-nLensHeight/2);
// Left and right edges
if (nRatioX>0) nRatioX = 0;
else if (nRatioX<nBoundRight) nRatioX = nBoundRight;
// Top and bottom edges
if (nRatioY>0) nRatioY = 0;
else if (nRatioY<nBoundBottom) nRatioY = nBoundBottom;
}
sBgPos = nRatioX + 'px ' + nRatioY + 'px';
}
// Now the lens moves with the mouse. The logic is to deduct half of the lens's
// width and height from the mouse coordinates to place it with its center at the
// mouse coordinates. If you hover on the image now, you should see the magnifying
// lens in action.
$lens.css({
'top': Math.round(nY-nLensHeight/2) + oImageOffset['top'] + 'px',
'left': Math.round(nX-nLensWidth/2) + oImageOffset['left'] + 'px',
'background-position': sBgPos
});
}
},
'mouseenter': function() {
// Need to update offsets here to support accordions
oContainerOffset = getOffset();
},
'mouseleave': hideLens
});
// Prevent magnifying lens from getting "stuck"
if (oOptions['timeout']>=0) {
$container.on('touchend', function() {
setTimeout(hideLens, oOptions['timeout']);
});
}
// Ensure lens is closed when tapping outside of it
$('body').not($container).on('touchstart', hideLens);
// Support image map click-throughs while zooming
var sUsemap = $image.attr('usemap');
if (sUsemap) {
var $map = $('map[name=' + sUsemap.slice(1) + ']');
// Image map needs to be on the same DOM level as image source
$image.after($map);
$container.click(function(e) {
// Trigger click on image below lens at current cursor position
if (e.clientX || e.clientY) {
$lens.hide();
var elPoint = document.elementFromPoint(
e.clientX || e.originalEvent.touches[0].clientX,
e.clientY || e.originalEvent.touches[0].clientY
);
if (elPoint.nodeName==='AREA') {
elPoint.click();
} else {
// Workaround for buggy implementation of elementFromPoint()
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1227469
$('area', $map).each(function() {
var a = $(this).attr('coords').split(',');
if (nX>=a[0] && nX<=a[2] && nY>=a[1] && nY<=a[3]) {
this.click();
return false;
}
});
}
}
});
}
if ($anchor.length) {
// Make parent anchor inline-block to have correct dimensions
$anchor.css('display', 'inline-block');
// Disable parent anchor if it's sourcing the large image
if ($anchor.attr('href') && !(oDataAttr['src'] || oOptions['src'])) {
$anchor.click(function(e) {
e.preventDefault();
});
}
}
},
'error': function() {
// Clean up
elZoomImage = null;
}
});
elZoomImage.src = sZoomSrc;
}, // END init()
// Simple debounce
nTimer = 0,
refresh = function() {
clearTimeout(nTimer);
nTimer = setTimeout(function() {
$that.destroy();
$that.magnify(oOptions);
}, 100);
};
/**
* Public Methods
*/
// Turn off zoom and reset to original state
this.destroy = function() {
this.each(function() {
var $this = $(this),
$lens = $this.prev('div.magnify-lens'),
sStyle = $this.data('originalStyle');
if ($this.parent('div.magnify').length && $lens.length) {
if (sStyle) $this.attr('style', sStyle);
else $this.removeAttr('style');
$this.unwrap();
$lens.remove();
}
});
// Unregister event handler
$(window).off('resize', refresh);
return $that;
}
// Handle window resizing
$(window).resize(refresh);
return this.each(function() {
// Initiate magnification powers
init(this);
});
};
}(jQuery));
|