jquery.magnify.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453
  1. /**
  2. * ___ ___ _____ ______ __ __ _____ ______ __ __
  3. * | \/ |/ _ \ / __ \| \ | |_ _| ___| \ / |
  4. * | | / \ | | \__| \| | | | | |__ \ \/ /
  5. * | |\/| | |_| | | ___ | | | | __| \ /
  6. * | | | | _ | \_/ | |\ |_| |_| | | |
  7. * |__| |__|__| |__|\____/|_|__| \__|_____|__| |__|
  8. *
  9. * jquery.magnify.js v0.1.0
  10. *
  11. * A jQuery plugin to view images just like in windows
  12. *
  13. * Copyright (c) 2017 nzbin
  14. *
  15. * Released under the MIT License
  16. *
  17. *
  18. */
  19. ;
  20. (function(factory) {
  21. if (typeof define === 'function' && define.amd) {
  22. // AMD. Register as anonymous module.
  23. define(['jquery'], factory);
  24. } else if (typeof exports === 'object') {
  25. // Node / CommonJS
  26. factory(require('jquery'));
  27. } else {
  28. // Browser globals.
  29. factory(jQuery);
  30. }
  31. })(function($) {
  32. 'use strict';
  33. /**
  34. * Private vars
  35. */
  36. var $W = $(window),
  37. $D = $(document),
  38. // plugin default options
  39. defaults = {
  40. draggable: true,
  41. resizable: true,
  42. movable: true,
  43. keyboard: true,
  44. title: true,
  45. modalWidth: 320,
  46. modalHeight: 320,
  47. fixedContent: false,
  48. fixedModalSize: false,
  49. initMaximized: false,
  50. gapThreshold: 0.02,
  51. ratioThreshold: 0.1,
  52. minRatio: 0.1,
  53. maxRatio: 16,
  54. toolbar: ['zoomIn', 'zoomOut', 'prev', 'fullscreen', 'next', 'actualSize', 'rotateRight'],
  55. icons: {
  56. maximize: 'fa fa-window-maximize',
  57. close: 'fa fa-close',
  58. zoomIn: 'fa fa-search-plus',
  59. zoomOut: 'fa fa-search-minus',
  60. prev: 'fa fa-arrow-left',
  61. next: 'fa fa-arrow-right',
  62. fullscreen: 'fa fa-photo',
  63. actualSize: 'fa fa-arrows-alt',
  64. rotateLeft: 'fa fa-rotate-left',
  65. rotateRight: 'fa fa-rotate-right'
  66. },
  67. lang: 'en',
  68. i18n: {},
  69. // beforeOpen:$.noop,
  70. // afterOpen:$.noop,
  71. // beforeClose:$.noop,
  72. // afterClose:$.noop
  73. },
  74. // jquery element of calling plugin
  75. jqEl = null,
  76. // image moving flag
  77. isMoving = false,
  78. // modal resizing flag
  79. isResizing = false,
  80. // modal open flag
  81. isOpened = false,
  82. // modal maximize flag
  83. isMaximized = false,
  84. // image rotate 90*(2n+1) flag
  85. isRotated = false,
  86. // image rotate angle
  87. rotateAngle = 0;
  88. /**
  89. * Magnify Class
  90. */
  91. var Magnify = function (el, options) {
  92. var self = this;
  93. this.options = $.extend(true, {}, defaults, options);
  94. if (options && $.isArray(options.toolbar)) {
  95. this.options.toolbar = options.toolbar;
  96. }
  97. this.init(el, self.options);
  98. this.isOpened = false;
  99. this.isMaximized = false;
  100. this.isRotated = false;
  101. this.rotateAngle = 0;
  102. // Store image data in every instance
  103. // this.imageData = {};
  104. }
  105. /**
  106. * Mangify prototype
  107. */
  108. Magnify.prototype = {
  109. init: function (el, options) {
  110. this.open();
  111. // Get image src
  112. var imgSrc = this.getImgSrc(el);
  113. // Get image group
  114. this.groupName = null;
  115. var currentGroupName = $(el).attr('data-group'),
  116. groupList = $D.find('[data-group="' + currentGroupName + '"]');
  117. if (currentGroupName !== undefined) {
  118. this.groupName = currentGroupName;
  119. this.getImgGroup(groupList, imgSrc);
  120. } else {
  121. this.getImgGroup(jqEl.not('[data-group]'), imgSrc);
  122. }
  123. this.loadImg(imgSrc);
  124. },
  125. creatBtns: function (btns) {
  126. var footerBtnsStr = '';
  127. $.each(this.options.toolbar, function (index, item) {
  128. footerBtnsStr += btns[item];
  129. });
  130. return footerBtnsStr;
  131. },
  132. creatDOM: function () {
  133. var btnsTpl = {
  134. maximize: '<button class="magnify-button magnify-button-maximize" title="maximize">\
  135. <i class="' + this.options.icons.maximize + '" aria-hidden="true"></i>\
  136. </button>',
  137. close: '<button class="magnify-button magnify-button-close" title="close">\
  138. <i class="' + this.options.icons.close + '" aria-hidden="true"></i>\
  139. </button>',
  140. zoomIn: '<button class="magnify-button magnify-button-zoom-in" title="zoom-in">\
  141. <i class="' + this.options.icons.zoomIn + '" aria-hidden="true"></i>\
  142. </button>',
  143. zoomOut: '<button class="magnify-button magnify-button-zoom-out" title="zoom-out">\
  144. <i class="' + this.options.icons.zoomOut + '" aria-hidden="true"></i>\
  145. </button>',
  146. prev: '<button class="magnify-button magnify-button-prev" title="prev">\
  147. <i class="' + this.options.icons.prev + '" aria-hidden="true"></i>\
  148. </button>',
  149. next: '<button class="magnify-button magnify-button-next" title="next">\
  150. <i class="' + this.options.icons.next + '" aria-hidden="true"></i>\
  151. </button>',
  152. fullscreen: '<button class="magnify-button magnify-button-fullscreen" title="fullscreen">\
  153. <i class="' + this.options.icons.fullscreen + '" aria-hidden="true"></i>\
  154. </button>',
  155. actualSize: '<button class="magnify-button magnify-button-actual-size" title="actual-size">\
  156. <i class="' + this.options.icons.actualSize + '" aria-hidden="true"></i>\
  157. </button>',
  158. rotateLeft: '<button class="magnify-button magnify-button-rotate-left" title="rotate-left">\
  159. <i class="' + this.options.icons.rotateLeft + '" aria-hidden="true"></i>\
  160. </button>',
  161. rotateRight: '<button class="magnify-button magnify-button-rotate-right" title="rotate-right">\
  162. <i class="' + this.options.icons.rotateRight + '" aria-hidden="true"></i>\
  163. </button>'
  164. }
  165. // magnify base HTML
  166. var magnifyHTML = '<div class="magnify-modal">\
  167. <div class="magnify-header">\
  168. <div class="magnify-title"></div>\
  169. <div class="magnify-toolbar">' + btnsTpl.maximize + btnsTpl.close + '</div>\
  170. </div>\
  171. <div class="magnify-stage">\
  172. <img src="" alt="" title="">\
  173. </div>\
  174. <div class="magnify-footer">\
  175. <div class="magnify-toolbar">' + this.creatBtns(btnsTpl) + '</div>\
  176. </div>\
  177. </div>';
  178. return magnifyHTML;
  179. },
  180. open: function () {
  181. // Fixed modal position bug
  182. if (!$('.magnify-modal').length) {
  183. $('html').css('overflow', 'hidden');
  184. }
  185. this.isOpened = isOpened = true;
  186. this.build();
  187. this.addEvent();
  188. this.resize();
  189. },
  190. build: function () {
  191. // Create magnify HTML string
  192. var magnifyHTML = this.creatDOM();
  193. // Make magnify HTML string to jQuery element
  194. var $magnify = $(magnifyHTML);
  195. // Get all magnify element
  196. this.$magnify = $magnify;
  197. this.$stage = $magnify.find('.magnify-stage');
  198. this.$title = $magnify.find('.magnify-title');
  199. this.$image = $magnify.find('.magnify-stage img');
  200. this.$close = $magnify.find('.magnify-button-close');
  201. this.$maximize = $magnify.find('.magnify-button-maximize');
  202. this.$zoomIn = $magnify.find('.magnify-button-zoom-in');
  203. this.$zoomOut = $magnify.find('.magnify-button-zoom-out');
  204. this.$actualSize = $magnify.find('.magnify-button-actual-size');
  205. this.$fullscreen = $magnify.find('.magnify-button-fullscreen');
  206. this.$rotateLeft = $magnify.find('.magnify-button-rotate-left');
  207. this.$rotateRight = $magnify.find('.magnify-button-rotate-right');
  208. this.$prev = $magnify.find('.magnify-button-prev');
  209. this.$next = $magnify.find('.magnify-button-next');
  210. $('body').append($magnify);
  211. this.setModalPos($magnify);
  212. // draggable & movable & resizable
  213. if (this.options.draggable) {
  214. this.draggable($magnify);
  215. }
  216. if (this.options.movable) {
  217. this.movable(this.$image, this.$stage);
  218. }
  219. if (this.options.resizable) {
  220. this.resizable($magnify, this.$stage, this.$image, this.options.modalWidth, this.options.modalHeight);
  221. }
  222. },
  223. close: function (el) {
  224. // off events
  225. // Remove instance
  226. this.$magnify.remove();
  227. this.isMaximized = false;
  228. this.isRotated = false;
  229. this.rotateAngle = rotateAngle = 0;
  230. this.isOpened = isOpened = false;
  231. // Fixed modal position bug
  232. if (!$('.magnify-modal').length) {
  233. $('html').css('overflow', 'auto');
  234. }
  235. },
  236. setModalPos: function (modal) {
  237. var winWidth = $W.width(),
  238. winHeight = $W.height(),
  239. scrollLeft = $D.scrollLeft(),
  240. scrollTop = $D.scrollTop();
  241. var modalWidth = modal.width(),
  242. modalHeight = modal.height();
  243. // Make the modal in windows center
  244. modal.css({
  245. left: (winWidth - modalWidth) / 2 + scrollLeft + 'px',
  246. top: (winHeight - modalHeight) / 2 + scrollTop + 'px'
  247. });
  248. },
  249. setModalSize: function (img) {
  250. var winWidth = $W.width(),
  251. winHeight = $W.height(),
  252. scrollLeft = $D.scrollLeft(),
  253. scrollTop = $D.scrollTop();
  254. // stage css value
  255. var stageCSS = {
  256. left: this.$stage.css('left'),
  257. right: this.$stage.css('right'),
  258. top: this.$stage.css('top'),
  259. bottom: this.$stage.css('bottom'),
  260. borderLeft: this.$stage.css('border-left-width'),
  261. borderRight: this.$stage.css('border-right-width'),
  262. borderTop: this.$stage.css('border-top-width'),
  263. borderBottom: this.$stage.css('border-bottom-width'),
  264. };
  265. // Modal size should calc with stage css value
  266. var modalWidth = img.width + getNumFromCSSValue(stageCSS.left) + getNumFromCSSValue(stageCSS.right) +
  267. getNumFromCSSValue(stageCSS.borderLeft) + getNumFromCSSValue(stageCSS.borderRight),
  268. modalHeight = img.height + getNumFromCSSValue(stageCSS.top) + getNumFromCSSValue(stageCSS.bottom) +
  269. getNumFromCSSValue(stageCSS.borderTop) + getNumFromCSSValue(stageCSS.borderBottom);
  270. var gapThreshold = (this.options.gapThreshold > 0 ? this.options.gapThreshold : 0) + 1,
  271. // modal scale to window
  272. scale = Math.min(winWidth / (modalWidth * gapThreshold), winHeight / (modalHeight * gapThreshold), 1);
  273. var minWidth = Math.max(modalWidth * scale, this.options.modalWidth),
  274. minHeight = Math.max(modalHeight * scale, this.options.modalHeight);
  275. minWidth = this.options.fixedModalSize ? this.options.modalWidth : Math.ceil(minWidth);
  276. minHeight = this.options.fixedModalSize ? this.options.modalHeight : Math.ceil(minHeight);
  277. this.$magnify.css({
  278. width: minWidth + 'px',
  279. height: minHeight + 'px',
  280. left: (winWidth - minWidth) / 2 + scrollLeft + 'px',
  281. top: (winHeight - minHeight) / 2 + scrollTop + 'px'
  282. });
  283. this.setImageSize(img);
  284. },
  285. setImageSize: function (img) {
  286. var stageData = {
  287. w: this.$stage.width(),
  288. h: this.$stage.height()
  289. }
  290. // image scale to stage
  291. var scale = 1;
  292. if (!this.isRotated) {
  293. scale = Math.min(stageData.w / img.width, stageData.h / img.height, 1);
  294. } else {
  295. scale = Math.min(stageData.w / img.height, stageData.h / img.width, 1);
  296. }
  297. this.$image.css({
  298. width: Math.ceil(img.width * scale) + 'px',
  299. height: Math.ceil(img.height * scale) + 'px',
  300. left: (stageData.w - img.width * scale) / 2 + 'px',
  301. top: (stageData.h - img.height * scale) / 2 + 'px'
  302. });
  303. // Store image initial data
  304. $.extend(this.imageData, {
  305. width: img.width * scale,
  306. height: img.height * scale,
  307. left: (stageData.w - img.width * scale) / 2,
  308. top: (stageData.h - img.height * scale) / 2
  309. });
  310. },
  311. loadImg: function (imgSrc) {
  312. var self = this;
  313. this.$image.attr('src', imgSrc);
  314. preloadImg(imgSrc, function (img) {
  315. // Store original data
  316. self.imageData = {
  317. originalWidth: img.width,
  318. originalHeight: img.height
  319. }
  320. if (self.isMaximized) {
  321. self.setImageSize(img);
  322. } else {
  323. self.setModalSize(img);
  324. }
  325. });
  326. if (this.options.title) {
  327. this.setImgTitle(imgSrc);
  328. }
  329. },
  330. getImgGroup: function (list, imgSrc) {
  331. var self = this;
  332. self.groupData = [];
  333. $(list).each(function (index, item) {
  334. var src = self.getImgSrc(this);
  335. self.groupData.push({
  336. src: src,
  337. caption: $(this).attr('data-caption')
  338. });
  339. // Get image index
  340. if (imgSrc === src) {
  341. self.groupIndex = index
  342. }
  343. });
  344. },
  345. setImgTitle: function (url) {
  346. var index = this.groupIndex,
  347. caption = this.groupData[index].caption,
  348. caption = caption ? caption : getImageNameFromUrl(url);
  349. this.$title.text(caption);
  350. },
  351. getImgSrc: function (el) {
  352. // Get data-src as image src at first
  353. var src = $(el).attr('data-src') ? $(el).attr('data-src') : $(el).attr('href');
  354. return src;
  355. },
  356. jump: function (index) {
  357. this.groupIndex = this.groupIndex + index;
  358. this.jumpTo(this.groupIndex);
  359. },
  360. jumpTo: function (index) {
  361. index = index % this.groupData.length;
  362. if (index >= 0) {
  363. index = index % this.groupData.length;
  364. } else if (index < 0) {
  365. index = (this.groupData.length + index) % this.groupData.length;
  366. }
  367. this.groupIndex = index;
  368. this.loadImg(this.groupData[index].src);
  369. },
  370. wheel: function (e) {
  371. e.preventDefault();
  372. var delta = 1;
  373. if (e.originalEvent.deltaY) {
  374. delta = e.originalEvent.deltaY > 0 ? 1 : -1;
  375. } else if (e.originalEvent.wheelDelta) {
  376. delta = -e.originalEvent.wheelDelta / 120;
  377. } else if (e.originalEvent.detail) {
  378. delta = e.originalEvent.detail > 0 ? 1 : -1;
  379. }
  380. // ratio threshold
  381. var ratio = -delta * this.options.ratioThreshold;
  382. // mouse point position
  383. var pointer = {
  384. x: e.originalEvent.clientX - this.$stage.offset().left,
  385. y: e.originalEvent.clientY - this.$stage.offset().top
  386. }
  387. this.zoom(ratio, pointer, e);
  388. },
  389. zoom: function (ratio, origin, e) {
  390. // zoom out & zoom in
  391. ratio = ratio < 0 ? (1 / (1 - ratio)) : (1 + ratio);
  392. if (ratio > 0.95 && ratio < 1.05) {
  393. ratio = 1;
  394. }
  395. ratio = this.$image.width() / this.imageData.originalWidth * ratio;
  396. // min image size
  397. ratio = Math.max(ratio, this.options.minRatio);
  398. // max image size
  399. ratio = Math.min(ratio, this.options.maxRatio);
  400. this.zoomTo(ratio, origin, e);
  401. },
  402. zoomTo: function (ratio, origin, e) {
  403. var $image = this.$image,
  404. $stage = this.$stage,
  405. imgData = {
  406. w: this.imageData.width,
  407. h: this.imageData.height,
  408. x: this.imageData.left,
  409. y: this.imageData.top
  410. };
  411. // image stage position
  412. // We will use it to calc the relative position of image
  413. var stageData = {
  414. w: $stage.width(),
  415. h: $stage.height(),
  416. x: $stage.offset().left,
  417. y: $stage.offset().top
  418. }
  419. var newWidth = this.imageData.originalWidth * ratio,
  420. newHeight = this.imageData.originalHeight * ratio,
  421. // Think about it for a while ~~~
  422. newLeft = origin.x - (origin.x - imgData.x) / imgData.w * newWidth,
  423. newTop = origin.y - (origin.y - imgData.y) / imgData.h * newHeight;
  424. var offsetX = stageData.w - newWidth,
  425. offsetY = stageData.h - newHeight,
  426. // Get the offsets when image rotate 90 deg
  427. offsetX2 = stageData.w - (newWidth + newHeight) / 2,
  428. offsetY2 = stageData.h - (newWidth + newHeight) / 2;
  429. // zoom out & zoom in condition
  430. // It's important and it takes me a lot of time to get it
  431. if (!this.isRotated) {
  432. if (newHeight <= stageData.h) {
  433. newTop = (stageData.h - newHeight) / 2;
  434. } else {
  435. newTop = newTop > 0 ? 0 : (newTop > offsetY ? newTop : offsetY);
  436. }
  437. if (newWidth <= stageData.w) {
  438. newLeft = (stageData.w - newWidth) / 2;
  439. } else {
  440. newLeft = newLeft > 0 ? 0 : (newLeft > offsetX ? newLeft : offsetX);
  441. }
  442. } else {
  443. // The conditions bellow drive me crazy alomst!
  444. if (newWidth <= stageData.h) {
  445. newTop = (stageData.h - newHeight) / 2;
  446. } else {
  447. newTop = newTop > (newWidth - newHeight) / 2 ? (newWidth - newHeight) / 2 : (newTop > offsetY2 ? newTop : offsetY2);
  448. }
  449. if (newHeight <= stageData.w) {
  450. newLeft = (stageData.w - newWidth) / 2;
  451. } else {
  452. newLeft = newLeft > (newHeight - newWidth) / 2 ? (newHeight - newWidth) / 2 : (newLeft > offsetX2 ? newLeft : offsetX2);
  453. }
  454. }
  455. $image.css({
  456. width: Math.ceil(newWidth) + 'px',
  457. height: Math.ceil(newHeight) + 'px',
  458. left: newLeft + 'px',
  459. top: newTop + 'px'
  460. });
  461. // Update image initial data
  462. $.extend(this.imageData, {
  463. width: newWidth,
  464. height: newHeight,
  465. left: newLeft,
  466. top: newTop
  467. });
  468. },
  469. rotate: function (angle) {
  470. this.rotateAngle = rotateAngle = rotateAngle + angle;
  471. if ((rotateAngle / 90) % 2 === 0) {
  472. this.isRotated = false;
  473. } else {
  474. this.isRotated = true;
  475. }
  476. this.rotateTo(rotateAngle);
  477. },
  478. rotateTo: function (angle) {
  479. var self = this;
  480. this.$image.css({
  481. transform: 'rotate(' + angle + 'deg)'
  482. });
  483. this.setImageSize({ width: this.imageData.originalWidth, height: this.imageData.originalHeight });
  484. },
  485. resize: function () {
  486. var self = this;
  487. var resizeHandler = throttle(function(){
  488. if (isOpened) {
  489. if (!self.isMaximized) {
  490. self.setModalSize({ width: self.imageData.originalWidth, height: self.imageData.originalHeight });
  491. }
  492. self.setImageSize({ width: self.imageData.originalWidth, height: self.imageData.originalHeight });
  493. }
  494. }, 500);
  495. $W.off('resize').on('resize', resizeHandler);
  496. },
  497. maximize: function () {
  498. var self = this;
  499. var scrollLeft = $D.scrollLeft(),
  500. scrollTop = $D.scrollTop();
  501. if (!this.isMaximized) {
  502. // Store modal data before maximize
  503. this.modalData = {
  504. width: this.$magnify.width(),
  505. height: this.$magnify.height(),
  506. left: this.$magnify.offset().left,
  507. top: this.$magnify.offset().top
  508. }
  509. this.$magnify.addClass('magnify-maximize');
  510. this.$magnify.css({
  511. width: '100%',
  512. height: '100%',
  513. left: scrollLeft,
  514. top: scrollTop
  515. });
  516. this.isMaximized = true;
  517. } else {
  518. this.$magnify.removeClass('magnify-maximize');
  519. this.$magnify.css({
  520. width: this.modalData.width,
  521. height: this.modalData.height,
  522. left: this.modalData.left,
  523. top: this.modalData.top
  524. });
  525. this.isMaximized = false;
  526. }
  527. this.setImageSize({ width: this.imageData.originalWidth, height: this.imageData.originalHeight });
  528. },
  529. fullscreen: function () {
  530. requestFullscreen(this.$magnify[0]);
  531. },
  532. keydown: function (e) {
  533. e.preventDefault();
  534. var self = this;
  535. if (!this.options.keyboard) {
  536. return false;
  537. }
  538. var keyCode = e.keyCode || e.which || e.charCode,
  539. ctrlKey = e.ctrlKey || e.metaKey,
  540. altKey = e.altKey || e.metaKey;
  541. switch (keyCode) {
  542. // ←
  543. case 37:
  544. self.jump(-1);
  545. break;
  546. // →
  547. case 39:
  548. self.jump(1);
  549. break;
  550. // +
  551. case 187:
  552. self.zoom(self.options.ratioThreshold * 3, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  553. break;
  554. // -
  555. case 189:
  556. self.zoom(-self.options.ratioThreshold * 3, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  557. break;
  558. // + Firefox
  559. case 61:
  560. self.zoom(self.options.ratioThreshold * 3, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  561. break;
  562. // - Firefox
  563. case 173:
  564. self.zoom(-self.options.ratioThreshold * 3, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  565. break;
  566. // ctrl + alt + 0
  567. case 48:
  568. if (ctrlKey && altKey) {
  569. self.zoomTo(1, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  570. }
  571. break;
  572. // ctrl + ,
  573. case 188:
  574. if (ctrlKey) {
  575. self.rotate(-90);
  576. }
  577. break;
  578. // ctrl + .
  579. case 190:
  580. if (ctrlKey) {
  581. self.rotate(90);
  582. }
  583. break;
  584. default:
  585. }
  586. },
  587. addEvent: function () {
  588. var self = this;
  589. this.$close.off('click').on('click', function (e) {
  590. self.close();
  591. });
  592. this.$stage.off('wheel mousewheel DOMMouseScroll').on('wheel mousewheel DOMMouseScroll', function (e) {
  593. self.wheel(e);
  594. });
  595. this.$zoomIn.off('click').on('click', function (e) {
  596. self.zoom(self.options.ratioThreshold * 3, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  597. });
  598. this.$zoomOut.off('click').on('click', function (e) {
  599. self.zoom(-self.options.ratioThreshold * 3, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  600. });
  601. this.$actualSize.off('click').on('click', function (e) {
  602. self.zoomTo(1, { x: self.$stage.width() / 2, y: self.$stage.height() / 2 }, e);
  603. });
  604. this.$prev.off('click').on('click', function () {
  605. self.jump(-1);
  606. });
  607. this.$fullscreen.off('click').on('click', function () {
  608. self.fullscreen();
  609. });
  610. this.$next.off('click').on('click', function () {
  611. self.jump(1);
  612. });
  613. this.$rotateLeft.off('click').on('click', function () {
  614. self.rotate(-90);
  615. });
  616. this.$rotateRight.off('click').on('click', function () {
  617. self.rotate(90);
  618. });
  619. this.$maximize.off('click').on('click', function () {
  620. self.maximize();
  621. });
  622. $D.off('keydown').on('keydown', function (e) {
  623. self.keydown(e);
  624. });
  625. }
  626. }
  627. /**
  628. * Public static functions
  629. */
  630. $.magnify = {
  631. instance: Magnify.prototype
  632. }
  633. $.fn.magnify = function (options) {
  634. jqEl = $(this);
  635. if (typeof options === 'string') {
  636. // $(this).data('magnify')[options]();
  637. } else {
  638. jqEl.off('click.magnify').on('click.magnify', function (e) {
  639. if (e.isDefaultPrevented()) {
  640. return;
  641. }
  642. e.preventDefault();
  643. $(this).data('magnify', new Magnify(this, options));
  644. });
  645. }
  646. return jqEl;
  647. }
  648. /**
  649. * MAGNIFY DATA-API
  650. */
  651. $D.on('click.magnify', '[data-magnify]', function (e) {
  652. jqEl = $('[data-magnify]');
  653. if (e.isDefaultPrevented()) {
  654. return;
  655. }
  656. e.preventDefault();
  657. $(this).data('magnify', new Magnify(this, {}));
  658. });
  659. /**
  660. * draggable
  661. */
  662. // modal draggable
  663. var draggable = function(modal) {
  664. var self = this;
  665. var isDragging = false;
  666. var startX = 0,
  667. startY = 0,
  668. left = 0,
  669. top = 0;
  670. var dragStart = function(e) {
  671. var e = e || window.event;
  672. e.preventDefault();
  673. // Get clicked button
  674. var elemCancel = $(e.target).closest('.magnify-button');
  675. // Stop modal moving when click buttons
  676. if(elemCancel.length){
  677. return true;
  678. }
  679. isDragging = true;
  680. startX = e.clientX;
  681. startY = e.clientY;
  682. left = $(modal).offset().left;
  683. top = $(modal).offset().top;
  684. }
  685. var dragMove = function(e) {
  686. var e = e || window.event;
  687. e.preventDefault();
  688. if (isDragging && !isMoving && !isResizing && !self.isMaximized) {
  689. var endX = e.clientX,
  690. endY = e.clientY,
  691. relativeX = endX - startX,
  692. relativeY = endY - startY;
  693. $(modal).css({
  694. left: relativeX + left + 'px',
  695. top: relativeY + top + 'px'
  696. });
  697. }
  698. return false;
  699. }
  700. var dragEnd = function(e) {
  701. isDragging = false;
  702. }
  703. $(modal).on('mousedown', dragStart);
  704. $D.on('mousemove', dragMove);
  705. $D.on('mouseup', dragEnd);
  706. }
  707. // Add to Magnify Prototype
  708. $.extend(Magnify.prototype, {
  709. draggable: draggable
  710. });
  711. /**
  712. * image movable
  713. * --------------------------------------
  714. * 1.no movable
  715. * 2.vertical movable
  716. * 3.horizontal movable
  717. * 4.vertical & horizontal movable
  718. * --------------------------------------
  719. */
  720. var movable = function(image, stage) {
  721. var self = this;
  722. var isDragging = false;
  723. var startX = 0,
  724. startY = 0,
  725. left = 0,
  726. top = 0,
  727. widthDiff = 0,
  728. heightDiff = 0,
  729. δ = 0;
  730. var dragStart = function(e) {
  731. var e = e || window.event;
  732. e.preventDefault();
  733. var imageWidth = $(image).width(),
  734. imageHeight = $(image).height(),
  735. stageWidth = $(stage).width(),
  736. stageHeight = $(stage).height();
  737. isDragging = true;
  738. isMoving = true;
  739. startX = e.clientX;
  740. startY = e.clientY;
  741. // δ is the difference between image width and height
  742. δ = !self.isRotated ? 0 : (imageWidth - imageHeight) / 2;
  743. // Width or height difference can be use to limit image right or top position
  744. widthDiff = !self.isRotated ? (imageWidth - stageWidth) : (imageHeight - stageWidth);
  745. heightDiff = !self.isRotated ? (imageHeight - stageHeight) : (imageWidth - stageHeight);
  746. // Reclac the element position when mousedown
  747. // Fixed the issue of stage with a border
  748. left = $(image).position().left - δ;
  749. top = $(image).position().top + δ;
  750. }
  751. var dragMove = function(e) {
  752. var e = e || window.event;
  753. e.preventDefault();
  754. if (isDragging) {
  755. var endX = e.clientX,
  756. endY = e.clientY,
  757. relativeX = endX - startX,
  758. relativeY = endY - startY,
  759. newLeft = relativeX + left,
  760. newTop = relativeY + top;
  761. // vertical limit
  762. if (heightDiff > 0) {
  763. if ((relativeY + top) > δ) {
  764. newTop = δ;
  765. } else if ((relativeY + top) < -heightDiff + δ) {
  766. newTop = -heightDiff + δ;
  767. }
  768. } else {
  769. newTop = top;
  770. }
  771. // horizontal limit
  772. if (widthDiff > 0) {
  773. if ((relativeX + left) > -δ) {
  774. newLeft = -δ;
  775. } else if ((relativeX + left) < -widthDiff - δ) {
  776. newLeft = -widthDiff - δ;
  777. }
  778. } else {
  779. newLeft = left;
  780. }
  781. $(image).css({
  782. left: newLeft + 'px',
  783. top: newTop + 'px',
  784. });
  785. // Update image initial data
  786. $.extend(self.imageData, {
  787. left: newLeft,
  788. top: newTop
  789. });
  790. }
  791. return false;
  792. }
  793. var dragEnd = function(e) {
  794. isDragging = false;
  795. isMoving = false;
  796. }
  797. $(image).on('mousedown', dragStart);
  798. $D.on('mousemove', dragMove);
  799. $D.on('mouseup', dragEnd);
  800. }
  801. // Add to Magnify Prototype
  802. $.extend(Magnify.prototype, {
  803. movable: movable
  804. });
  805. /**
  806. * resizable
  807. * ------------------------------
  808. * 1.modal resizable
  809. * 2.keep image in stage center
  810. * ------------------------------
  811. */
  812. var resizable = function(modal, stage, image, minWidth, minHeight) {
  813. var self = this;
  814. var resizableHandleE = $('<div class="resizable-handle resizable-handle-e"></div>'),
  815. resizableHandleW = $('<div class="resizable-handle resizable-handle-w"></div>'),
  816. resizableHandleS = $('<div class="resizable-handle resizable-handle-s"></div>'),
  817. resizableHandleN = $('<div class="resizable-handle resizable-handle-n"></div>'),
  818. resizableHandleSE = $('<div class="resizable-handle resizable-handle-se"></div>'),
  819. resizableHandleSW = $('<div class="resizable-handle resizable-handle-sw"></div>'),
  820. resizableHandleNE = $('<div class="resizable-handle resizable-handle-ne"></div>'),
  821. resizableHandleNW = $('<div class="resizable-handle resizable-handle-nw"></div>');
  822. var resizableHandles = {
  823. 'e': resizableHandleE,
  824. 's': resizableHandleS,
  825. 'se': resizableHandleSE,
  826. 'n': resizableHandleN,
  827. 'w': resizableHandleW,
  828. 'nw': resizableHandleNW,
  829. 'ne': resizableHandleNE,
  830. 'sw': resizableHandleSW,
  831. }
  832. $(modal).append(
  833. resizableHandleE, resizableHandleW, resizableHandleS, resizableHandleN, resizableHandleSE, resizableHandleSW, resizableHandleNE, resizableHandleNW
  834. );
  835. var isDragging = false;
  836. var draggingLimit = false;
  837. var startX = 0,
  838. startY = 0,
  839. modalData = {
  840. w: 0,
  841. h: 0,
  842. l: 0,
  843. t: 0
  844. },
  845. stageData = {
  846. w: 0,
  847. h: 0,
  848. l: 0,
  849. t: 0
  850. },
  851. imageData = {
  852. w: 0,
  853. h: 0,
  854. l: 0,
  855. t: 0
  856. };
  857. var direction = '';
  858. // modal CSS options
  859. var getModalOpts = function(dir, offsetX, offsetY) {
  860. // Modal should not move when its width to the minwidth
  861. var modalLeft = (-offsetX + modalData.w) > minWidth ? (offsetX + modalData.l) : (modalData.l + modalData.w - minWidth),
  862. modalTop = (-offsetY + modalData.h) > minHeight ? (offsetY + modalData.t) : (modalData.t + modalData.h - minHeight);
  863. var opts = {
  864. 'e': {
  865. width: Math.max((offsetX + modalData.w), minWidth) + 'px',
  866. },
  867. 's': {
  868. height: Math.max((offsetY + modalData.h), minHeight) + 'px'
  869. },
  870. 'se': {
  871. width: Math.max((offsetX + modalData.w), minWidth) + 'px',
  872. height: Math.max((offsetY + modalData.h), minHeight) + 'px'
  873. },
  874. 'w': {
  875. width: Math.max((-offsetX + modalData.w), minWidth) + 'px',
  876. left: modalLeft + 'px'
  877. },
  878. 'n': {
  879. height: Math.max((-offsetY + modalData.h), minHeight) + 'px',
  880. top: modalTop + 'px'
  881. },
  882. 'nw': {
  883. width: Math.max((-offsetX + modalData.w), minWidth) + 'px',
  884. height: Math.max((-offsetY + modalData.h), minHeight) + 'px',
  885. top: modalTop + 'px',
  886. left: modalLeft + 'px'
  887. },
  888. 'ne': {
  889. width: Math.max((offsetX + modalData.w), minWidth) + 'px',
  890. height: Math.max((-offsetY + modalData.h), minHeight) + 'px',
  891. top: modalTop + 'px'
  892. },
  893. 'sw': {
  894. width: Math.max((-offsetX + modalData.w), minWidth) + 'px',
  895. height: Math.max((offsetY + modalData.h), minHeight) + 'px',
  896. left: modalLeft + 'px'
  897. }
  898. };
  899. return opts[dir];
  900. }
  901. // image CSS options
  902. var getImageOpts = function(dir, offsetX, offsetY) {
  903. // δ is the difference between image width and height
  904. var δ = !self.isRotated ? 0 : (imageData.w - imageData.h) / 2,
  905. imgWidth = !self.isRotated ? imageData.w : imageData.h,
  906. imgHeight = !self.isRotated ? imageData.h : imageData.w;
  907. // Image should not move when modal width to the min width
  908. // The minwidth is modal width, so we should clac the stage minwidth
  909. var widthDiff = (offsetX + modalData.w) > minWidth ? (stageData.w - imgWidth + offsetX - δ) : (minWidth - (modalData.w - stageData.w) - imgWidth - δ),
  910. heightDiff = (offsetY + modalData.h) > minHeight ? (stageData.h - imgHeight + offsetY + δ) : (minHeight - (modalData.h - stageData.h) - imgHeight + δ),
  911. widthDiff2 = (-offsetX + modalData.w) > minWidth ? (stageData.w - imgWidth - offsetX - δ) : (minWidth - (modalData.w - stageData.w) - imgWidth - δ),
  912. heightDiff2 = (-offsetY + modalData.h) > minHeight ? (stageData.h - imgHeight - offsetY + δ) : (minHeight - (modalData.h - stageData.h) - imgHeight + δ);
  913. // Get image position in dragging
  914. var imgLeft = $(image).position().left - δ,
  915. imgTop = $(image).position().top + δ;
  916. var opts = {
  917. 'e': {
  918. left: widthDiff >= -δ ? ((widthDiff - δ) / 2 + 'px') : (imgLeft > widthDiff ? (imgLeft + 'px') : (widthDiff + 'px'))
  919. },
  920. 's': {
  921. top: heightDiff >= δ ? ((heightDiff + δ) / 2 + 'px') : (imgTop > heightDiff ? (imgTop + 'px') : (heightDiff + 'px'))
  922. },
  923. 'se': {
  924. top: heightDiff >= δ ? ((heightDiff + δ) / 2 + 'px') : (imgTop > heightDiff ? (imgTop + 'px') : (heightDiff + 'px')),
  925. left: widthDiff >= -δ ? ((widthDiff - δ) / 2 + 'px') : (imgLeft > widthDiff ? (imgLeft + 'px') : (widthDiff + 'px'))
  926. },
  927. 'w': {
  928. left: widthDiff2 >= -δ ? ((widthDiff2 - δ) / 2 + 'px') : (imgLeft > widthDiff2 ? (imgLeft + 'px') : (widthDiff2 + 'px'))
  929. },
  930. 'n': {
  931. top: heightDiff2 >= δ ? ((heightDiff2 + δ) / 2 + 'px') : (imgTop > heightDiff2 ? (imgTop + 'px') : (heightDiff2 + 'px'))
  932. },
  933. 'nw': {
  934. top: heightDiff2 >= δ ? ((heightDiff2 + δ) / 2 + 'px') : (imgTop > heightDiff2 ? (imgTop + 'px') : (heightDiff2 + 'px')),
  935. left: widthDiff2 >= -δ ? ((widthDiff2 - δ) / 2 + 'px') : (imgLeft > widthDiff2 ? (imgLeft + 'px') : (widthDiff2 + 'px'))
  936. },
  937. 'ne': {
  938. top: heightDiff2 >= δ ? ((heightDiff2 + δ) / 2 + 'px') : (imgTop > heightDiff2 ? (imgTop + 'px') : (heightDiff2 + 'px')),
  939. left: widthDiff >= -δ ? ((widthDiff - δ) / 2 + 'px') : (imgLeft > widthDiff ? (imgLeft + 'px') : (widthDiff + 'px'))
  940. },
  941. 'sw': {
  942. top: heightDiff >= δ ? ((heightDiff + δ) / 2 + 'px') : (imgTop > heightDiff ? (imgTop + 'px') : (heightDiff + 'px')),
  943. left: widthDiff2 >= -δ ? ((widthDiff2 - δ) / 2 + 'px') : (imgLeft > widthDiff2 ? (imgLeft + 'px') : (widthDiff2 + 'px'))
  944. }
  945. };
  946. return opts[dir];
  947. }
  948. var dragStart = function(dir, e) {
  949. var e = e || window.event;
  950. e.preventDefault();
  951. isDragging = true;
  952. isResizing = true;
  953. startX = e.clientX;
  954. startY = e.clientY;
  955. // Reclac the modal data when mousedown
  956. modalData = {
  957. w: $(modal).width(),
  958. h: $(modal).height(),
  959. l: $(modal).offset().left,
  960. t: $(modal).offset().top
  961. };
  962. stageData = {
  963. w: $(stage).width(),
  964. h: $(stage).height(),
  965. l: $(stage).offset().left,
  966. t: $(stage).offset().top
  967. };
  968. imageData = {
  969. w: $(image).width(),
  970. h: $(image).height(),
  971. l: $(image).position().left,
  972. t: $(image).position().top
  973. };
  974. direction = dir;
  975. }
  976. var dragMove = function(e) {
  977. var e = e || window.event;
  978. e.preventDefault();
  979. if (isDragging && !self.isMaximized) {
  980. var endX = e.clientX,
  981. endY = e.clientY,
  982. relativeX = endX - startX,
  983. relativeY = endY - startY;
  984. var modalOpts = getModalOpts(direction, relativeX, relativeY);
  985. $(modal).css(modalOpts);
  986. // Limit dragging speed to prevent drag too fast
  987. // ?
  988. // if (draggingLimit) {
  989. // return false;
  990. // }
  991. // draggingLimit = true;
  992. // setTimeout(function() {
  993. // draggingLimit = false;
  994. // }, 50);
  995. var imageOpts = getImageOpts(direction, relativeX, relativeY);
  996. $(image).css(imageOpts);
  997. }
  998. return false;
  999. }
  1000. var dragEnd = function(e) {
  1001. isDragging = false;
  1002. isResizing = false;
  1003. }
  1004. $.each(resizableHandles, function(dir, handle) {
  1005. handle.on('mousedown', function(e) {
  1006. dragStart(dir, e);
  1007. });
  1008. });
  1009. $D.on('mousemove', dragMove);
  1010. $D.on('mouseup', dragEnd);
  1011. }
  1012. // Add to Magnify Prototype
  1013. $.extend(Magnify.prototype, {
  1014. resizable: resizable
  1015. });
  1016. /**
  1017. * Private functions
  1018. */
  1019. /**
  1020. * [throttle]
  1021. * @param {Function} fn [description]
  1022. * @param {[Number]} delay [description]
  1023. * @return {Function} [description]
  1024. */
  1025. function throttle(fn, delay) {
  1026. var timer = null;
  1027. return function() {
  1028. var context = this,
  1029. args = arguments;
  1030. clearTimeout(timer);
  1031. timer = setTimeout(function() {
  1032. fn.apply(context, args);
  1033. }, delay);
  1034. };
  1035. };
  1036. /**
  1037. * [preloadImg]
  1038. * @param {[String]} src [image src]
  1039. * @param {Function} fn [callbacks]
  1040. */
  1041. function preloadImg(src, fn) {
  1042. var img = new Image();
  1043. if (!!window.ActiveXObject) {
  1044. img.onreadystatechange = function() {
  1045. if (this.readyState == 'complete') {
  1046. fn(img);
  1047. }
  1048. }
  1049. } else {
  1050. img.onload = function() {
  1051. fn(img);
  1052. }
  1053. }
  1054. img.src = src;
  1055. }
  1056. /**
  1057. * [requestFullscreen description]
  1058. * @param {[type]} element [description]
  1059. */
  1060. function requestFullscreen(element) {
  1061. if (element.requestFullscreen) {
  1062. element.requestFullscreen();
  1063. } else if (element.mozRequestFullScreen) {
  1064. element.mozRequestFullScreen();
  1065. } else if (element.webkitRequestFullscreen) {
  1066. element.webkitRequestFullscreen();
  1067. } else if (element.msRequestFullscreen) {
  1068. element.msRequestFullscreen();
  1069. }
  1070. }
  1071. /**
  1072. * [exitFullscreen description]
  1073. */
  1074. function exitFullscreen() {
  1075. if (document.exitFullscreen) {
  1076. document.exitFullscreen();
  1077. } else if (document.mozCancelFullScreen) {
  1078. document.mozCancelFullScreen();
  1079. } else if (document.webkitExitFullscreen) {
  1080. document.webkitExitFullscreen();
  1081. }
  1082. }
  1083. /**
  1084. * [getImageNameFromUrl]
  1085. * @param {[String]} url [description]
  1086. * @return {[String]} [description]
  1087. */
  1088. function getImageNameFromUrl(url) {
  1089. var reg = /^.*?\/*([^/?]*)\.[a-z]+(\?.+|$)/ig,
  1090. txt = url.replace(reg, '$1');
  1091. return txt;
  1092. }
  1093. /**
  1094. * [getNumFromCSSValue description]
  1095. * @param {[type]} value [description]
  1096. * @return {[type]} [description]
  1097. */
  1098. function getNumFromCSSValue(value) {
  1099. var reg = /\d+/g,
  1100. arr = value.match(reg),
  1101. num = parseFloat(arr[0]);
  1102. return num;
  1103. }
  1104. });