e-smart-zoom-jquery.min.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. /*
  2. * jQuery zoom plugin
  3. * Demo and documentation:
  4. * http://e-smartdev.com/#!jsPluginList/panAndZoomJQuery
  5. *
  6. * Copyright (c) 2012 Damien Corzani
  7. * http://e-smartdev.com/
  8. *
  9. * Dual licensed under the MIT and GPL licenses.
  10. * http://en.wikipedia.org/wiki/MIT_License
  11. * http://en.wikipedia.org/wiki/GNU_General_Public_License
  12. */
  13. (function( $ ){
  14. $.fn.smartZoom = function(method) {
  15. // define global vars
  16. var targetElement = this; // the element target of the plugin
  17. /**
  18. * ESmartZoomEvent Class
  19. * define const use to dispatch zoom events
  20. */
  21. function ESmartZoomEvent(type){}
  22. ESmartZoomEvent.ZOOM = "SmartZoom_ZOOM";
  23. ESmartZoomEvent.PAN = "SmartZoom_PAN";
  24. ESmartZoomEvent.START = "START";
  25. ESmartZoomEvent.END = "END";
  26. ESmartZoomEvent.DESTROYED = "SmartZoom_DESTROYED";
  27. /**
  28. * define public methods that user user could call
  29. */
  30. var publicMethods = {
  31. /**
  32. * initialize zoom component
  33. * @param {Object} options = {'top': '0' zoom target container top position in pixel
  34. * 'left': '0' zoom target container left position in pixel
  35. * 'width' : '100%' zoom target container width in pixel or in percent
  36. * 'height' : '100%' zoom target container height in pixel or in percent
  37. * 'easing' : 'smartZoomEasing' jquery easing function used when the browser doesn't support css transitions
  38. * 'initCallback' : null a callback function to call after plugin initilization
  39. * 'maxScale' : 3 the max scale that will be applied on the zoom target
  40. * 'dblClickMaxScale' : 1.8 the max scale that will be applied on the zoom target on double click
  41. * 'mouseEnabled' : true enable plugin mouse interaction
  42. * 'scrollEnabled' : true enable plugin mouse wheel behviour
  43. * 'dblClickEnabled' : true enable plugin mouse doubleClick behviour
  44. * 'mouseMoveEnabled' : true enable plugin target drag behviour
  45. * 'moveCursorEnabled' : true show moveCursor for drag
  46. * 'touchEnabled' : true enable plugin touch interaction
  47. * 'dblTapEnabled' : true enable plugin double tap behaviour
  48. * 'zoomOnSimpleClick': false enable zoom on simple click (if set to true dbl lick is disabled)
  49. * 'pinchEnabled' : true enable zoom when user pinch on target
  50. * 'touchMoveEnabled' : true enable target move via touch
  51. * 'containerBackground' : '#FFFFFF' zoom target container background color (if containerClass is not set)
  52. * 'containerClass' : '' class to apply to zoom target container if you whant to change background or borders (don't change size or position via the class)
  53. * }
  54. */
  55. init : function(options) {
  56. // is smartZoomData exists on the targetElement, we have already initialize it
  57. if(targetElement.data('smartZoomData'))
  58. return;
  59. // Create some defaults, extending them with any options that were provided
  60. var settings = $.extend( {
  61. 'top' : "0",
  62. 'left' : "0",
  63. 'width' : "100%",
  64. 'height' : "100%",
  65. 'easing' : "smartZoomEasing",
  66. 'initCallback' : null,
  67. 'maxScale' : 3,
  68. 'dblClickMaxScale' : 1.8,
  69. 'mouseEnabled' : true,
  70. 'scrollEnabled' : true,
  71. 'dblClickEnabled' : true,
  72. 'mouseMoveEnabled' : true,
  73. 'moveCursorEnabled' : true,
  74. 'adjustOnResize' : true,
  75. 'touchEnabled' : true,
  76. 'dblTapEnabled' : true,
  77. 'zoomOnSimpleClick': false,
  78. 'pinchEnabled' : true,
  79. 'touchMoveEnabled' : true,
  80. 'containerBackground' : "#FFFFFF",
  81. 'containerClass' : ""
  82. }, options);
  83. var targetElementInitialStyles = targetElement.attr('style'); // save target element initial styles
  84. // create the container that will contain the zoom target
  85. var zoomContainerId = "smartZoomContainer"+new Date().getTime();
  86. var containerDiv = $('<div id="'+zoomContainerId+'" class="'+settings.containerClass+'"></div>');
  87. targetElement.before(containerDiv);
  88. targetElement.remove();
  89. //containerDiv = $('#'+zoomContainerId);
  90. containerDiv.css({'overflow':'hidden'});
  91. if(settings.containerClass == "")
  92. containerDiv.css({'background-color':settings.containerBackground});
  93. containerDiv.append(targetElement);
  94. // create touchInfos object that will be use to manage zoom with touch events
  95. var touchInfos = new Object();
  96. touchInfos.lastTouchEndTime = 0; // this var is use to know if user had doubleTap or not
  97. touchInfos.lastTouchPositionArr = null; // list of touch point used for pinch
  98. touchInfos.touchMove = false; // is the user is in move mode?
  99. touchInfos.touchPinch = false; // or is the user is in pinch mode?
  100. // smartZoomData is used to saved every plugin vars
  101. targetElement.data('smartZoomData', {
  102. settings : settings, // given settings
  103. containerDiv : containerDiv, // target container
  104. originalSize: {width:targetElement.width(), height:targetElement.height()}, // plugin target size at the beginning
  105. originalPosition: targetElement.offset(), // plugin target position at the beginning
  106. transitionObject:getBrowserTransitionObject(), // use to know if the browser is compatible with css transition
  107. touch:touchInfos, // save touchInfos
  108. mouseWheelDeltaFactor:0.15, // enable to slow down the zoom via mouse wheel
  109. currentWheelDelta:0, // the current mouse wheel delta used to calculate scale to apply
  110. adjustedPosInfos:null, // use to save the adjust information in "adjustToContainer" method (so we can access the normal target size in plugin when we whant)
  111. moveCurrentPosition:null, // save the current mouse/touch position use in "moveOnMotion" method
  112. moveLastPosition:null, // save the last mouse/touch position use in "moveOnMotion" method
  113. mouseMoveForPan:false, // use to know if the user pan or not
  114. currentActionType:'', // use to save the current user action type (equal to 'ZOOM' or 'PAN')
  115. initialStyles:targetElementInitialStyles, // use in destroy method to reset initial styles
  116. currentActionStep:'' // equal to 'START' or 'END'
  117. });
  118. // adjust the contain and target size into the page
  119. adjustToContainer();
  120. // listening mouse and touch events
  121. if(settings.touchEnabled == true)
  122. targetElement.bind('touchstart.smartZoom', touchStartHandler);
  123. if(settings.mouseEnabled == true){
  124. if(settings.mouseMoveEnabled == true)
  125. targetElement.bind('mousedown.smartZoom', mouseDownHandler);
  126. if(settings.scrollEnabled == true){
  127. containerDiv.bind('mousewheel.smartZoom', mouseWheelHandler);
  128. containerDiv.bind( 'mousewheel.smartZoom DOMMouseScroll.smartZoom', containerMouseWheelHander);
  129. }
  130. if(settings.dblClickEnabled == true && settings.zoomOnSimpleClick == false)
  131. containerDiv.bind('dblclick.smartZoom', mouseDblClickHandler);
  132. }
  133. targetElement.context.ondragstart = function () { return false; }; // allow to remove browser default drag behaviour
  134. if(settings.adjustOnResize == true)
  135. $(window).bind('resize.smartZoom', windowResizeEventHandler); // call "adjustToContainer" on resize
  136. if(settings.initCallback != null) // call callback function after plugin initialization
  137. settings.initCallback.apply(this, targetElement);
  138. },
  139. /**
  140. * zoom function used into the plugin and accessible via direct call (ex : $('#zoomImage').smartZoom('zoom', 0.2);)
  141. * @param {Number} scaleToAdd : the scale to add to the plugin target current scale (often < 1)
  142. * @param {Point} globalRequestedPosition {'x': global requested position in x
  143. * 'y': global requested position in y
  144. * } if this parameter is missing the zoom will target the object center
  145. * @param {Number} duration : zoom effect duration 700ms by default
  146. */
  147. zoom : function(scaleToAdd, globalRequestedPosition, duration){
  148. var smartData = targetElement.data('smartZoomData');
  149. var globaRequestedX;
  150. var globaRequestedY;
  151. if(!globalRequestedPosition){ // use target center if globalRequestedPosition is not set
  152. var containerRect = getRect(smartData.containerDiv);
  153. globaRequestedX = containerRect.x + containerRect.width/2;
  154. globaRequestedY = containerRect.y + containerRect.height/2;
  155. }else{
  156. globaRequestedX = globalRequestedPosition.x;
  157. globaRequestedY = globalRequestedPosition.y;
  158. }
  159. // stop previous effect before make calculation
  160. stopAnim(ESmartZoomEvent.ZOOM);
  161. var targetRect = getTargetRect(true); // the target rectangle in global position
  162. var originalSize = smartData.originalSize;
  163. var newScale = (targetRect.width / originalSize.width) + scaleToAdd; // calculate new scale to apply
  164. // manage scale min, max
  165. newScale = Math.max(smartData.adjustedPosInfos.scale, newScale); // the scale couldn't be lowest than the initial scale
  166. newScale = Math.min(smartData.settings.maxScale, newScale); // the scale couldn't be highest than the max setted by user
  167. var newWidth = originalSize.width * newScale; // new size to apply according to new scale
  168. var newHeight = originalSize.height * newScale;
  169. var positionGlobalXDiff = globaRequestedX - targetRect.x; // the position difference between with the target position
  170. var positionGlobalYDiff = globaRequestedY - targetRect.y;
  171. var sizeRatio = newWidth / targetRect.width;
  172. var newGlobalX = targetRect.x - ((positionGlobalXDiff * sizeRatio) - positionGlobalXDiff); // apply the ratio to positionGlobalDiff and then find the final target point
  173. var newGlobalY = targetRect.y - ((positionGlobalYDiff * sizeRatio) - positionGlobalYDiff);
  174. var validPosition = getValidTargetElementPosition(newGlobalX, newGlobalY, newWidth, newHeight); // return a valid position from the calculated position
  175. if(duration == null) // default effect duration is 700ms
  176. duration = 700;
  177. dispatchSmartZoomEvent(ESmartZoomEvent.ZOOM, ESmartZoomEvent.START, false);
  178. animate(targetElement, validPosition.x, validPosition.y, newWidth, newHeight, duration, function(){ // set the new position and size via the animation function
  179. smartData.currentWheelDelta = 0; // reset the weelDelta when zoom end
  180. updateMouseMoveCursor(); // remove "move" cursor when zoom end
  181. dispatchSmartZoomEvent(ESmartZoomEvent.ZOOM, ESmartZoomEvent.END, false);
  182. });
  183. },
  184. /**
  185. * pan function accessible via direct call (ex : $('#zoomImage').smartZoom('pan', 5, 0);)
  186. * @param {Number} xToAdd : a number to add to the object current position in X
  187. * @param {Point} yToAdd : a number to add to the object current position in Y
  188. * @param {Number} duration : move effect duration 700ms by default
  189. */
  190. pan : function(xToAdd, yToAdd, duration){
  191. if(xToAdd == null || yToAdd == null) // check params
  192. return;
  193. if(duration == null) // default pan duration is 700ms
  194. duration = 700;
  195. var currentPosition = targetElement.offset();
  196. var targetRect = getTargetRect();
  197. var validPosition = getValidTargetElementPosition(currentPosition.left+xToAdd, currentPosition.top+yToAdd, targetRect.width, targetRect.height); // add the given numbers to the current coordinates and valid the result
  198. if(validPosition.x != currentPosition.left || validPosition.y != currentPosition.top){
  199. // stop previous effect before make calculation
  200. stopAnim(ESmartZoomEvent.PAN);
  201. dispatchSmartZoomEvent(ESmartZoomEvent.PAN, ESmartZoomEvent.START, false);
  202. animate(targetElement, validPosition.x, validPosition.y, targetRect.width, targetRect.height, duration, function(){ // set the new position and size via the animation function
  203. dispatchSmartZoomEvent(ESmartZoomEvent.PAN, ESmartZoomEvent.END, false);
  204. });
  205. }
  206. },
  207. /**
  208. * destroy function accessible via direct call (ex : $('#zoomImage').smartZoom('destroy');)
  209. * use this function to clean and remove smartZoom plugin
  210. */
  211. destroy : function() {
  212. var smartData = targetElement.data('smartZoomData');
  213. if(!smartData)
  214. return;
  215. stopAnim(); // stop current animation
  216. var containerDiv = smartData.containerDiv;
  217. // remove all listenerns
  218. targetElement.unbind('mousedown.smartZoom');
  219. targetElement.unbind('touchstart.smartZoom');
  220. containerDiv.unbind('mousewheel.smartZoom');
  221. containerDiv.unbind('dblclick.smartZoom');
  222. containerDiv.unbind( 'mousewheel.smartZoom DOMMouseScroll.smartZoom');
  223. $(window).unbind('resize.smartZoom');
  224. $(targetElement.context).unbind('mousemove.smartZoom');
  225. $(targetElement.context).unbind('mouseup.smartZoom');
  226. $(targetElement.context).unbind('touchmove.smartZoom');
  227. $(targetElement.context).unbind('touchend.smartZoom');
  228. targetElement.css({"cursor":"default"}); // reset default cursor
  229. containerDiv.before(targetElement); // move target element to original container
  230. animate(targetElement, smartData.originalPosition.left, smartData.originalPosition.top, smartData.originalSize.width, smartData.originalSize.height, 5); // reset initial position
  231. targetElement.removeData('smartZoomData');// clean saved data
  232. containerDiv.remove(); // remove zoom container
  233. targetElement.attr('style', smartData.initialStyles); // reset initial styles
  234. targetElement.trigger(ESmartZoomEvent.DESTROYED); // dispatch event after plugin destruction
  235. },
  236. /**
  237. * call this funcion to know if the plugin is used
  238. */
  239. isPluginActive : function(){
  240. return targetElement.data('smartZoomData') != undefined;
  241. }
  242. };
  243. if (publicMethods[method] ) { // if the parameter is an existing method, then we call this method
  244. return publicMethods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
  245. } else if (typeof method === 'object' || ! method ) { // else if it's an object we initilize the plugin
  246. if(targetElement[0].tagName.toLowerCase() == "img" && !targetElement[0].complete){ // if the target is an image when wait for image loading before initialization
  247. targetElement.bind('load.smartZoom',{arguments:arguments[0]}, imgLoadedHandler);
  248. }else{
  249. publicMethods.init.apply( targetElement, [arguments[0]]);
  250. }
  251. } else {
  252. $.error( 'Method ' + method + ' does not exist on e-smartzoom jquery plugin' );
  253. }
  254. /**
  255. * call zoom function on mouse wheel event
  256. * @param {Object} e : mouse event
  257. * @param {Object} delta : wheel direction 1 or -1
  258. */
  259. function mouseWheelHandler(e, delta){
  260. var smartData = targetElement.data('smartZoomData');
  261. if(smartData.currentWheelDelta*delta < 0) // if current and delta have != sign we set 0 to go to other direction
  262. smartData.currentWheelDelta = 0;
  263. smartData.currentWheelDelta += delta; // increment delta zoom faster when the user use wheel again
  264. publicMethods.zoom(smartData.mouseWheelDeltaFactor*smartData.currentWheelDelta, {"x":e.pageX, "y":e.pageY}); // 0.15
  265. }
  266. /**
  267. * prevent page scroll when scrolling on zoomableContainer
  268. * @param {Object} e : mouse event
  269. */
  270. function containerMouseWheelHander(e){
  271. e.preventDefault();
  272. }
  273. /**
  274. * update mouse cursor (move or default) if the zoom target is draggable
  275. */
  276. function updateMouseMoveCursor(){
  277. var smartData = targetElement.data('smartZoomData');
  278. if(smartData.settings.mouseMoveEnabled != true || smartData.settings.moveCursorEnabled != true)
  279. return;
  280. var targetRect = getTargetRect();
  281. var currentScale = (targetRect.width / smartData.originalSize.width);
  282. if(parseInt(currentScale*100)>parseInt(smartData.adjustedPosInfos.scale*100)) // multiply by 100 to resolve precision problem
  283. targetElement.css({"cursor":"move"});
  284. else
  285. targetElement.css({"cursor":"default"});
  286. }
  287. /**
  288. * 鼠标双击
  289. * @param {Object} e : mouse event
  290. */
  291. function mouseDblClickHandler(e){
  292. zoomOnDblClick(e.pageX, e.pageY);
  293. }
  294. /**
  295. * save mouse position on mouse down (positions will be used in moveOnMotion function)
  296. * @param {Object} e : mouse event
  297. */
  298. function mouseDownHandler(e){
  299. e.preventDefault(); // prevent default browser drag
  300. $(targetElement.context).on('mousemove.smartZoom', mouseMoveHandler); // add mouse move and mouseup listeners to enable drag
  301. $(targetElement.context).bind('mouseup.smartZoom', mouseUpHandler);
  302. var smartData = targetElement.data('smartZoomData'); // save mouse position on mouse down
  303. smartData.moveCurrentPosition = new Point(e.pageX, e.pageY);
  304. smartData.moveLastPosition = new Point(e.pageX, e.pageY);
  305. }
  306. /**
  307. * call "moveOnMotion" when the mouse move after mouseDown
  308. * @param {Object} e : mouse event
  309. */
  310. function mouseMoveHandler(e){
  311. var smartData = targetElement.data('smartZoomData');
  312. if(smartData.mouseMoveForPan || (!smartData.mouseMoveForPan && smartData.moveCurrentPosition.x != e.pageX && smartData.moveCurrentPosition.y != e.pageY)){
  313. smartData.mouseMoveForPan = true;
  314. moveOnMotion(e.pageX, e.pageY, 0, false);
  315. }
  316. }
  317. /**
  318. * stop the drag on mouseup
  319. * @param {Object} e : mouse event
  320. */
  321. function mouseUpHandler(e){
  322. var smartData = targetElement.data('smartZoomData');
  323. if(smartData.mouseMoveForPan){
  324. smartData.mouseMoveForPan = false;
  325. if(smartData.moveLastPosition.distance(smartData.moveCurrentPosition) > 4){ // smooth the drag end when user move the mouse fast
  326. var interpolateP = smartData.moveLastPosition.interpolate(smartData.moveCurrentPosition, -4);
  327. moveOnMotion(interpolateP.x, interpolateP.y, 500, true);
  328. }else{
  329. moveOnMotion(smartData.moveLastPosition.x, smartData.moveLastPosition.y, 0, true);
  330. }
  331. }else if(smartData.settings.zoomOnSimpleClick){
  332. zoomOnDblClick(e.pageX, e.pageY);
  333. }
  334. $(targetElement.context).unbind('mousemove.smartZoom'); // remove listeners when drag is done
  335. $(targetElement.context).unbind('mouseup.smartZoom');
  336. }
  337. /**
  338. * save touch position and init vars on touch start (information will be use in touchMoveHandler function and touch end)
  339. * @param {Object} e : touch event
  340. */
  341. function touchStartHandler(e){
  342. e.preventDefault(); // prevent default browser drag
  343. $(targetElement.context).unbind('touchmove.smartZoom'); // unbind if we already listen touch events
  344. $(targetElement.context).unbind('touchend.smartZoom');
  345. $(targetElement.context).bind('touchmove.smartZoom', touchMoveHandler); // listen move and touch end events to manage drag on touch
  346. $(targetElement.context).bind('touchend.smartZoom', touchEndHandler);
  347. var touchList = e.originalEvent.touches; // get touch infos from event
  348. var firstTouch = touchList[0];
  349. var smartData = targetElement.data('smartZoomData');
  350. smartData.touch.touchMove = false; // will be set to true if the user start drag
  351. smartData.touch.touchPinch = false; // will be set to true if the user whant to pinch (zoom)
  352. smartData.moveCurrentPosition = new Point(firstTouch.pageX, firstTouch.pageY); // save the finger position on screen on touch start
  353. smartData.moveLastPosition = new Point(firstTouch.pageX, firstTouch.pageY);
  354. smartData.touch.lastTouchPositionArr = new Array(); // save touch position off all fingers to manage pinch
  355. var currentTouch;
  356. var nbTouch = touchList.length;
  357. for(var i = 0;i<nbTouch;++i){
  358. currentTouch = touchList[i];
  359. smartData.touch.lastTouchPositionArr.push(new Point(currentTouch.pageX, currentTouch.pageY));
  360. }
  361. }
  362. /**
  363. * manage pinch or drag when touch move
  364. * @param {Object} e : touch event
  365. */
  366. function touchMoveHandler(e){
  367. e.preventDefault(); // prevent default browser behaviour
  368. var smartData = targetElement.data('smartZoomData');
  369. var touchListEv = e.originalEvent.touches; // get touch information on event
  370. var nbTouch = touchListEv.length;
  371. var currentFirstTouchEv = touchListEv[0];
  372. if(nbTouch == 1 && !smartData.touch.touchPinch && smartData.settings.touchMoveEnabled == true){ // if the user use only one finger and touchPinch==false => we manage drag
  373. if(!smartData.touch.touchMove){
  374. var downLastPoint = smartData.touch.lastTouchPositionArr[0];
  375. if(downLastPoint.distance(new Point(currentFirstTouchEv.pageX, currentFirstTouchEv.pageY))<3){ // check if user really had moved
  376. return;
  377. }
  378. else
  379. smartData.touch.touchMove = true;
  380. }
  381. moveOnMotion(currentFirstTouchEv.pageX, currentFirstTouchEv.pageY, 0, false);
  382. }else if(nbTouch == 2 && !smartData.touch.touchMove && smartData.settings.pinchEnabled == true){ // if the user use two fingers and touchMove==false => we manage pinch
  383. smartData.touch.touchPinch = true;
  384. var currentSecondTouchEv = touchListEv[1]; // get current fingers position and last fingers positions
  385. var lastP1 = smartData.touch.lastTouchPositionArr[0];
  386. var lastP2 = smartData.touch.lastTouchPositionArr[1];
  387. var currentP1 = new Point(currentFirstTouchEv.pageX, currentFirstTouchEv.pageY);
  388. var currentP2 = new Point(currentSecondTouchEv.pageX, currentSecondTouchEv.pageY);
  389. var currentP1P2Distance = currentP1.distance(currentP2); // distance between current two fingers positions
  390. var lastP1P2Distance = lastP1.distance(lastP2); // distance between the last two fingers positions registered
  391. var currentDistance = currentP1P2Distance - lastP1P2Distance; // distance between last move and current move
  392. if(Math.abs(currentDistance)<3) // finger pixel error (jump between in and out mode sometimes if i don't do this check)
  393. return;
  394. var middle = new Point((currentP1.x + currentP2.x) /2, (currentP1.y + currentP2.y) /2); // get the point between the two fingers
  395. var targetRect = getTargetRect(); // the current plugin target size
  396. var originSize = smartData.originalSize; // original plugin target size
  397. var currentScale = (targetRect.width / originSize.width); // current scale base on original size
  398. var newZoomValueToAdd = currentP1P2Distance/lastP1P2Distance; // scale between current distance and last fingers distance
  399. var newZoomScale = ((targetRect.width * newZoomValueToAdd) / originSize.width); // the new zoom scale
  400. publicMethods.zoom(newZoomScale - currentScale, middle, 0); // call zoom fonction with the scale to add (newZoomScale - currentScale)
  401. smartData.touch.lastTouchPositionArr[0] = currentP1; //update last touch position points for next function iteration
  402. smartData.touch.lastTouchPositionArr[1] = currentP2;
  403. }
  404. }
  405. /**
  406. * manage touch move end or double tap at touch end
  407. * @param {Object} e : touch event
  408. */
  409. function touchEndHandler(e){
  410. e.preventDefault(); // prevent default browser behaviour
  411. var nbTouchAtEnd = e.originalEvent.touches.length;
  412. if(nbTouchAtEnd == 0){ // unbind listeners if the user take off all his fingers
  413. $(targetElement.context).unbind('touchmove.smartZoom');
  414. $(targetElement.context).unbind('touchend.smartZoom');
  415. }
  416. var smartData = targetElement.data('smartZoomData');
  417. if(smartData.touch.touchPinch) // nothing to do for pinch behaviour in this function
  418. return;
  419. if(smartData.touch.touchMove){ // smooth motion at end if we are in drag mode
  420. if(smartData.moveLastPosition.distance(smartData.moveCurrentPosition) > 2){ // smooth only if the user drag fast
  421. var interpolateP = smartData.moveLastPosition.interpolate(smartData.moveCurrentPosition, -4); // the end smooth motion is calculate according to last finger motion
  422. moveOnMotion(interpolateP.x, interpolateP.y, 500, true);
  423. }
  424. }else{
  425. if(smartData.settings.dblTapEnabled == true && smartData.touch.lastTouchEndTime != 0 && new Date().getTime() - smartData.touch.lastTouchEndTime < 400){ // if the user double tap (double tap if there is less than 300 ms between first and second tap)
  426. var lastStartPos = smartData.touch.lastTouchPositionArr[0];
  427. zoomOnDblClick(lastStartPos.x, lastStartPos.y);
  428. }
  429. smartData.touch.lastTouchEndTime = new Date().getTime();
  430. }
  431. }
  432. /**
  433. * manage plugin target move after mouse or finger motion
  434. * @param {Number} xPos : new x position to set
  435. * @param {Number} yPos : new y position to set
  436. * @param {Number} duration : move effect duration
  437. */
  438. function moveOnMotion(xPos, yPos, duration, motionEnd){
  439. stopAnim(ESmartZoomEvent.PAN);// stop previous effect before make calculation
  440. var smartData = targetElement.data('smartZoomData');
  441. smartData.moveLastPosition.x = smartData.moveCurrentPosition.x; // save the current position in "moveLastPosition" before moving the plugin target
  442. smartData.moveLastPosition.y = smartData.moveCurrentPosition.y; // (moveLastPosition will be use to smooth last motion in "touchEndHandler" and "mouseUpHandler")
  443. var currentPosition = targetElement.offset(); // the target current position
  444. var targetRect = getTargetRect(); // current target size
  445. var newMarginLeft = currentPosition.left + (xPos - smartData.moveCurrentPosition.x); // add current mouseX (orTouchX) position difference to the target position
  446. var newMarginTop = currentPosition.top + (yPos - smartData.moveCurrentPosition.y);
  447. var validPosition = getValidTargetElementPosition(newMarginLeft, newMarginTop, targetRect.width, targetRect.height); // check if the new position is valid
  448. dispatchSmartZoomEvent(ESmartZoomEvent.PAN, ESmartZoomEvent.START, false);
  449. console.log("--------------------------------");
  450. console.log(currentPosition);
  451. console.log(targetRect);
  452. console.log(validPosition);
  453. animate(targetElement, validPosition.x, validPosition.y, targetRect.width, targetRect.height, duration, motionEnd == true ? function(){
  454. dispatchSmartZoomEvent(ESmartZoomEvent.PAN, ESmartZoomEvent.END, false);
  455. }: null); // move to the right position
  456. smartData.moveCurrentPosition.x = xPos; // save the new position
  457. smartData.moveCurrentPosition.y = yPos;
  458. }
  459. /**
  460. * manage zoom when user double click or double tap
  461. * @param {Number} pageX : double click (or tap) x position in page
  462. * @param {Number} pageY : double click (or tap) y position in page
  463. */
  464. function zoomOnDblClick(pageX, pageY){
  465. var smartData = targetElement.data('smartZoomData');
  466. var originalSize = smartData.originalSize; // original target size
  467. var targetRect = getTargetRect(); // current target size
  468. var currentScale = (targetRect.width / originalSize.width); // the current target scale
  469. var originalScale = smartData.adjustedPosInfos.scale; // original scale
  470. var dblClickMaxScale = parseFloat(smartData.settings.dblClickMaxScale); // the doucble click or double tap max scale
  471. var scaleDiff; // if the current scale is close from "dblClickMaxScale" go to "originalScale" else go to "dblClickMaxScale"
  472. if(currentScale.toFixed(2)>dblClickMaxScale.toFixed(2) || Math.abs(dblClickMaxScale - currentScale)>Math.abs(currentScale-originalScale)){
  473. scaleDiff = dblClickMaxScale - currentScale;
  474. }else{
  475. scaleDiff = originalScale - currentScale;
  476. }
  477. publicMethods.zoom(scaleDiff, {"x":pageX, "y":pageY});
  478. }
  479. /**
  480. * stop the animation
  481. */
  482. function stopAnim(userActionType){
  483. var smartData = targetElement.data('smartZoomData');
  484. if(smartData.transitionObject){ // if css transformation is surpported
  485. if(smartData.transitionObject.cssAnimHandler) // stop the transformation end handler if it exists
  486. targetElement.off($.support.transition, smartData.transitionObject.cssAnimTimer);
  487. var originalSize = smartData.originalSize; // get the original target size
  488. var targetRect = getTargetRect(); // the target current size
  489. var cssObject = new Object(); // set the current current target size and the target scale to css transformation so it stop previous transformation
  490. cssObject[smartData.transitionObject.transition] = 'all 0s'; // apply transformation now
  491. if(smartData.transitionObject.css3dSupported){
  492. cssObject[smartData.transitionObject.transform] = 'translate3d('+targetRect.x+'px, '+targetRect.y+'px, 0) scale3d('+targetRect.width/originalSize.width+','+targetRect.height/originalSize.height+', 1)';
  493. }else{
  494. cssObject[smartData.transitionObject.transform] = 'translateX('+targetRect.x+'px) translateY('+targetRect.y+'px) scale('+targetRect.width/originalSize.width+','+targetRect.height/originalSize.height+')';
  495. }
  496. targetElement.css(cssObject);
  497. }else{
  498. targetElement.stop(); // if we use a jquery transformation, just call stop function
  499. }
  500. updateMouseMoveCursor(); // update mouse move cursor after animation stop (set the cross cursor or not)
  501. if(userActionType != null)
  502. dispatchSmartZoomEvent(userActionType, '', true);
  503. }
  504. /**
  505. * manage position validation
  506. * @param {Number} marginLeft : global x position
  507. * @param {Number} marginTop : global y position
  508. * @param {Number} width : element width
  509. * @param {Number} height : element height
  510. */
  511. function getValidTargetElementPosition(xPosition, yPosition, width, height){
  512. var smartData = targetElement.data('smartZoomData');
  513. // adjusting if the content is out of the initial content box from "adjustedPosInfos"
  514. var newMarginTop = Math.min(smartData.adjustedPosInfos.top, yPosition); // adjust in top
  515. newMarginTop += Math.max(0, (smartData.adjustedPosInfos.top + smartData.adjustedPosInfos.height) - (newMarginTop + height)) // adjust in bottom
  516. var newMarginLeft = Math.min(smartData.adjustedPosInfos.left, xPosition); // adjust in left
  517. newMarginLeft += Math.max(0, (smartData.adjustedPosInfos.left + smartData.adjustedPosInfos.width) - (newMarginLeft + width)); // adjust in right
  518. return new Point(newMarginLeft.toFixed(2), newMarginTop.toFixed(2));
  519. }
  520. /**
  521. * when the plugin target is an image we wait for image loading before initilization
  522. * @param {Object} e : load event
  523. */
  524. function imgLoadedHandler(e){
  525. targetElement.unbind('load.smartZoom');
  526. publicMethods.init.apply( targetElement, [e.data.arguments]);
  527. }
  528. /**
  529. * this function fit the plugin target to the zoom container at initialization and when the window is resized
  530. */
  531. function adjustToContainer(){
  532. var smartData = targetElement.data('smartZoomData');
  533. var containerDiv = smartData.containerDiv; // the zoom container
  534. var originalSize = smartData.originalSize; // target original size
  535. // get the zoomable container position from settings
  536. var parentOffset = containerDiv.parent().offset();
  537. var containerDivNewLeft = getContainerDivPositionFromSettings(smartData.settings.left, parentOffset.left, containerDiv.parent().width());
  538. var containerDivNewTop = getContainerDivPositionFromSettings(smartData.settings.top, parentOffset.top, containerDiv.parent().height());
  539. containerDiv.offset({left: containerDivNewLeft, top: containerDivNewTop}); // apply position find
  540. containerDiv.width(getContainerDivSizeFromSettings(smartData.settings.width, containerDiv.parent().width(), containerDivNewLeft - parentOffset.left)); // apply size found to zoomablecontainer
  541. containerDiv.height(getContainerDivSizeFromSettings(smartData.settings.height, containerDiv.parent().height(), containerDivNewTop - parentOffset.top));
  542. var containerRect = getRect(containerDiv); // get the rectangle from the new containerDiv position and size
  543. var scaleToFit = Math.min(Math.min(containerRect.width/originalSize.width, containerRect.height/originalSize.height), 1).toFixed(2); // scale to use to include the target into containerRect
  544. var newWidth = originalSize.width * scaleToFit; // we could now find the new size
  545. var newHeight = originalSize.height * scaleToFit;
  546. // store the position and size information in adjustedPosInfos object
  547. smartData.adjustedPosInfos = {"left":(containerRect.width - newWidth)/2 + parentOffset.left, "top": (containerRect.height - newHeight)/2 + parentOffset.top, "width": newWidth, "height" : newHeight, "scale":scaleToFit};
  548. stopAnim();
  549. // call animate method with 10 ms duration to apply new target position and size
  550. animate(targetElement, smartData.adjustedPosInfos.left , smartData.adjustedPosInfos.top, newWidth, newHeight, 0, function() {
  551. targetElement.css('visibility','visible'); // set target visibility to visible if developper whant to hide it before the plugin resize
  552. });
  553. updateMouseMoveCursor();
  554. }
  555. /**
  556. * animate the plugin target
  557. * @param {Object} target : the element to animate
  558. * @param {Number} globalLeft : the global x target position
  559. * @param {Number} globalTop : the global y target position
  560. * @param {Number} width : targeted width
  561. * @param {Number} width : targeted height
  562. * @param {Number} duration : effect duration
  563. * @param {Function} callback : function to call when effect end
  564. *
  565. */
  566. function animate(target, globalLeft, globalTop, width, height, duration, callback){
  567. var smartData = targetElement.data('smartZoomData');
  568. var parentOffset = smartData.containerDiv.offset();
  569. var left = globalLeft - parentOffset.left; // get the relative position from parent
  570. var top = globalTop - parentOffset.top;
  571. if (smartData.transitionObject != null) { // use css transition if supported
  572. var originalSize = smartData.originalSize;
  573. var cssObject = new Object();
  574. cssObject[smartData.transitionObject.transform+'-origin'] = '0 0';
  575. cssObject[smartData.transitionObject.transition] = 'all '+duration / 1000+'s ease-out'; // set effect duration
  576. if(smartData.transitionObject.css3dSupported) // use css 3d translate if supported
  577. cssObject[smartData.transitionObject.transform] = 'translate3d('+left+'px, '+top+'px, 0) scale3d('+width/originalSize.width+','+height/originalSize.height+', 1)';
  578. else
  579. cssObject[smartData.transitionObject.transform] = 'translateX('+left+'px) translateY('+top+'px) scale('+width/originalSize.width+','+height/originalSize.height+')';
  580. if(callback != null){
  581. smartData.transitionObject.cssAnimHandler = callback;
  582. target.one($.support.transition.end, smartData.transitionObject.cssAnimHandler);
  583. }
  584. target.css(cssObject); // apply css transformation
  585. }else{ // use JQuery animate if css transition is not supported
  586. target.animate({"margin-left": left, "margin-top": top, "width": width, "height" : height}, {duration:duration, easing:smartData.settings.easing, complete:function() {
  587. if(callback != null)
  588. callback();
  589. }});
  590. }
  591. }
  592. /**
  593. * get the plugin target rectangle on screen
  594. * @param {Boolean} globalPosition : if true return global position else return relative position
  595. * @return {Object} {'x':'x Position', 'y':'y Position', 'width':' element width', 'height': 'element height'}
  596. */
  597. function getTargetRect(globalPosition){
  598. var smartData = targetElement.data('smartZoomData');
  599. var width = targetElement.width(); // get the current object size
  600. var height = targetElement.height();
  601. var position = targetElement.offset(); // global position
  602. var x = parseInt(position.left); // save global position in vars
  603. var y = parseInt(position.top);
  604. var parentOffset = smartData.containerDiv.offset(); // get zoomable container global position
  605. if(globalPosition != true){ // set local position
  606. x = parseInt(x) - parentOffset.left;
  607. y = parseInt(y) - parentOffset.top;
  608. }
  609. if (smartData.transitionObject != null) { // if CSS3 transition is enabled
  610. var transformMatrix = targetElement.css(smartData.transitionObject.transform);
  611. if(transformMatrix && transformMatrix != "" && transformMatrix.search('matrix') != -1){ // get the target css matrix tranform
  612. var scale;
  613. var arrValues;
  614. if(transformMatrix.search('matrix3d') != -1){ // check the matrix type
  615. arrValues = transformMatrix.replace('matrix3d(','').replace(')','').split(',');
  616. scale = arrValues[0]; // get target current scale
  617. }else{
  618. arrValues = transformMatrix.replace('matrix(','').replace(')','').split(',');
  619. scale = arrValues[3];// get target current scale
  620. x = parseFloat(arrValues[4]);// get target current position
  621. y = parseFloat(arrValues[5]);
  622. if(globalPosition){ // adjust for global
  623. x = parseFloat(x) + parentOffset.left;
  624. y = parseFloat(y) + parentOffset.top;
  625. }
  626. }
  627. width = scale * width; // find the actual object size thanks to current scale
  628. height = scale * height;
  629. }
  630. }
  631. return {'x' : x,'y' : y, 'width' : width, 'height' : height};
  632. }
  633. /**
  634. * dispatch zoom or pan event
  635. * @param {Boolean} fromZoom : set to true if the function is call from zoom action
  636. *
  637. **/
  638. function dispatchSmartZoomEvent(actionType, actionStep, stop){
  639. var smartData = targetElement.data('smartZoomData');
  640. var eventTypeToDispatch = '';
  641. if(stop == true && smartData.currentActionType != actionType){ // if the action type has changed and that the function is called from "stopAnim" dispatch END event
  642. eventTypeToDispatch = smartData.currentActionType+'_'+ESmartZoomEvent.END;
  643. smartData.currentActionType = '';
  644. smartData.currentActionStep = '';
  645. }else{
  646. if(smartData.currentActionType != actionType || smartData.currentActionStep == ESmartZoomEvent.END){ // if the current action had changed (MOVE to ZOOM for exemple) and the last action step was END we whant to dispatch START
  647. smartData.currentActionType = actionType;
  648. smartData.currentActionStep = ESmartZoomEvent.START;
  649. eventTypeToDispatch = smartData.currentActionType+'_'+smartData.currentActionStep;
  650. }else if(smartData.currentActionType == actionType && actionStep == ESmartZoomEvent.END){ // dispatch END if actionstep ask is END and action type don't change
  651. smartData.currentActionStep = ESmartZoomEvent.END;
  652. eventTypeToDispatch = smartData.currentActionType+'_'+smartData.currentActionStep;
  653. smartData.currentActionType = '';
  654. smartData.currentActionStep = '';
  655. }
  656. }
  657. if(eventTypeToDispatch != ''){ // dispatch event if we have to
  658. var ev = jQuery.Event(eventTypeToDispatch);
  659. ev.targetRect = getTargetRect(true);
  660. ev.scale = ev.targetRect.width / smartData.originalSize.width;
  661. //console.log(targetElement, eventTypeToDispatch);
  662. targetElement.trigger(ev);
  663. }
  664. }
  665. /**
  666. * return an object that contains the kind of CSS3 supported transition
  667. * @return {Object} {'transition':'-webkit-transition', 'transform':'-webkit-transform', 'css3dSupported':'true'}
  668. */
  669. function getBrowserTransitionObject(){
  670. var pageBody = targetElement.context.body || targetElement.context.documentElement;
  671. var bodyStyle = pageBody.style;
  672. var transitionTestArr = ['transition', 'WebkitTransition', 'MozTransition', 'MsTransition', 'OTransition']; // all type all transitions to test
  673. var transitionArr = ['transition', '-webkit-transition', '-moz-transition', '-ms-transition', '-o-transition'];
  674. var transformArr = ['transform', '-webkit-transform', '-moz-transform', '-ms-transform', '-o-transform'];
  675. var length = transitionTestArr.length;
  676. var transformObject;
  677. for(var i=0; i<length; i++){ // for all kind of css transition we make a test
  678. if(bodyStyle[transitionTestArr[i]] != null){
  679. transformStr = transformArr[i];
  680. var div = $('<div style="position:absolute;">Translate3d Test</div>');
  681. $('body').append(div); // try a transformation on a new div each time
  682. transformObject = new Object();
  683. transformObject[transformArr[i]] = "translate3d(20px,0,0)";
  684. div.css(transformObject);
  685. css3dSupported = ((div.offset().left - $('body').offset().left) == 20); // if translate3d(20px,0,0) via transformArr[i] == 20px the transformation is valid for this browser
  686. div.empty().remove();
  687. if(css3dSupported){ // return the kind of transformation supported
  688. return {transition:transitionArr[i], transform:transformArr[i], css3dSupported:css3dSupported};
  689. }
  690. }
  691. }
  692. return null;
  693. }
  694. /**
  695. * get the plugin target rectangle on screen
  696. * @param {Object} settingsValue : a number or a string value given in plugin params
  697. * @param {Number} zoomableContainerParentValue : zoomable container parent width or height
  698. * @param {Number} divPosDiff : zoomable container parent and zoomable container position difference
  699. * @return {Number} return the zoomable container size (in pixel) from setting value (pixel or percent)
  700. */
  701. function getContainerDivSizeFromSettings(settingsValue, zoomableContainerParentValue, divPosDiff){
  702. if(settingsValue.search && settingsValue.search("%") != -1)
  703. return (zoomableContainerParentValue - divPosDiff)* (parseInt(settingsValue)/100);
  704. else
  705. return parseInt(settingsValue);
  706. }
  707. /**
  708. * get the plugin target rectangle on screen
  709. * @param {Object} settingsValue : a number or a string value given in plugin params
  710. * @param {Number} zoomableContainerParentPosValue : zoomable container parent global x or y
  711. * @param {Number} zoomableContainerParentSizeValue : zoomable container parent width or height
  712. * @return {Number} return the zoomable container position (in pixel) from setting value (pixel or percent)
  713. */
  714. function getContainerDivPositionFromSettings(settingsValue, zoomableContainerParentPosValue, zoomableContainerParentSizeValue){
  715. if(settingsValue.search && settingsValue.search("%") != -1)
  716. return zoomableContainerParentPosValue + zoomableContainerParentSizeValue * (parseInt(settingsValue)/100);
  717. else
  718. return zoomableContainerParentPosValue + parseInt(settingsValue);
  719. }
  720. /**
  721. * reinit the component when the user resize the window
  722. */
  723. function windowResizeEventHandler(){
  724. adjustToContainer();
  725. }
  726. /**
  727. * return a retangle from a JQuery object
  728. * @param {Object} jqObject : a JQuery object like $('#myObject')
  729. * @return {Object} return {'x':objectX, 'y':objectY, 'width':objectWidth, 'height':objectHeight}
  730. */
  731. function getRect(jqObject) {
  732. var offset = jqObject.offset();
  733. if(!offset)
  734. return null;
  735. var formX = offset.left;
  736. var formY = offset.top;
  737. return {'x':formX, 'y':formY, 'width':jqObject.outerWidth(), 'height':jqObject.outerHeight()};
  738. }
  739. /**
  740. * Point Class
  741. * @param {Number} x : point position on X axis
  742. * @param {Number} y : point position on Y axis
  743. */
  744. function Point(x, y) {
  745. this.x = x;
  746. this.y = y;
  747. /**
  748. * return point informations into a string
  749. * @return {String} return (x=5, y=5)
  750. */
  751. this.toString = function() {
  752. return '(x=' + this.x + ', y=' + this.y + ')';
  753. };
  754. /**
  755. * return a new point who is the interpolation of this and the given point
  756. * the new point position is calculate thanks to percentInterpolate (the distance between this and pointToInterpolate in percent)
  757. * @return {Point} return {'x':interpolateX, 'y':interpolateY}
  758. */
  759. this.interpolate = function(pointToInterpolate, percentInterpolate) {
  760. var x = percentInterpolate * this.x + (1 - percentInterpolate) * pointToInterpolate.x;
  761. var y = percentInterpolate * this.y + (1 - percentInterpolate) * pointToInterpolate.y;
  762. return new Point(x, y);
  763. };
  764. /**
  765. * return the distance between "this" point and the given point
  766. * @return {Number} distance between this and "point"
  767. */
  768. this.distance = function(point) {
  769. return Math.sqrt(Math.pow((point.y - this.y) ,2) + Math.pow((point.x - this.x),2));
  770. }
  771. }
  772. };
  773. })( jQuery );
  774. /*
  775. * add smartZoomEasing and smartZoomOutQuad to jQuery easing function
  776. */
  777. (function($){
  778. $.extend($.easing,
  779. {
  780. smartZoomEasing: function (x, t, b, c, d) {
  781. return $.easing['smartZoomOutQuad'](x, t, b, c, d);
  782. },
  783. smartZoomOutQuad: function (x, t, b, c, d) {
  784. return -c *(t/=d)*(t-2) + b;
  785. }
  786. });
  787. })(jQuery);
  788. /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
  789. * Licensed under the MIT License (LICENSE.txt).
  790. *
  791. * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
  792. * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
  793. * Thanks to: Seamus Leahy for adding deltaX and deltaY
  794. *
  795. * Version: 3.0.6
  796. *
  797. * Requires: 1.2.2+
  798. */
  799. (function(e){function r(t){var n=t||window.event,r=[].slice.call(arguments,1),i=0,s=true,o=0,u=0;t=e.event.fix(n);t.type="mousewheel";if(n.wheelDelta){i=n.wheelDelta/120}if(n.detail){i=-n.detail/3}u=i;if(n.axis!==undefined&&n.axis===n.HORIZONTAL_AXIS){u=0;o=-1*i}if(n.wheelDeltaY!==undefined){u=n.wheelDeltaY/120}if(n.wheelDeltaX!==undefined){o=-1*n.wheelDeltaX/120}r.unshift(t,i,o,u);return(e.event.dispatch||e.event.handle).apply(this,r)}var t=["DOMMouseScroll","mousewheel"];if(e.event.fixHooks){for(var n=t.length;n;){e.event.fixHooks[t[--n]]=e.event.mouseHooks}}e.event.special.mousewheel={setup:function(){if(this.addEventListener){for(var e=t.length;e;){this.addEventListener(t[--e],r,false)}}else{this.onmousewheel=r}},teardown:function(){if(this.removeEventListener){for(var e=t.length;e;){this.removeEventListener(t[--e],r,false)}}else{this.onmousewheel=null}}};e.fn.extend({mousewheel:function(e){return e?this.bind("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.unbind("mousewheel",e)}})})(jQuery)
  800. // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
  801. // ============================================================
  802. function transitionEnd() {
  803. var el = document.createElement('bootstrap');
  804. var transEndEventNames = {
  805. 'WebkitTransition' : 'webkitTransitionEnd',
  806. 'MozTransition' : 'transitionend',
  807. 'OTransition' : 'oTransitionEnd otransitionend',
  808. 'transition' : 'transitionend'
  809. };
  810. for (var name in transEndEventNames) {
  811. if (el.style[name] !== undefined) {
  812. return { end: transEndEventNames[name] };
  813. }
  814. }
  815. return false; // explicit for ie8 ( ._.)
  816. }
  817. // http://blog.alexmaccaw.com/css-transitions
  818. $.fn.emulateTransitionEnd = function (duration) {
  819. var called = false, $el = this;
  820. $(this).one($.support.transition.end, function () { called = true; })
  821. var callback = function () { if (!called) $($el).trigger($.support.transition.end); }
  822. setTimeout(callback, duration);
  823. return this;
  824. };
  825. $(function () {
  826. $.support.transition = transitionEnd();
  827. });