@@ 14-1169 (lines=1156) @@ | ||
11 | * Version: 0.7.7 |
|
12 | */ |
|
13 | ||
14 | ( function( $, undefined ) { |
|
15 | ||
16 | //this code was taken from the https://github.com/furf/jquery-ui-touch-punch |
|
17 | var mouseEvents = { |
|
18 | touchstart: 'mousedown', |
|
19 | touchmove: 'mousemove', |
|
20 | touchend: 'mouseup' |
|
21 | }, |
|
22 | gesturesSupport = 'ongesturestart' in document.createElement('div'); |
|
23 | ||
24 | ||
25 | /** |
|
26 | * Convert a touch event to a mouse-like |
|
27 | */ |
|
28 | function makeMouseEvent (event) { |
|
29 | var touch = event.originalEvent.changedTouches[0]; |
|
30 | ||
31 | return $.extend(event, { |
|
32 | type: mouseEvents[event.type], |
|
33 | which: 1, |
|
34 | pageX: touch.pageX, |
|
35 | pageY: touch.pageY, |
|
36 | screenX: touch.screenX, |
|
37 | screenY: touch.screenY, |
|
38 | clientX: touch.clientX, |
|
39 | clientY: touch.clientY, |
|
40 | isTouchEvent: true |
|
41 | }); |
|
42 | } |
|
43 | ||
44 | var mouseProto = $.ui.mouse.prototype, |
|
45 | _mouseInit = $.ui.mouse.prototype._mouseInit; |
|
46 | ||
47 | mouseProto._mouseInit = function() { |
|
48 | var self = this; |
|
49 | self._touchActive = false; |
|
50 | ||
51 | this.element.bind( 'touchstart.' + this.widgetName, function(event) { |
|
52 | if (gesturesSupport && event.originalEvent.touches.length > 1) { return; } |
|
53 | self._touchActive = true; |
|
54 | return self._mouseDown(makeMouseEvent(event)); |
|
55 | }) |
|
56 | ||
57 | var self = this; |
|
58 | // these delegates are required to keep context |
|
59 | this._mouseMoveDelegate = function(event) { |
|
60 | if (gesturesSupport && event.originalEvent.touches && event.originalEvent.touches.length > 1) { return; } |
|
61 | if (self._touchActive) { |
|
62 | return self._mouseMove(makeMouseEvent(event)); |
|
63 | } |
|
64 | }; |
|
65 | this._mouseUpDelegate = function(event) { |
|
66 | if (self._touchActive) { |
|
67 | self._touchActive = false; |
|
68 | return self._mouseUp(makeMouseEvent(event)); |
|
69 | } |
|
70 | }; |
|
71 | ||
72 | $(document) |
|
73 | .bind('touchmove.'+ this.widgetName, this._mouseMoveDelegate) |
|
74 | .bind('touchend.' + this.widgetName, this._mouseUpDelegate); |
|
75 | ||
76 | _mouseInit.apply(this); |
|
77 | } |
|
78 | ||
79 | /** |
|
80 | * Simple implementation of jQuery like getters/setters |
|
81 | * var val = something(); |
|
82 | * something(val); |
|
83 | */ |
|
84 | var setter = function(setter, getter) { |
|
85 | return function(val) { |
|
86 | if (arguments.length === 0) { |
|
87 | return getter.apply(this); |
|
88 | } else { |
|
89 | setter.apply(this, arguments); |
|
90 | } |
|
91 | } |
|
92 | }; |
|
93 | ||
94 | /** |
|
95 | * Internet explorer rotates image relative left top corner, so we should |
|
96 | * shift image when it's rotated. |
|
97 | */ |
|
98 | var ieTransforms = { |
|
99 | '0': { |
|
100 | marginLeft: 0, |
|
101 | marginTop: 0, |
|
102 | filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1, SizingMethod="auto expand")' |
|
103 | }, |
|
104 | ||
105 | '90': { |
|
106 | marginLeft: -1, |
|
107 | marginTop: 1, |
|
108 | filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=-1, M21=1, M22=0, SizingMethod="auto expand")' |
|
109 | }, |
|
110 | ||
111 | '180': { |
|
112 | marginLeft: 0, |
|
113 | marginTop: 0, |
|
114 | filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=-1, M12=0, M21=0, M22=-1, SizingMethod="auto expand")' |
|
115 | }, |
|
116 | ||
117 | '270': { |
|
118 | marginLeft: -1, |
|
119 | marginTop: 1, |
|
120 | filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=1, M21=-1, M22=0, SizingMethod="auto expand")' |
|
121 | } |
|
122 | }, |
|
123 | // this test is the inversion of the css filters test from the modernizr project |
|
124 | useIeTransforms = function() { |
|
125 | var modElem = document.createElement('modernizr'), |
|
126 | mStyle = modElem.style, |
|
127 | omPrefixes = 'Webkit Moz O ms', |
|
128 | domPrefixes = omPrefixes.toLowerCase().split(' '), |
|
129 | props = ("transform" + ' ' + domPrefixes.join("Transform ") + "Transform").split(' '); |
|
130 | for ( var i in props ) { |
|
131 | var prop = props[i]; |
|
132 | if ( !$.contains(prop, "-") && mStyle[prop] !== undefined ) { |
|
133 | return false; |
|
134 | } |
|
135 | } |
|
136 | return true; |
|
137 | }(); |
|
138 | ||
139 | $.widget( "ui.iviewer", $.ui.mouse, { |
|
140 | widgetEventPrefix: "iviewer", |
|
141 | options : { |
|
142 | /** |
|
143 | * start zoom value for image, not used now |
|
144 | * may be equal to "fit" to fit image into container or scale in % |
|
145 | **/ |
|
146 | zoom: "fit", |
|
147 | /** |
|
148 | * base value to scale image |
|
149 | **/ |
|
150 | zoom_base: 100, |
|
151 | /** |
|
152 | * maximum zoom |
|
153 | **/ |
|
154 | zoom_max: 800, |
|
155 | /** |
|
156 | * minimum zoom |
|
157 | **/ |
|
158 | zoom_min: 25, |
|
159 | /** |
|
160 | * base of rate multiplier. |
|
161 | * zoom is calculated by formula: zoom_base * zoom_delta^rate |
|
162 | **/ |
|
163 | zoom_delta: 1.4, |
|
164 | /** |
|
165 | * whether the zoom should be animated. |
|
166 | */ |
|
167 | zoom_animation: true, |
|
168 | /** |
|
169 | * if true plugin doesn't add its own controls |
|
170 | **/ |
|
171 | ui_disabled: false, |
|
172 | /** |
|
173 | * If false mousewheel will be disabled |
|
174 | */ |
|
175 | mousewheel: true, |
|
176 | /** |
|
177 | * if false, plugin doesn't bind resize event on window and this must |
|
178 | * be handled manually |
|
179 | **/ |
|
180 | update_on_resize: true, |
|
181 | /** |
|
182 | * event is triggered when zoom value is changed |
|
183 | * @param int new zoom value |
|
184 | * @return boolean if false zoom action is aborted |
|
185 | **/ |
|
186 | onZoom: jQuery.noop, |
|
187 | /** |
|
188 | * event is triggered when zoom value is changed after image is set to the new dimensions |
|
189 | * @param int new zoom value |
|
190 | * @return boolean if false zoom action is aborted |
|
191 | **/ |
|
192 | onAfterZoom: jQuery.noop, |
|
193 | /** |
|
194 | * event is fired on drag begin |
|
195 | * @param object coords mouse coordinates on the image |
|
196 | * @return boolean if false is returned, drag action is aborted |
|
197 | **/ |
|
198 | onStartDrag: jQuery.noop, |
|
199 | /** |
|
200 | * event is fired on drag action |
|
201 | * @param object coords mouse coordinates on the image |
|
202 | **/ |
|
203 | onDrag: jQuery.noop, |
|
204 | /** |
|
205 | * event is fired on drag stop |
|
206 | * @param object coords mouse coordinates on the image |
|
207 | **/ |
|
208 | onStopDrag: jQuery.noop, |
|
209 | /** |
|
210 | * event is fired when mouse moves over image |
|
211 | * @param object coords mouse coordinates on the image |
|
212 | **/ |
|
213 | onMouseMove: jQuery.noop, |
|
214 | /** |
|
215 | * mouse click event |
|
216 | * @param object coords mouse coordinates on the image |
|
217 | **/ |
|
218 | onClick: jQuery.noop, |
|
219 | /** |
|
220 | * event is fired when image starts to load |
|
221 | */ |
|
222 | onStartLoad: null, |
|
223 | /** |
|
224 | * event is fired, when image is loaded and initially positioned |
|
225 | */ |
|
226 | onFinishLoad: null, |
|
227 | /** |
|
228 | * event is fired when image load error occurs |
|
229 | */ |
|
230 | onErrorLoad: null |
|
231 | }, |
|
232 | ||
233 | _create: function() { |
|
234 | var me = this; |
|
235 | ||
236 | //drag variables |
|
237 | this.dx = 0; |
|
238 | this.dy = 0; |
|
239 | ||
240 | /* object containing actual information about image |
|
241 | * @img_object.object - jquery img object |
|
242 | * @img_object.orig_{width|height} - original dimensions |
|
243 | * @img_object.display_{width|height} - actual dimensions |
|
244 | */ |
|
245 | this.img_object = {}; |
|
246 | ||
247 | this.zoom_object = {}; //object to show zoom status |
|
248 | ||
249 | this._angle = 0; |
|
250 | ||
251 | this.current_zoom = this.options.zoom; |
|
252 | ||
253 | if(this.options.src === null){ |
|
254 | return; |
|
255 | } |
|
256 | ||
257 | this.container = this.element; |
|
258 | ||
259 | this._updateContainerInfo(); |
|
260 | ||
261 | //init container |
|
262 | this.container.css("overflow","hidden"); |
|
263 | ||
264 | if (this.options.update_on_resize == true) { |
|
265 | $(window).resize(function() { |
|
266 | me.update(); |
|
267 | }); |
|
268 | } |
|
269 | ||
270 | this.img_object = new $.ui.iviewer.ImageObject(this.options.zoom_animation); |
|
271 | ||
272 | if (this.options.mousewheel) { |
|
273 | this.container.bind('mousewheel.iviewer', function(ev, delta) |
|
274 | { |
|
275 | //this event is there instead of containing div, because |
|
276 | //at opera it triggers many times on div |
|
277 | var zoom = (delta > 0)?1:-1, |
|
278 | container_offset = me.container.offset(), |
|
279 | mouse_pos = { |
|
280 | //jquery.mousewheel 3.1.0 uses strange MozMousePixelScroll event |
|
281 | //which is not being fixed by jQuery.Event |
|
282 | x: (ev.pageX || ev.originalEvent.pageX) - container_offset.left, |
|
283 | y: (ev.pageY || ev.originalEvent.pageX) - container_offset.top |
|
284 | }; |
|
285 | ||
286 | me.zoom_by(zoom, mouse_pos); |
|
287 | return false; |
|
288 | }); |
|
289 | ||
290 | if (gesturesSupport) { |
|
291 | var gestureThrottle = +new Date(); |
|
292 | var originalScale, originalCenter; |
|
293 | this.img_object.object() |
|
294 | // .bind('gesturestart', function(ev) { |
|
295 | .bind('touchstart', function(ev) { |
|
296 | originalScale = me.current_zoom; |
|
297 | var touches = ev.originalEvent.touches, |
|
298 | container_offset; |
|
299 | if (touches.length == 2) { |
|
300 | container_offset = me.container.offset(); |
|
301 | originalCenter = { |
|
302 | x: (touches[0].pageX + touches[1].pageX) / 2 - container_offset.left, |
|
303 | y: (touches[0].pageY + touches[1].pageY) / 2 - container_offset.top |
|
304 | }; |
|
305 | } else { |
|
306 | originalCenter = null; |
|
307 | } |
|
308 | }).bind('gesturechange', function(ev) { |
|
309 | //do not want to import throttle function from underscore |
|
310 | var d = +new Date(); |
|
311 | if ((d - gestureThrottle) < 50) { return; } |
|
312 | gestureThrottle = d; |
|
313 | var zoom = originalScale * ev.originalEvent.scale; |
|
314 | me.set_zoom(zoom, originalCenter); |
|
315 | ev.preventDefault(); |
|
316 | }).bind('gestureend', function(ev) { |
|
317 | originalCenter = null; |
|
318 | }); |
|
319 | } |
|
320 | } |
|
321 | ||
322 | //init object |
|
323 | this.img_object.object() |
|
324 | //bind mouse events |
|
325 | .click(function(e){return me._click(e)}) |
|
326 | .prependTo(this.container); |
|
327 | ||
328 | this.container.bind('mousemove', function(ev) { me._handleMouseMove(ev); }); |
|
329 | ||
330 | this.loadImage(this.options.src); |
|
331 | ||
332 | if(!this.options.ui_disabled) |
|
333 | { |
|
334 | this.createui(); |
|
335 | } |
|
336 | ||
337 | this._mouseInit(); |
|
338 | }, |
|
339 | ||
340 | destroy: function() { |
|
341 | $.Widget.prototype.destroy.call( this ); |
|
342 | this._mouseDestroy(); |
|
343 | this.img_object.object().remove(); |
|
344 | this.container.off('.iviewer'); |
|
345 | this.container.css('overflow', ''); //cleanup styles on destroy |
|
346 | }, |
|
347 | ||
348 | _updateContainerInfo: function() |
|
349 | { |
|
350 | this.options.height = this.container.height(); |
|
351 | this.options.width = this.container.width(); |
|
352 | }, |
|
353 | ||
354 | update: function() |
|
355 | { |
|
356 | this._updateContainerInfo() |
|
357 | this.setCoords(this.img_object.x(), this.img_object.y()); |
|
358 | }, |
|
359 | ||
360 | loadImage: function( src ) |
|
361 | { |
|
362 | this.current_zoom = this.options.zoom; |
|
363 | var me = this; |
|
364 | ||
365 | this._trigger('onStartLoad', 0, src); |
|
366 | ||
367 | this.container.addClass("iviewer_loading"); |
|
368 | this.img_object.load(src, function() { |
|
369 | me._imageLoaded(src); |
|
370 | }, function() { |
|
371 | me._trigger("onErrorLoad", 0, src); |
|
372 | }); |
|
373 | }, |
|
374 | ||
375 | _imageLoaded: function(src) { |
|
376 | this.container.removeClass("iviewer_loading"); |
|
377 | this.container.addClass("iviewer_cursor"); |
|
378 | ||
379 | if(this.options.zoom == "fit"){ |
|
380 | this.fit(true); |
|
381 | } |
|
382 | else { |
|
383 | this.set_zoom(this.options.zoom, true); |
|
384 | } |
|
385 | ||
386 | this._trigger('onFinishLoad', 0, src); |
|
387 | }, |
|
388 | ||
389 | /** |
|
390 | * fits image in the container |
|
391 | * |
|
392 | * @param {boolean} skip_animation |
|
393 | **/ |
|
394 | fit: function(skip_animation) |
|
395 | { |
|
396 | var aspect_ratio = this.img_object.orig_width() / this.img_object.orig_height(); |
|
397 | var window_ratio = this.options.width / this.options.height; |
|
398 | var choose_left = (aspect_ratio > window_ratio); |
|
399 | var new_zoom = 0; |
|
400 | ||
401 | if(choose_left){ |
|
402 | new_zoom = this.options.width / this.img_object.orig_width() * 100; |
|
403 | } |
|
404 | else { |
|
405 | new_zoom = this.options.height / this.img_object.orig_height() * 100; |
|
406 | } |
|
407 | ||
408 | this.set_zoom(new_zoom, skip_animation); |
|
409 | }, |
|
410 | ||
411 | /** |
|
412 | * center image in container |
|
413 | **/ |
|
414 | center: function() |
|
415 | { |
|
416 | this.setCoords(-Math.round((this.img_object.display_width() - this.options.width)/2), |
|
417 | -Math.round((this.img_object.display_height() - this.options.height)/2)); |
|
418 | }, |
|
419 | ||
420 | /** |
|
421 | * move a point in container to the center of display area |
|
422 | * @param x a point in container |
|
423 | * @param y a point in container |
|
424 | **/ |
|
425 | moveTo: function(x, y) |
|
426 | { |
|
427 | var dx = x-Math.round(this.options.width/2); |
|
428 | var dy = y-Math.round(this.options.height/2); |
|
429 | ||
430 | var new_x = this.img_object.x() - dx; |
|
431 | var new_y = this.img_object.y() - dy; |
|
432 | ||
433 | this.setCoords(new_x, new_y); |
|
434 | }, |
|
435 | ||
436 | /** |
|
437 | * Get container offset object. |
|
438 | */ |
|
439 | getContainerOffset: function() { |
|
440 | return jQuery.extend({}, this.container.offset()); |
|
441 | }, |
|
442 | ||
443 | /** |
|
444 | * set coordinates of upper left corner of image object |
|
445 | **/ |
|
446 | setCoords: function(x,y) |
|
447 | { |
|
448 | //do nothing while image is being loaded |
|
449 | if(!this.img_object.loaded()) { return; } |
|
450 | ||
451 | var coords = this._correctCoords(x,y); |
|
452 | this.img_object.x(coords.x); |
|
453 | this.img_object.y(coords.y); |
|
454 | }, |
|
455 | ||
456 | _correctCoords: function( x, y ) |
|
457 | { |
|
458 | x = parseInt(x, 10); |
|
459 | y = parseInt(y, 10); |
|
460 | ||
461 | //check new coordinates to be correct (to be in rect) |
|
462 | if(y > 0){ |
|
463 | y = 0; |
|
464 | } |
|
465 | if(x > 0){ |
|
466 | x = 0; |
|
467 | } |
|
468 | if(y + this.img_object.display_height() < this.options.height){ |
|
469 | y = this.options.height - this.img_object.display_height(); |
|
470 | } |
|
471 | if(x + this.img_object.display_width() < this.options.width){ |
|
472 | x = this.options.width - this.img_object.display_width(); |
|
473 | } |
|
474 | if(this.img_object.display_width() <= this.options.width){ |
|
475 | x = -(this.img_object.display_width() - this.options.width)/2; |
|
476 | } |
|
477 | if(this.img_object.display_height() <= this.options.height){ |
|
478 | y = -(this.img_object.display_height() - this.options.height)/2; |
|
479 | } |
|
480 | ||
481 | return { x: x, y:y }; |
|
482 | }, |
|
483 | ||
484 | ||
485 | /** |
|
486 | * convert coordinates on the container to the coordinates on the image (in original size) |
|
487 | * |
|
488 | * @return object with fields x,y according to coordinates or false |
|
489 | * if initial coords are not inside image |
|
490 | **/ |
|
491 | containerToImage : function (x,y) |
|
492 | { |
|
493 | var coords = { x : x - this.img_object.x(), |
|
494 | y : y - this.img_object.y() |
|
495 | }; |
|
496 | ||
497 | coords = this.img_object.toOriginalCoords(coords); |
|
498 | ||
499 | return { x : util.descaleValue(coords.x, this.current_zoom), |
|
500 | y : util.descaleValue(coords.y, this.current_zoom) |
|
501 | }; |
|
502 | }, |
|
503 | ||
504 | /** |
|
505 | * convert coordinates on the image (in original size, and zero angle) to the coordinates on the container |
|
506 | * |
|
507 | * @return object with fields x,y according to coordinates |
|
508 | **/ |
|
509 | imageToContainer : function (x,y) |
|
510 | { |
|
511 | var coords = { |
|
512 | x : util.scaleValue(x, this.current_zoom), |
|
513 | y : util.scaleValue(y, this.current_zoom) |
|
514 | }; |
|
515 | ||
516 | return this.img_object.toRealCoords(coords); |
|
517 | }, |
|
518 | ||
519 | /** |
|
520 | * get mouse coordinates on the image |
|
521 | * @param e - object containing pageX and pageY fields, e.g. mouse event object |
|
522 | * |
|
523 | * @return object with fields x,y according to coordinates or false |
|
524 | * if initial coords are not inside image |
|
525 | **/ |
|
526 | _getMouseCoords : function(e) |
|
527 | { |
|
528 | var containerOffset = this.container.offset(); |
|
529 | coords = this.containerToImage(e.pageX - containerOffset.left, e.pageY - containerOffset.top); |
|
530 | ||
531 | return coords; |
|
532 | }, |
|
533 | ||
534 | /** |
|
535 | * set image scale to the new_zoom |
|
536 | * |
|
537 | * @param {number} new_zoom image scale in % |
|
538 | * @param {boolean} skip_animation |
|
539 | * @param {x: number, y: number} Coordinates of point the should not be moved on zoom. The default is the center of image. |
|
540 | **/ |
|
541 | set_zoom: function(new_zoom, skip_animation, zoom_center) |
|
542 | { |
|
543 | if (this._trigger('onZoom', 0, new_zoom) == false) { |
|
544 | return; |
|
545 | } |
|
546 | ||
547 | //do nothing while image is being loaded |
|
548 | if(!this.img_object.loaded()) { return; } |
|
549 | ||
550 | zoom_center = zoom_center || { |
|
551 | x: Math.round(this.options.width/2), |
|
552 | y: Math.round(this.options.height/2) |
|
553 | } |
|
554 | ||
555 | if(new_zoom < this.options.zoom_min) |
|
556 | { |
|
557 | new_zoom = this.options.zoom_min; |
|
558 | } |
|
559 | else if(new_zoom > this.options.zoom_max) |
|
560 | { |
|
561 | new_zoom = this.options.zoom_max; |
|
562 | } |
|
563 | ||
564 | /* we fake these values to make fit zoom properly work */ |
|
565 | if(this.current_zoom == "fit") |
|
566 | { |
|
567 | var old_x = zoom_center.x + Math.round(this.img_object.orig_width()/2); |
|
568 | var old_y = zoom_center.y + Math.round(this.img_object.orig_height()/2); |
|
569 | this.current_zoom = 100; |
|
570 | } |
|
571 | else { |
|
572 | var old_x = -this.img_object.x() + zoom_center.x; |
|
573 | var old_y = -this.img_object.y() + zoom_center.y |
|
574 | } |
|
575 | ||
576 | var new_width = util.scaleValue(this.img_object.orig_width(), new_zoom); |
|
577 | var new_height = util.scaleValue(this.img_object.orig_height(), new_zoom); |
|
578 | var new_x = util.scaleValue( util.descaleValue(old_x, this.current_zoom), new_zoom); |
|
579 | var new_y = util.scaleValue( util.descaleValue(old_y, this.current_zoom), new_zoom); |
|
580 | ||
581 | new_x = zoom_center.x - new_x; |
|
582 | new_y = zoom_center.y - new_y; |
|
583 | ||
584 | new_width = Math.floor(new_width); |
|
585 | new_height = Math.floor(new_height); |
|
586 | new_x = Math.floor(new_x); |
|
587 | new_y = Math.floor(new_y); |
|
588 | ||
589 | this.img_object.display_width(new_width); |
|
590 | this.img_object.display_height(new_height); |
|
591 | ||
592 | var coords = this._correctCoords( new_x, new_y ), |
|
593 | self = this; |
|
594 | ||
595 | this.img_object.setImageProps(new_width, new_height, coords.x, coords.y, |
|
596 | skip_animation, function() { |
|
597 | self._trigger('onAfterZoom', 0, new_zoom ); |
|
598 | }); |
|
599 | this.current_zoom = new_zoom; |
|
600 | ||
601 | this.update_status(); |
|
602 | }, |
|
603 | ||
604 | /** |
|
605 | * changes zoom scale by delta |
|
606 | * zoom is calculated by formula: zoom_base * zoom_delta^rate |
|
607 | * @param Integer delta number to add to the current multiplier rate number |
|
608 | * @param {x: number, y: number=} Coordinates of point the should not be moved on zoom. |
|
609 | **/ |
|
610 | zoom_by: function(delta, zoom_center) |
|
611 | { |
|
612 | var closest_rate = this.find_closest_zoom_rate(this.current_zoom); |
|
613 | ||
614 | var next_rate = closest_rate + delta; |
|
615 | var next_zoom = this.options.zoom_base * Math.pow(this.options.zoom_delta, next_rate) |
|
616 | if(delta > 0 && next_zoom < this.current_zoom) |
|
617 | { |
|
618 | next_zoom *= this.options.zoom_delta; |
|
619 | } |
|
620 | ||
621 | if(delta < 0 && next_zoom > this.current_zoom) |
|
622 | { |
|
623 | next_zoom /= this.options.zoom_delta; |
|
624 | } |
|
625 | ||
626 | this.set_zoom(next_zoom, undefined, zoom_center); |
|
627 | }, |
|
628 | ||
629 | /** |
|
630 | * Rotate image |
|
631 | * @param {num} deg Degrees amount to rotate. Positive values rotate image clockwise. |
|
632 | * Currently 0, 90, 180, 270 and -90, -180, -270 values are supported |
|
633 | * |
|
634 | * @param {boolean} abs If the flag is true if, the deg parameter will be considered as |
|
635 | * a absolute value and relative otherwise. |
|
636 | * @return {num|null} Method will return current image angle if called without any arguments. |
|
637 | **/ |
|
638 | angle: function(deg, abs) { |
|
639 | if (arguments.length === 0) { return this.img_object.angle(); } |
|
640 | ||
641 | if (deg < -270 || deg > 270 || deg % 90 !== 0) { return; } |
|
642 | if (!abs) { deg += this.img_object.angle(); } |
|
643 | if (deg < 0) { deg += 360; } |
|
644 | if (deg >= 360) { deg -= 360; } |
|
645 | ||
646 | if (deg === this.img_object.angle()) { return; } |
|
647 | ||
648 | this.img_object.angle(deg); |
|
649 | //the rotate behavior is different in all editors. For now we just center the |
|
650 | //image. However, it will be better to try to keep the position. |
|
651 | this.center(); |
|
652 | this._trigger('angle', 0, { angle: this.img_object.angle() }); |
|
653 | }, |
|
654 | ||
655 | /** |
|
656 | * finds closest multiplier rate for value |
|
657 | * basing on zoom_base and zoom_delta values from settings |
|
658 | * @param Number value zoom value to examine |
|
659 | **/ |
|
660 | find_closest_zoom_rate: function(value) |
|
661 | { |
|
662 | if(value == this.options.zoom_base) |
|
663 | { |
|
664 | return 0; |
|
665 | } |
|
666 | ||
667 | function div(val1,val2) { return val1 / val2 }; |
|
668 | function mul(val1,val2) { return val1 * val2 }; |
|
669 | ||
670 | var func = (value > this.options.zoom_base)?mul:div; |
|
671 | var sgn = (value > this.options.zoom_base)?1:-1; |
|
672 | ||
673 | var mltplr = this.options.zoom_delta; |
|
674 | var rate = 1; |
|
675 | ||
676 | while(Math.abs(func(this.options.zoom_base, Math.pow(mltplr,rate)) - value) > |
|
677 | Math.abs(func(this.options.zoom_base, Math.pow(mltplr,rate+1)) - value)) |
|
678 | { |
|
679 | rate++; |
|
680 | } |
|
681 | ||
682 | return sgn * rate; |
|
683 | }, |
|
684 | ||
685 | /* update scale info in the container */ |
|
686 | update_status: function() |
|
687 | { |
|
688 | if(!this.options.ui_disabled) |
|
689 | { |
|
690 | var percent = Math.round(100*this.img_object.display_height()/this.img_object.orig_height()); |
|
691 | if(percent) |
|
692 | { |
|
693 | this.zoom_object.html(percent + "%"); |
|
694 | } |
|
695 | } |
|
696 | }, |
|
697 | ||
698 | /** |
|
699 | * Get some information about the image. |
|
700 | * Currently orig_(width|height), display_(width|height), angle, zoom and src params are supported. |
|
701 | * |
|
702 | * @param {string} parameter to check |
|
703 | * @param {boolean} withoutRotation if param is orig_width or orig_height and this flag is set to true, |
|
704 | * method will return original image width without considering rotation. |
|
705 | * |
|
706 | */ |
|
707 | info: function(param, withoutRotation) { |
|
708 | if (!param) { return; } |
|
709 | ||
710 | switch (param) { |
|
711 | case 'orig_width': |
|
712 | case 'orig_height': |
|
713 | if (withoutRotation) { |
|
714 | return (this.img_object.angle() % 180 === 0 ? this.img_object[param]() : |
|
715 | param === 'orig_width' ? this.img_object.orig_height() : |
|
716 | this.img_object.orig_width()); |
|
717 | } else { |
|
718 | return this.img_object[param](); |
|
719 | } |
|
720 | case 'display_width': |
|
721 | case 'display_height': |
|
722 | case 'angle': |
|
723 | return this.img_object[param](); |
|
724 | case 'zoom': |
|
725 | return this.current_zoom; |
|
726 | case 'src': |
|
727 | return this.img_object.object().attr('src'); |
|
728 | case 'coords': |
|
729 | return { |
|
730 | x: this.img_object.x(), |
|
731 | y: this.img_object.y() |
|
732 | }; |
|
733 | } |
|
734 | }, |
|
735 | ||
736 | /** |
|
737 | * callback for handling mousdown event to start dragging image |
|
738 | **/ |
|
739 | _mouseStart: function( e ) |
|
740 | { |
|
741 | $.ui.mouse.prototype._mouseStart.call(this, e); |
|
742 | if (this._trigger('onStartDrag', 0, this._getMouseCoords(e)) === false) { |
|
743 | return false; |
|
744 | } |
|
745 | ||
746 | /* start drag event*/ |
|
747 | this.container.addClass("iviewer_drag_cursor"); |
|
748 | ||
749 | //#10: fix movement quirks for ipad |
|
750 | this._dragInitialized = !(e.originalEvent.changedTouches && e.originalEvent.changedTouches.length==1); |
|
751 | ||
752 | this.dx = e.pageX - this.img_object.x(); |
|
753 | this.dy = e.pageY - this.img_object.y(); |
|
754 | return true; |
|
755 | }, |
|
756 | ||
757 | _mouseCapture: function( e ) { |
|
758 | return true; |
|
759 | }, |
|
760 | ||
761 | /** |
|
762 | * Handle mouse move if needed. User can avoid using this callback, because |
|
763 | * he can get the same information through public methods. |
|
764 | * @param {jQuery.Event} e |
|
765 | */ |
|
766 | _handleMouseMove: function(e) { |
|
767 | this._trigger('onMouseMove', e, this._getMouseCoords(e)); |
|
768 | }, |
|
769 | ||
770 | /** |
|
771 | * callback for handling mousemove event to drag image |
|
772 | **/ |
|
773 | _mouseDrag: function(e) |
|
774 | { |
|
775 | $.ui.mouse.prototype._mouseDrag.call(this, e); |
|
776 | ||
777 | //#10: imitate mouseStart, because we can get here without it on iPad for some reason |
|
778 | if (!this._dragInitialized) { |
|
779 | this.dx = e.pageX - this.img_object.x(); |
|
780 | this.dy = e.pageY - this.img_object.y(); |
|
781 | this._dragInitialized = true; |
|
782 | } |
|
783 | ||
784 | var ltop = e.pageY - this.dy; |
|
785 | var lleft = e.pageX - this.dx; |
|
786 | ||
787 | this.setCoords(lleft, ltop); |
|
788 | this._trigger('onDrag', e, this._getMouseCoords(e)); |
|
789 | return false; |
|
790 | }, |
|
791 | ||
792 | /** |
|
793 | * callback for handling stop drag |
|
794 | **/ |
|
795 | _mouseStop: function(e) |
|
796 | { |
|
797 | $.ui.mouse.prototype._mouseStop.call(this, e); |
|
798 | this.container.removeClass("iviewer_drag_cursor"); |
|
799 | this._trigger('onStopDrag', 0, this._getMouseCoords(e)); |
|
800 | }, |
|
801 | ||
802 | _click: function(e) |
|
803 | { |
|
804 | this._trigger('onClick', 0, this._getMouseCoords(e)); |
|
805 | }, |
|
806 | ||
807 | /** |
|
808 | * create zoom buttons info box |
|
809 | **/ |
|
810 | createui: function() |
|
811 | { |
|
812 | var me=this; |
|
813 | ||
814 | $("<div>", { 'class': "iviewer_zoom_in iviewer_common iviewer_button"}) |
|
815 | .bind('mousedown touchstart',function(){me.zoom_by(1); return false;}) |
|
816 | .appendTo(this.container); |
|
817 | ||
818 | $("<div>", { 'class': "iviewer_zoom_out iviewer_common iviewer_button"}) |
|
819 | .bind('mousedown touchstart',function(){me.zoom_by(- 1); return false;}) |
|
820 | .appendTo(this.container); |
|
821 | ||
822 | $("<div>", { 'class': "iviewer_zoom_zero iviewer_common iviewer_button"}) |
|
823 | .bind('mousedown touchstart',function(){me.set_zoom(100); return false;}) |
|
824 | .appendTo(this.container); |
|
825 | ||
826 | $("<div>", { 'class': "iviewer_zoom_fit iviewer_common iviewer_button"}) |
|
827 | .bind('mousedown touchstart',function(){me.fit(this); return false;}) |
|
828 | .appendTo(this.container); |
|
829 | ||
830 | this.zoom_object = $("<div>").addClass("iviewer_zoom_status iviewer_common") |
|
831 | .appendTo(this.container); |
|
832 | ||
833 | $("<div>", { 'class': "iviewer_rotate_left iviewer_common iviewer_button"}) |
|
834 | .bind('mousedown touchstart',function(){me.angle(-90); return false;}) |
|
835 | .appendTo(this.container); |
|
836 | ||
837 | $("<div>", { 'class': "iviewer_rotate_right iviewer_common iviewer_button" }) |
|
838 | .bind('mousedown touchstart',function(){me.angle(90); return false;}) |
|
839 | .appendTo(this.container); |
|
840 | ||
841 | this.update_status(); //initial status update |
|
842 | } |
|
843 | ||
844 | } ); |
|
845 | ||
846 | /** |
|
847 | * @class $.ui.iviewer.ImageObject Class represents image and provides public api without |
|
848 | * extending image prototype. |
|
849 | * @constructor |
|
850 | * @param {boolean} do_anim Do we want to animate image on dimension changes? |
|
851 | */ |
|
852 | $.ui.iviewer.ImageObject = function(do_anim) { |
|
853 | this._img = $("<img>") |
|
854 | //this is needed, because chromium sets them auto otherwise |
|
855 | .css({ position: "absolute", top :"0px", left: "0px"}); |
|
856 | ||
857 | this._loaded = false; |
|
858 | this._swapDimensions = false; |
|
859 | this._do_anim = do_anim || false; |
|
860 | this.x(0, true); |
|
861 | this.y(0, true); |
|
862 | this.angle(0); |
|
863 | }; |
|
864 | ||
865 | ||
866 | /** @lends $.ui.iviewer.ImageObject.prototype */ |
|
867 | (function() { |
|
868 | /** |
|
869 | * Restore initial object state. |
|
870 | * |
|
871 | * @param {number} w Image width. |
|
872 | * @param {number} h Image height. |
|
873 | */ |
|
874 | this._reset = function(w, h) { |
|
875 | this._angle = 0; |
|
876 | this._swapDimensions = false; |
|
877 | this.x(0); |
|
878 | this.y(0); |
|
879 | ||
880 | this.orig_width(w); |
|
881 | this.orig_height(h); |
|
882 | this.display_width(w); |
|
883 | this.display_height(h); |
|
884 | }; |
|
885 | ||
886 | /** |
|
887 | * Check if image is loaded. |
|
888 | * |
|
889 | * @return {boolean} |
|
890 | */ |
|
891 | this.loaded = function() { return this._loaded; }; |
|
892 | ||
893 | /** |
|
894 | * Load image. |
|
895 | * |
|
896 | * @param {string} src Image url. |
|
897 | * @param {Function=} loaded Function will be called on image load. |
|
898 | */ |
|
899 | this.load = function(src, loaded, error) { |
|
900 | var self = this; |
|
901 | ||
902 | loaded = loaded || jQuery.noop; |
|
903 | this._loaded = false; |
|
904 | ||
905 | //If we assign new image url to the this._img IE9 fires onload event and image width and |
|
906 | //height are set to zero. So, we create another image object and load image through it. |
|
907 | var img = new Image(); |
|
908 | img.onload = function() { |
|
909 | self._loaded = true; |
|
910 | self._reset(this.width, this.height); |
|
911 | ||
912 | self._img |
|
913 | .removeAttr("width") |
|
914 | .removeAttr("height") |
|
915 | .removeAttr("style") |
|
916 | //max-width is reset, because plugin breaks in the twitter bootstrap otherwise |
|
917 | .css({ position: "absolute", top :"0px", left: "0px", maxWidth: "none"}) |
|
918 | ||
919 | self._img[0].src = src; |
|
920 | loaded(); |
|
921 | }; |
|
922 | ||
923 | img.onerror = error; |
|
924 | ||
925 | //we need this because sometimes internet explorer 8 fires onload event |
|
926 | //right after assignment (synchronously) |
|
927 | setTimeout(function() { |
|
928 | img.src = src; |
|
929 | }, 0); |
|
930 | ||
931 | this.angle(0); |
|
932 | }; |
|
933 | ||
934 | this._dimension = function(prefix, name) { |
|
935 | var horiz = '_' + prefix + '_' + name, |
|
936 | vert = '_' + prefix + '_' + (name === 'height' ? 'width' : 'height'); |
|
937 | return setter(function(val) { |
|
938 | this[this._swapDimensions ? horiz: vert] = val; |
|
939 | }, |
|
940 | function() { |
|
941 | return this[this._swapDimensions ? horiz: vert]; |
|
942 | }); |
|
943 | }; |
|
944 | ||
945 | /** |
|
946 | * Getters and setter for common image dimensions. |
|
947 | * display_ means real image tag dimensions |
|
948 | * orig_ means physical image dimensions. |
|
949 | * Note, that dimensions are swapped if image is rotated. It necessary, |
|
950 | * because as little as possible code should know about rotation. |
|
951 | */ |
|
952 | this.display_width = this._dimension('display', 'width'), |
|
953 | this.display_height = this._dimension('display', 'height'), |
|
954 | this.display_diff = function() { return Math.floor( this.display_width() - this.display_height() ) }; |
|
955 | this.orig_width = this._dimension('orig', 'width'), |
|
956 | this.orig_height = this._dimension('orig', 'height'), |
|
957 | ||
958 | /** |
|
959 | * Setter for X coordinate. If image is rotated we need to additionaly shift an |
|
960 | * image to map image coordinate to the visual position. |
|
961 | * |
|
962 | * @param {number} val Coordinate value. |
|
963 | * @param {boolean} skipCss If true, we only set the value and do not touch the dom. |
|
964 | */ |
|
965 | this.x = setter(function(val, skipCss) { |
|
966 | this._x = val; |
|
967 | if (!skipCss) { |
|
968 | this._finishAnimation(); |
|
969 | this._img.css("left",this._x + (this._swapDimensions ? this.display_diff() / 2 : 0) + "px"); |
|
970 | } |
|
971 | }, |
|
972 | function() { |
|
973 | return this._x; |
|
974 | }); |
|
975 | ||
976 | /** |
|
977 | * Setter for Y coordinate. If image is rotated we need to additionaly shift an |
|
978 | * image to map image coordinate to the visual position. |
|
979 | * |
|
980 | * @param {number} val Coordinate value. |
|
981 | * @param {boolean} skipCss If true, we only set the value and do not touch the dom. |
|
982 | */ |
|
983 | this.y = setter(function(val, skipCss) { |
|
984 | this._y = val; |
|
985 | if (!skipCss) { |
|
986 | this._finishAnimation(); |
|
987 | this._img.css("top",this._y - (this._swapDimensions ? this.display_diff() / 2 : 0) + "px"); |
|
988 | } |
|
989 | }, |
|
990 | function() { |
|
991 | return this._y; |
|
992 | }); |
|
993 | ||
994 | /** |
|
995 | * Perform image rotation. |
|
996 | * |
|
997 | * @param {number} deg Absolute image angle. The method will work with values 0, 90, 180, 270 degrees. |
|
998 | */ |
|
999 | this.angle = setter(function(deg) { |
|
1000 | var prevSwap = this._swapDimensions; |
|
1001 | ||
1002 | this._angle = deg; |
|
1003 | this._swapDimensions = deg % 180 !== 0; |
|
1004 | ||
1005 | if (prevSwap !== this._swapDimensions) { |
|
1006 | var verticalMod = this._swapDimensions ? -1 : 1; |
|
1007 | this.x(this.x() - verticalMod * this.display_diff() / 2, true); |
|
1008 | this.y(this.y() + verticalMod * this.display_diff() / 2, true); |
|
1009 | }; |
|
1010 | ||
1011 | var cssVal = 'rotate(' + deg + 'deg)', |
|
1012 | img = this._img; |
|
1013 | ||
1014 | jQuery.each(['', '-webkit-', '-moz-', '-o-', '-ms-'], function(i, prefix) { |
|
1015 | img.css(prefix + 'transform', cssVal); |
|
1016 | }); |
|
1017 | ||
1018 | if (useIeTransforms) { |
|
1019 | jQuery.each(['-ms-', ''], function(i, prefix) { |
|
1020 | img.css(prefix + 'filter', ieTransforms[deg].filter); |
|
1021 | }); |
|
1022 | ||
1023 | img.css({ |
|
1024 | marginLeft: ieTransforms[deg].marginLeft * this.display_diff() / 2, |
|
1025 | marginTop: ieTransforms[deg].marginTop * this.display_diff() / 2 |
|
1026 | }); |
|
1027 | } |
|
1028 | }, |
|
1029 | function() { return this._angle; }); |
|
1030 | ||
1031 | /** |
|
1032 | * Map point in the container coordinates to the point in image coordinates. |
|
1033 | * You will get coordinates of point on image with respect to rotation, |
|
1034 | * but will be set as if image was not rotated. |
|
1035 | * So, if image was rotated 90 degrees, it's (0,0) point will be on the |
|
1036 | * top right corner. |
|
1037 | * |
|
1038 | * @param {{x: number, y: number}} point Point in container coordinates. |
|
1039 | * @return {{x: number, y: number}} |
|
1040 | */ |
|
1041 | this.toOriginalCoords = function(point) { |
|
1042 | switch (this.angle()) { |
|
1043 | case 0: return { x: point.x, y: point.y } |
|
1044 | case 90: return { x: point.y, y: this.display_width() - point.x } |
|
1045 | case 180: return { x: this.display_width() - point.x, y: this.display_height() - point.y } |
|
1046 | case 270: return { x: this.display_height() - point.y, y: point.x } |
|
1047 | } |
|
1048 | }; |
|
1049 | ||
1050 | /** |
|
1051 | * Map point in the image coordinates to the point in container coordinates. |
|
1052 | * You will get coordinates of point on container with respect to rotation. |
|
1053 | * Note, if image was rotated 90 degrees, it's (0,0) point will be on the |
|
1054 | * top right corner. |
|
1055 | * |
|
1056 | * @param {{x: number, y: number}} point Point in container coordinates. |
|
1057 | * @return {{x: number, y: number}} |
|
1058 | */ |
|
1059 | this.toRealCoords = function(point) { |
|
1060 | switch (this.angle()) { |
|
1061 | case 0: return { x: this.x() + point.x, y: this.y() + point.y } |
|
1062 | case 90: return { x: this.x() + this.display_width() - point.y, y: this.y() + point.x} |
|
1063 | case 180: return { x: this.x() + this.display_width() - point.x, y: this.y() + this.display_height() - point.y} |
|
1064 | case 270: return { x: this.x() + point.y, y: this.y() + this.display_height() - point.x} |
|
1065 | } |
|
1066 | }; |
|
1067 | ||
1068 | /** |
|
1069 | * @return {jQuery} Return image node. this is needed to add event handlers. |
|
1070 | */ |
|
1071 | this.object = setter(jQuery.noop, |
|
1072 | function() { return this._img; }); |
|
1073 | ||
1074 | /** |
|
1075 | * Change image properties. |
|
1076 | * |
|
1077 | * @param {number} disp_w Display width; |
|
1078 | * @param {number} disp_h Display height; |
|
1079 | * @param {number} x |
|
1080 | * @param {number} y |
|
1081 | * @param {boolean} skip_animation If true, the animation will be skiped despite the |
|
1082 | * value set in constructor. |
|
1083 | * @param {Function=} complete Call back will be fired when zoom will be complete. |
|
1084 | */ |
|
1085 | this.setImageProps = function(disp_w, disp_h, x, y, skip_animation, complete) { |
|
1086 | complete = complete || jQuery.noop; |
|
1087 | ||
1088 | this.display_width(disp_w); |
|
1089 | this.display_height(disp_h); |
|
1090 | this.x(x, true); |
|
1091 | this.y(y, true); |
|
1092 | ||
1093 | var w = this._swapDimensions ? disp_h : disp_w; |
|
1094 | var h = this._swapDimensions ? disp_w : disp_h; |
|
1095 | ||
1096 | var params = { |
|
1097 | width: w, |
|
1098 | height: h, |
|
1099 | top: y - (this._swapDimensions ? this.display_diff() / 2 : 0) + "px", |
|
1100 | left: x + (this._swapDimensions ? this.display_diff() / 2 : 0) + "px" |
|
1101 | }; |
|
1102 | ||
1103 | if (useIeTransforms) { |
|
1104 | jQuery.extend(params, { |
|
1105 | marginLeft: ieTransforms[this.angle()].marginLeft * this.display_diff() / 2, |
|
1106 | marginTop: ieTransforms[this.angle()].marginTop * this.display_diff() / 2 |
|
1107 | }); |
|
1108 | } |
|
1109 | ||
1110 | var swapDims = this._swapDimensions, |
|
1111 | img = this._img; |
|
1112 | ||
1113 | //here we come: another IE oddness. If image is rotated 90 degrees with a filter, than |
|
1114 | //width and height getters return real width and height of rotated image. The bad news |
|
1115 | //is that to set height you need to set a width and vice versa. Fuck IE. |
|
1116 | //So, in this case we have to animate width and height manually. |
|
1117 | if(useIeTransforms && swapDims) { |
|
1118 | var ieh = this._img.width(), |
|
1119 | iew = this._img.height(), |
|
1120 | iedh = params.height - ieh; |
|
1121 | iedw = params.width - iew; |
|
1122 | ||
1123 | delete params.width; |
|
1124 | delete params.height; |
|
1125 | } |
|
1126 | ||
1127 | if (this._do_anim && !skip_animation) { |
|
1128 | this._img.stop(true) |
|
1129 | .animate(params, { |
|
1130 | duration: 200, |
|
1131 | complete: complete, |
|
1132 | step: function(now, fx) { |
|
1133 | if(useIeTransforms && swapDims && (fx.prop === 'top')) { |
|
1134 | var percent = (now - fx.start) / (fx.end - fx.start); |
|
1135 | ||
1136 | img.height(ieh + iedh * percent); |
|
1137 | img.width(iew + iedw * percent); |
|
1138 | img.css('top', now); |
|
1139 | } |
|
1140 | } |
|
1141 | }); |
|
1142 | } else { |
|
1143 | this._img.css(params); |
|
1144 | setTimeout(complete, 0); //both if branches should behave equally. |
|
1145 | } |
|
1146 | }; |
|
1147 | ||
1148 | //if we set image coordinates we need to be sure that no animation is active atm |
|
1149 | this._finishAnimation = function() { |
|
1150 | this._img.stop(true, true); |
|
1151 | } |
|
1152 | ||
1153 | }).apply($.ui.iviewer.ImageObject.prototype); |
|
1154 | ||
1155 | ||
1156 | ||
1157 | var util = { |
|
1158 | scaleValue: function(value, toZoom) |
|
1159 | { |
|
1160 | return value * toZoom / 100; |
|
1161 | }, |
|
1162 | ||
1163 | descaleValue: function(value, fromZoom) |
|
1164 | { |
|
1165 | return value * 100 / fromZoom; |
|
1166 | } |
|
1167 | }; |
|
1168 | ||
1169 | } )( jQuery, undefined ); |
|
1170 |
@@ 13-47 (lines=35) @@ | ||
10 | * Author: Dmitry Petrov |
|
11 | * Version: 0.7.7 |
|
12 | */ |
|
13 | (function($,undefined){var mouseEvents={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup"},gesturesSupport="ongesturestart"in document.createElement("div");function makeMouseEvent(event){var touch=event.originalEvent.changedTouches[0];return $.extend(event,{type:mouseEvents[event.type],which:1,pageX:touch.pageX,pageY:touch.pageY,screenX:touch.screenX,screenY:touch.screenY,clientX:touch.clientX,clientY:touch.clientY,isTouchEvent:true})}var mouseProto=$.ui.mouse.prototype,_mouseInit=$.ui.mouse.prototype._mouseInit; |
|
14 | mouseProto._mouseInit=function(){var self=this;self._touchActive=false;this.element.bind("touchstart."+this.widgetName,function(event){if(gesturesSupport&&event.originalEvent.touches.length>1)return;self._touchActive=true;return self._mouseDown(makeMouseEvent(event))});var self=this;this._mouseMoveDelegate=function(event){if(gesturesSupport&&event.originalEvent.touches&&event.originalEvent.touches.length>1)return;if(self._touchActive)return self._mouseMove(makeMouseEvent(event))};this._mouseUpDelegate= |
|
15 | function(event){if(self._touchActive){self._touchActive=false;return self._mouseUp(makeMouseEvent(event))}};$(document).bind("touchmove."+this.widgetName,this._mouseMoveDelegate).bind("touchend."+this.widgetName,this._mouseUpDelegate);_mouseInit.apply(this)};var setter=function(setter,getter){return function(val){if(arguments.length===0)return getter.apply(this);else setter.apply(this,arguments)}};var ieTransforms={"0":{marginLeft:0,marginTop:0,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1, SizingMethod="auto expand")'}, |
|
16 | 90:{marginLeft:-1,marginTop:1,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=-1, M21=1, M22=0, SizingMethod="auto expand")'},180:{marginLeft:0,marginTop:0,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=-1, M12=0, M21=0, M22=-1, SizingMethod="auto expand")'},270:{marginLeft:-1,marginTop:1,filter:'progid:DXImageTransform.Microsoft.Matrix(M11=0, M12=1, M21=-1, M22=0, SizingMethod="auto expand")'}},useIeTransforms=function(){var el=document.createElement("div");el.style.cssText=["-ms-", |
|
17 | "",""].join("filter:blur(2px); ");return!!el.style.cssText&&document.documentMode<9}();$.widget("ui.iviewer",$.ui.mouse,{widgetEventPrefix:"iviewer",options:{zoom:"fit",zoom_base:100,zoom_max:800,zoom_min:25,zoom_delta:1.4,zoom_animation:true,ui_disabled:false,mousewheel:true,update_on_resize:true,onZoom:jQuery.noop,onAfterZoom:jQuery.noop,onStartDrag:jQuery.noop,onDrag:jQuery.noop,onStopDrag:jQuery.noop,onMouseMove:jQuery.noop,onClick:jQuery.noop,onStartLoad:null,onFinishLoad:null,onErrorLoad:null}, |
|
18 | _create:function(){var me=this;this.dx=0;this.dy=0;this.img_object={};this.zoom_object={};this._angle=0;this.current_zoom=this.options.zoom;if(this.options.src===null)return;this.container=this.element;this._updateContainerInfo();this.container.css("overflow","hidden");if(this.options.update_on_resize==true)$(window).resize(function(){me.update()});this.img_object=new $.ui.iviewer.ImageObject(this.options.zoom_animation);if(this.options.mousewheel){this.container.bind("mousewheel.iviewer",function(ev, |
|
19 | delta){var zoom=delta>0?1:-1,container_offset=me.container.offset(),mouse_pos={x:ev.pageX-container_offset.left,y:ev.pageY-container_offset.top};me.zoom_by(zoom,mouse_pos);return false});if(gesturesSupport){var gestureThrottle=+new Date;var originalScale,originalCenter;this.img_object.object().bind("touchstart",function(ev){originalScale=me.current_zoom;var touches=ev.originalEvent.touches,container_offset;if(touches.length==2){container_offset=me.container.offset();originalCenter={x:(touches[0].pageX+ |
|
20 | touches[1].pageX)/2-container_offset.left,y:(touches[0].pageY+touches[1].pageY)/2-container_offset.top}}else originalCenter=null}).bind("gesturechange",function(ev){var d=+new Date;if(d-gestureThrottle<50)return;gestureThrottle=d;var zoom=originalScale*ev.originalEvent.scale;me.set_zoom(zoom,originalCenter);ev.preventDefault()}).bind("gestureend",function(ev){originalCenter=null})}}this.img_object.object().click(function(e){return me._click(e)}).prependTo(this.container);this.container.bind("mousemove", |
|
21 | function(ev){me._handleMouseMove(ev)});this.loadImage(this.options.src);if(!this.options.ui_disabled)this.createui();this._mouseInit()},destroy:function(){$.Widget.prototype.destroy.call(this);this._mouseDestroy();this.img_object.object().remove();this.container.off(".iviewer");this.container.css("overflow","")},_updateContainerInfo:function(){this.options.height=this.container.height();this.options.width=this.container.width()},update:function(){this._updateContainerInfo();this.setCoords(this.img_object.x(), |
|
22 | this.img_object.y())},loadImage:function(src){this.current_zoom=this.options.zoom;var me=this;this._trigger("onStartLoad",0,src);this.container.addClass("iviewer_loading");this.img_object.load(src,function(){me._imageLoaded(src)},function(){me._trigger("onErrorLoad",0,src)})},_imageLoaded:function(src){this.container.removeClass("iviewer_loading");this.container.addClass("iviewer_cursor");if(this.options.zoom=="fit")this.fit(true);else this.set_zoom(this.options.zoom,true);this._trigger("onFinishLoad", |
|
23 | 0,src)},fit:function(skip_animation){var aspect_ratio=this.img_object.orig_width()/this.img_object.orig_height();var window_ratio=this.options.width/this.options.height;var choose_left=aspect_ratio>window_ratio;var new_zoom=0;if(choose_left)new_zoom=this.options.width/this.img_object.orig_width()*100;else new_zoom=this.options.height/this.img_object.orig_height()*100;this.set_zoom(new_zoom,skip_animation)},center:function(){this.setCoords(-Math.round((this.img_object.display_width()-this.options.width)/ |
|
24 | 2),-Math.round((this.img_object.display_height()-this.options.height)/2))},moveTo:function(x,y){var dx=x-Math.round(this.options.width/2);var dy=y-Math.round(this.options.height/2);var new_x=this.img_object.x()-dx;var new_y=this.img_object.y()-dy;this.setCoords(new_x,new_y)},getContainerOffset:function(){return jQuery.extend({},this.container.offset())},setCoords:function(x,y){if(!this.img_object.loaded())return;var coords=this._correctCoords(x,y);this.img_object.x(coords.x);this.img_object.y(coords.y)}, |
|
25 | _correctCoords:function(x,y){x=parseInt(x,10);y=parseInt(y,10);if(y>0)y=0;if(x>0)x=0;if(y+this.img_object.display_height()<this.options.height)y=this.options.height-this.img_object.display_height();if(x+this.img_object.display_width()<this.options.width)x=this.options.width-this.img_object.display_width();if(this.img_object.display_width()<=this.options.width)x=-(this.img_object.display_width()-this.options.width)/2;if(this.img_object.display_height()<=this.options.height)y=-(this.img_object.display_height()- |
|
26 | this.options.height)/2;return{x:x,y:y}},containerToImage:function(x,y){var coords={x:x-this.img_object.x(),y:y-this.img_object.y()};coords=this.img_object.toOriginalCoords(coords);return{x:util.descaleValue(coords.x,this.current_zoom),y:util.descaleValue(coords.y,this.current_zoom)}},imageToContainer:function(x,y){var coords={x:util.scaleValue(x,this.current_zoom),y:util.scaleValue(y,this.current_zoom)};return this.img_object.toRealCoords(coords)},_getMouseCoords:function(e){var containerOffset=this.container.offset(); |
|
27 | coords=this.containerToImage(e.pageX-containerOffset.left,e.pageY-containerOffset.top);return coords},set_zoom:function(new_zoom,skip_animation,zoom_center){if(this._trigger("onZoom",0,new_zoom)==false)return;if(!this.img_object.loaded())return;zoom_center=zoom_center||{x:Math.round(this.options.width/2),y:Math.round(this.options.height/2)};if(new_zoom<this.options.zoom_min)new_zoom=this.options.zoom_min;else if(new_zoom>this.options.zoom_max)new_zoom=this.options.zoom_max;if(this.current_zoom=="fit"){var old_x= |
|
28 | zoom_center.x+Math.round(this.img_object.orig_width()/2);var old_y=zoom_center.y+Math.round(this.img_object.orig_height()/2);this.current_zoom=100}else{var old_x=-this.img_object.x()+zoom_center.x;var old_y=-this.img_object.y()+zoom_center.y}var new_width=util.scaleValue(this.img_object.orig_width(),new_zoom);var new_height=util.scaleValue(this.img_object.orig_height(),new_zoom);var new_x=util.scaleValue(util.descaleValue(old_x,this.current_zoom),new_zoom);var new_y=util.scaleValue(util.descaleValue(old_y, |
|
29 | this.current_zoom),new_zoom);new_x=zoom_center.x-new_x;new_y=zoom_center.y-new_y;new_width=Math.floor(new_width);new_height=Math.floor(new_height);new_x=Math.floor(new_x);new_y=Math.floor(new_y);this.img_object.display_width(new_width);this.img_object.display_height(new_height);var coords=this._correctCoords(new_x,new_y),self=this;this.img_object.setImageProps(new_width,new_height,coords.x,coords.y,skip_animation,function(){self._trigger("onAfterZoom",0,new_zoom)});this.current_zoom=new_zoom;this.update_status()}, |
|
30 | zoom_by:function(delta,zoom_center){var closest_rate=this.find_closest_zoom_rate(this.current_zoom);var next_rate=closest_rate+delta;var next_zoom=this.options.zoom_base*Math.pow(this.options.zoom_delta,next_rate);if(delta>0&&next_zoom<this.current_zoom)next_zoom*=this.options.zoom_delta;if(delta<0&&next_zoom>this.current_zoom)next_zoom/=this.options.zoom_delta;this.set_zoom(next_zoom,undefined,zoom_center)},angle:function(deg,abs){if(arguments.length===0)return this.img_object.angle();if(deg<-270|| |
|
31 | deg>270||deg%90!==0)return;if(!abs)deg+=this.img_object.angle();if(deg<0)deg+=360;if(deg>=360)deg-=360;if(deg===this.img_object.angle())return;this.img_object.angle(deg);this.center();this._trigger("angle",0,{angle:this.img_object.angle()})},find_closest_zoom_rate:function(value){if(value==this.options.zoom_base)return 0;function div(val1,val2){return val1/val2}function mul(val1,val2){return val1*val2}var func=value>this.options.zoom_base?mul:div;var sgn=value>this.options.zoom_base?1:-1;var mltplr= |
|
32 | this.options.zoom_delta;var rate=1;while(Math.abs(func(this.options.zoom_base,Math.pow(mltplr,rate))-value)>Math.abs(func(this.options.zoom_base,Math.pow(mltplr,rate+1))-value))rate++;return sgn*rate},update_status:function(){if(!this.options.ui_disabled){var percent=Math.round(100*this.img_object.display_height()/this.img_object.orig_height());if(percent)this.zoom_object.html(percent+"%")}},info:function(param,withoutRotation){if(!param)return;switch(param){case "orig_width":case "orig_height":if(withoutRotation)return this.img_object.angle()% |
|
33 | 180===0?this.img_object[param]():param==="orig_width"?this.img_object.orig_height():this.img_object.orig_width();else return this.img_object[param]();case "display_width":case "display_height":case "angle":return this.img_object[param]();case "zoom":return this.current_zoom;case "src":return this.img_object.object().attr("src");case "coords":return{x:this.img_object.x(),y:this.img_object.y()}}},_mouseStart:function(e){$.ui.mouse.prototype._mouseStart.call(this,e);if(this._trigger("onStartDrag",0, |
|
34 | this._getMouseCoords(e))===false)return false;this.container.addClass("iviewer_drag_cursor");this._dragInitialized=!(e.originalEvent.changedTouches&&e.originalEvent.changedTouches.length==1);this.dx=e.pageX-this.img_object.x();this.dy=e.pageY-this.img_object.y();return true},_mouseCapture:function(e){return true},_handleMouseMove:function(e){this._trigger("onMouseMove",e,this._getMouseCoords(e))},_mouseDrag:function(e){$.ui.mouse.prototype._mouseDrag.call(this,e);if(!this._dragInitialized){this.dx= |
|
35 | e.pageX-this.img_object.x();this.dy=e.pageY-this.img_object.y();this._dragInitialized=true}var ltop=e.pageY-this.dy;var lleft=e.pageX-this.dx;this.setCoords(lleft,ltop);this._trigger("onDrag",e,this._getMouseCoords(e));return false},_mouseStop:function(e){$.ui.mouse.prototype._mouseStop.call(this,e);this.container.removeClass("iviewer_drag_cursor");this._trigger("onStopDrag",0,this._getMouseCoords(e))},_click:function(e){this._trigger("onClick",0,this._getMouseCoords(e))},createui:function(){var me= |
|
36 | this;$("<div>",{"class":"iviewer_zoom_in iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.zoom_by(1);return false}).appendTo(this.container);$("<div>",{"class":"iviewer_zoom_out iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.zoom_by(-1);return false}).appendTo(this.container);$("<div>",{"class":"iviewer_zoom_zero iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.set_zoom(100);return false}).appendTo(this.container);$("<div>", |
|
37 | {"class":"iviewer_zoom_fit iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.fit(this);return false}).appendTo(this.container);this.zoom_object=$("<div>").addClass("iviewer_zoom_status iviewer_common").appendTo(this.container);$("<div>",{"class":"iviewer_rotate_left iviewer_common iviewer_button"}).bind("mousedown touchstart",function(){me.angle(-90);return false}).appendTo(this.container);$("<div>",{"class":"iviewer_rotate_right iviewer_common iviewer_button"}).bind("mousedown touchstart", |
|
38 | function(){me.angle(90);return false}).appendTo(this.container);this.update_status()}});$.ui.iviewer.ImageObject=function(do_anim){this._img=$("<img>").css({position:"absolute",top:"0px",left:"0px"});this._loaded=false;this._swapDimensions=false;this._do_anim=do_anim||false;this.x(0,true);this.y(0,true);this.angle(0)};(function(){this._reset=function(w,h){this._angle=0;this._swapDimensions=false;this.x(0);this.y(0);this.orig_width(w);this.orig_height(h);this.display_width(w);this.display_height(h)}; |
|
39 | this.loaded=function(){return this._loaded};this.load=function(src,loaded,error){var self=this;loaded=loaded||jQuery.noop;this._loaded=false;var img=new Image;img.onload=function(){self._loaded=true;self._reset(this.width,this.height);self._img.removeAttr("width").removeAttr("height").removeAttr("style").css({position:"absolute",top:"0px",left:"0px",maxWidth:"none"});self._img[0].src=src;loaded()};img.onerror=error;setTimeout(function(){img.src=src},0);this.angle(0)};this._dimension=function(prefix, |
|
40 | name){var horiz="_"+prefix+"_"+name,vert="_"+prefix+"_"+(name==="height"?"width":"height");return setter(function(val){this[this._swapDimensions?horiz:vert]=val},function(){return this[this._swapDimensions?horiz:vert]})};this.display_width=this._dimension("display","width"),this.display_height=this._dimension("display","height"),this.display_diff=function(){return Math.floor(this.display_width()-this.display_height())};this.orig_width=this._dimension("orig","width"),this.orig_height=this._dimension("orig", |
|
41 | "height"),this.x=setter(function(val,skipCss){this._x=val;if(!skipCss){this._finishAnimation();this._img.css("left",this._x+(this._swapDimensions?this.display_diff()/2:0)+"px")}},function(){return this._x});this.y=setter(function(val,skipCss){this._y=val;if(!skipCss){this._finishAnimation();this._img.css("top",this._y-(this._swapDimensions?this.display_diff()/2:0)+"px")}},function(){return this._y});this.angle=setter(function(deg){var prevSwap=this._swapDimensions;this._angle=deg;this._swapDimensions= |
|
42 | deg%180!==0;if(prevSwap!==this._swapDimensions){var verticalMod=this._swapDimensions?-1:1;this.x(this.x()-verticalMod*this.display_diff()/2,true);this.y(this.y()+verticalMod*this.display_diff()/2,true)}var cssVal="rotate("+deg+"deg)",img=this._img;jQuery.each(["","-webkit-","-moz-","-o-","-ms-"],function(i,prefix){img.css(prefix+"transform",cssVal)});if(useIeTransforms){jQuery.each(["-ms-",""],function(i,prefix){img.css(prefix+"filter",ieTransforms[deg].filter)});img.css({marginLeft:ieTransforms[deg].marginLeft* |
|
43 | this.display_diff()/2,marginTop:ieTransforms[deg].marginTop*this.display_diff()/2})}},function(){return this._angle});this.toOriginalCoords=function(point){switch(this.angle()){case 0:return{x:point.x,y:point.y};case 90:return{x:point.y,y:this.display_width()-point.x};case 180:return{x:this.display_width()-point.x,y:this.display_height()-point.y};case 270:return{x:this.display_height()-point.y,y:point.x}}};this.toRealCoords=function(point){switch(this.angle()){case 0:return{x:this.x()+point.x,y:this.y()+ |
|
44 | point.y};case 90:return{x:this.x()+this.display_width()-point.y,y:this.y()+point.x};case 180:return{x:this.x()+this.display_width()-point.x,y:this.y()+this.display_height()-point.y};case 270:return{x:this.x()+point.y,y:this.y()+this.display_height()-point.x}}};this.object=setter(jQuery.noop,function(){return this._img});this.setImageProps=function(disp_w,disp_h,x,y,skip_animation,complete){complete=complete||jQuery.noop;this.display_width(disp_w);this.display_height(disp_h);this.x(x,true);this.y(y, |
|
45 | true);var w=this._swapDimensions?disp_h:disp_w;var h=this._swapDimensions?disp_w:disp_h;var params={width:w,height:h,top:y-(this._swapDimensions?this.display_diff()/2:0)+"px",left:x+(this._swapDimensions?this.display_diff()/2:0)+"px"};if(useIeTransforms)jQuery.extend(params,{marginLeft:ieTransforms[this.angle()].marginLeft*this.display_diff()/2,marginTop:ieTransforms[this.angle()].marginTop*this.display_diff()/2});var swapDims=this._swapDimensions,img=this._img;if(useIeTransforms&&swapDims){var ieh= |
|
46 | this._img.width(),iew=this._img.height(),iedh=params.height-ieh;iedw=params.width-iew;delete params.width;delete params.height}if(this._do_anim&&!skip_animation)this._img.stop(true).animate(params,{duration:200,complete:complete,step:function(now,fx){if(useIeTransforms&&swapDims&&fx.prop==="top"){var percent=(now-fx.start)/(fx.end-fx.start);img.height(ieh+iedh*percent);img.width(iew+iedw*percent);img.css("top",now)}}});else{this._img.css(params);setTimeout(complete,0)}};this._finishAnimation=function(){this._img.stop(true, |
|
47 | true)}}).apply($.ui.iviewer.ImageObject.prototype);var util={scaleValue:function(value,toZoom){return value*toZoom/100},descaleValue:function(value,fromZoom){return value*100/fromZoom}}})(jQuery,undefined); |
|
48 |