magnifier.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. (function ($) {
  6. $.fn.magnify = function (options) {
  7. 'use strict';
  8. var magnify = new Magnify($(this), options);
  9. /* events must be tracked here */
  10. /**
  11. * Return that from _init function
  12. *
  13. */
  14. return magnify;
  15. };
  16. function Magnify(element, options) {
  17. var customUserOptions = options || {},
  18. $box = $(element),
  19. $thumb,
  20. that = this,
  21. largeWrapper = options.largeWrapper || '.magnifier-preview',
  22. $magnifierPreview = $(largeWrapper);
  23. curThumb = null,
  24. magnifierOptions = {
  25. x: 0,
  26. y: 0,
  27. w: 0,
  28. h: 0,
  29. lensW: 0,
  30. lensH: 0,
  31. lensBgX: 0,
  32. lensBgY: 0,
  33. largeW: 0,
  34. largeH: 0,
  35. largeL: 0,
  36. largeT: 0,
  37. zoom: 2,
  38. zoomMin: 1.1,
  39. zoomMax: 5,
  40. mode: 'outside',
  41. eventType: 'click',
  42. status: 0,
  43. zoomAttached: false,
  44. zoomable: customUserOptions.zoomable !== undefined ?
  45. customUserOptions.zoomable
  46. : false,
  47. onthumbenter: customUserOptions.onthumbenter !== undefined ?
  48. customUserOptions.onthumbenter
  49. : null,
  50. onthumbmove: customUserOptions.onthumbmove !== undefined ?
  51. customUserOptions.onthumbmove
  52. : null,
  53. onthumbleave: customUserOptions.onthumbleave !== undefined ?
  54. customUserOptions.onthumbleave
  55. : null,
  56. onzoom: customUserOptions.onzoom !== undefined ?
  57. customUserOptions.onzoom
  58. : null
  59. },
  60. pos = {
  61. t: 0,
  62. l: 0,
  63. x: 0,
  64. y: 0
  65. },
  66. gId = 0,
  67. status = 0,
  68. curIdx = '',
  69. curLens = null,
  70. curLarge = null,
  71. lensbg = customUserOptions.bg !== undefined ?
  72. customUserOptions.lensbg
  73. : true,
  74. gZoom = customUserOptions.zoom !== undefined ?
  75. customUserOptions.zoom
  76. : magnifierOptions.zoom,
  77. gZoomMin = customUserOptions.zoomMin !== undefined ?
  78. customUserOptions.zoomMin
  79. : magnifierOptions.zoomMin,
  80. gZoomMax = customUserOptions.zoomMax !== undefined ?
  81. customUserOptions.zoomMax
  82. : magnifierOptions.zoomMax,
  83. gMode = customUserOptions.mode || magnifierOptions.mode,
  84. gEventType = customUserOptions.eventType || magnifierOptions.eventType,
  85. data = {},
  86. inBounds = false,
  87. isOverThumb = false,
  88. rate = 1,
  89. paddingX = 0,
  90. paddingY = 0,
  91. enabled = true,
  92. showWrapper = true;
  93. var MagnifyCls = {
  94. magnifyHidden: 'magnify-hidden',
  95. magnifyOpaque: 'magnify-opaque',
  96. magnifyFull: 'magnify-fullimage'
  97. };
  98. /**
  99. * Update Lens positon on.
  100. *
  101. */
  102. that.update = function () {
  103. updateLensOnLoad();
  104. };
  105. /**
  106. * Init new Magnifier
  107. *
  108. */
  109. that.init = function () {
  110. _init($box, options);
  111. };
  112. function _toBoolean(str) {
  113. if (typeof str === 'string') {
  114. if (str === 'true') {
  115. return true;
  116. } else if (str === 'false' || '') {
  117. return false;
  118. }
  119. console.warn('Wrong type: can\'t be transformed to Boolean');
  120. } else if (typeof str === 'boolean') {
  121. return str;
  122. }
  123. }
  124. function createLens(thumb) {
  125. if ($(thumb).siblings('.magnify-lens').length) {
  126. return false;
  127. }
  128. var lens = $('<div class="magnify-lens magnify-hidden" data-gallery-role="magnifier-zoom"></div>');
  129. $(thumb).parent().append(lens);
  130. }
  131. function updateLensOnLoad(idSelectorMainImg, thumb, largeImgInMagnifyLens, largeWrapper) {
  132. var magnifyLensElement= $box.find('.magnify-lens'),
  133. textWrapper;
  134. if (data[idSelectorMainImg].status === 1) {
  135. textWrapper = $('<div class="magnifier-loader-text"></div>');
  136. magnifyLensElement.className = 'magnifier-loader magnify-hidden';
  137. textWrapper.html('Loading...');
  138. magnifyLensElement.html('').append(textWrapper);
  139. } else if (data[idSelectorMainImg].status === 2) {
  140. magnifyLensElement.addClass(MagnifyCls.magnifyHidden);
  141. magnifyLensElement.html('');
  142. largeImgInMagnifyLens.id = idSelectorMainImg + '-large';
  143. largeImgInMagnifyLens.style.width = data[idSelectorMainImg].largeImgInMagnifyLensWidth + 'px';
  144. largeImgInMagnifyLens.style.height = data[idSelectorMainImg].largeImgInMagnifyLensHeight + 'px';
  145. largeImgInMagnifyLens.className = 'magnifier-large magnify-hidden';
  146. if (data[idSelectorMainImg].mode === 'inside') {
  147. magnifyLensElement.append(largeImgInMagnifyLens);
  148. } else {
  149. largeWrapper.html('').append(largeImgInMagnifyLens);
  150. }
  151. }
  152. data[idSelectorMainImg].lensH = data[idSelectorMainImg].lensH > $thumb.height() ? $thumb.height() : data[idSelectorMainImg].lensH;
  153. if (Math.round(data[idSelectorMainImg].lensW) === 0) {
  154. magnifyLensElement.css('display', 'none');
  155. } else {
  156. magnifyLensElement.css({
  157. width: Math.round(data[idSelectorMainImg].lensW) + 'px',
  158. height: Math.round(data[idSelectorMainImg].lensH) + 'px',
  159. display: ''
  160. });
  161. }
  162. }
  163. function getMousePos() {
  164. var xPos = pos.x - magnifierOptions.x,
  165. yPos = pos.y - magnifierOptions.y,
  166. t,
  167. l;
  168. inBounds = xPos < 0 || yPos < 0 || xPos > magnifierOptions.w || yPos > magnifierOptions.h ? false : true;
  169. l = xPos - magnifierOptions.lensW / 2;
  170. t = yPos - magnifierOptions.lensH / 2;
  171. if (xPos < magnifierOptions.lensW / 2) {
  172. l = 0;
  173. }
  174. if (yPos < magnifierOptions.lensH / 2) {
  175. t = 0;
  176. }
  177. if (xPos - magnifierOptions.w + Math.ceil(magnifierOptions.lensW / 2) > 0) {
  178. l = magnifierOptions.w - Math.ceil(magnifierOptions.lensW + 2);
  179. }
  180. if (yPos - magnifierOptions.h + Math.ceil(magnifierOptions.lensH / 2) > 0) {
  181. t = magnifierOptions.h - Math.ceil(magnifierOptions.lensH);
  182. }
  183. pos.l = l;
  184. pos.t = t;
  185. magnifierOptions.lensBgX = pos.l;
  186. magnifierOptions.lensBgY = pos.t;
  187. if (magnifierOptions.mode === 'inside') {
  188. magnifierOptions.largeL = Math.round(xPos * (magnifierOptions.zoom - magnifierOptions.lensW / magnifierOptions.w));
  189. magnifierOptions.largeT = Math.round(yPos * (magnifierOptions.zoom - magnifierOptions.lensH / magnifierOptions.h));
  190. } else {
  191. magnifierOptions.largeL = Math.round(magnifierOptions.lensBgX * magnifierOptions.zoom * (magnifierOptions.largeWrapperW / magnifierOptions.w));
  192. magnifierOptions.largeT = Math.round(magnifierOptions.lensBgY * magnifierOptions.zoom * (magnifierOptions.largeWrapperH / magnifierOptions.h));
  193. }
  194. }
  195. function onThumbEnter() {
  196. if (_toBoolean(enabled)) {
  197. magnifierOptions = data[curIdx];
  198. curLens = $box.find('.magnify-lens');
  199. if (magnifierOptions.status === 2) {
  200. curLens.removeClass(MagnifyCls.magnifyOpaque);
  201. curLarge = $('#' + curIdx + '-large');
  202. curLarge.removeClass(MagnifyCls.magnifyHidden);
  203. } else if (magnifierOptions.status === 1) {
  204. curLens.className = 'magnifier-loader';
  205. }
  206. }
  207. }
  208. function onThumbLeave() {
  209. if (magnifierOptions.status > 0) {
  210. var handler = magnifierOptions.onthumbleave;
  211. if (handler !== null) {
  212. handler({
  213. thumb: curThumb,
  214. lens: curLens,
  215. large: curLarge,
  216. x: pos.x,
  217. y: pos.y
  218. });
  219. }
  220. if (!curLens.hasClass(MagnifyCls.magnifyHidden)) {
  221. curLens.addClass(MagnifyCls.magnifyHidden);
  222. //$curThumb.removeClass(MagnifyCls.magnifyOpaque);
  223. if (curLarge !== null) {
  224. curLarge.addClass(MagnifyCls.magnifyHidden);
  225. }
  226. }
  227. }
  228. }
  229. function move() {
  230. if (_toBoolean(enabled)) {
  231. if (status !== magnifierOptions.status) {
  232. onThumbEnter();
  233. }
  234. if (magnifierOptions.status > 0) {
  235. curThumb.className = magnifierOptions.thumbCssClass + ' magnify-opaque';
  236. if (magnifierOptions.status === 1) {
  237. curLens.className = 'magnifier-loader';
  238. } else if (magnifierOptions.status === 2) {
  239. curLens.removeClass(MagnifyCls.magnifyHidden);
  240. curLarge.removeClass(MagnifyCls.magnifyHidden);
  241. curLarge.css({
  242. left: '-' + magnifierOptions.largeL + 'px',
  243. top: '-' + magnifierOptions.largeT + 'px'
  244. });
  245. }
  246. var borderOffset = 2; // Offset for magnify-lens border
  247. pos.t = pos.t <= 0 ? 0 : pos.t - borderOffset;
  248. curLens.css({
  249. left: pos.l + paddingX + 'px',
  250. top: pos.t + paddingY + 'px'
  251. });
  252. if (lensbg) {
  253. curLens.css({
  254. 'background-color': 'rgba(f,f,f,.5)'
  255. });
  256. } else {
  257. curLens.get(0).style.backgroundPosition = '-' +
  258. magnifierOptions.lensBgX + 'px -' +
  259. magnifierOptions.lensBgY + 'px';
  260. }
  261. var handler = magnifierOptions.onthumbmove;
  262. if (handler !== null) {
  263. handler({
  264. thumb: curThumb,
  265. lens: curLens,
  266. large: curLarge,
  267. x: pos.x,
  268. y: pos.y
  269. });
  270. }
  271. }
  272. status = magnifierOptions.status;
  273. }
  274. }
  275. function setThumbData(mainImage, mainImageData) {
  276. var thumbBounds = mainImage.getBoundingClientRect(),
  277. w = 0,
  278. h = 0;
  279. mainImageData.x = Math.round(thumbBounds.left);
  280. mainImageData.y = Math.round(thumbBounds.top);
  281. mainImageData.w = Math.round(thumbBounds.right - mainImageData.x);
  282. mainImageData.h = Math.round(thumbBounds.bottom - mainImageData.y);
  283. if (mainImageData.mode === 'inside') {
  284. w = mainImageData.w;
  285. h = mainImageData.h;
  286. } else {
  287. w = mainImageData.largeWrapperW;
  288. h = mainImageData.largeWrapperH;
  289. }
  290. mainImageData.largeImgInMagnifyLensWidth = Math.round(mainImageData.zoom * w);
  291. mainImageData.largeImgInMagnifyLensHeight = Math.round(mainImageData.zoom * h);
  292. mainImageData.lensW = Math.round(mainImageData.w / mainImageData.zoom);
  293. mainImageData.lensH = Math.round(mainImageData.h / mainImageData.zoom);
  294. }
  295. function _init($box, options) {
  296. var opts = {};
  297. if (options.thumb === undefined) {
  298. return false;
  299. }
  300. $thumb = $box.find(options.thumb);
  301. if ($thumb.length) {
  302. for (var key in options) {
  303. opts[key] = options[key];
  304. }
  305. opts.thumb = $thumb;
  306. enabled = opts.enabled;
  307. if (_toBoolean(enabled)) {
  308. $magnifierPreview.show().css('display', '');
  309. $magnifierPreview.addClass(MagnifyCls.magnifyHidden);
  310. set(opts);
  311. } else {
  312. $magnifierPreview.empty().hide();
  313. }
  314. }
  315. return that;
  316. }
  317. function hoverEvents(thumb) {
  318. $(thumb).on('mouseover', function (e) {
  319. if (showWrapper) {
  320. if (magnifierOptions.status !== 0) {
  321. onThumbLeave();
  322. }
  323. handleEvents(e);
  324. isOverThumb = inBounds;
  325. }
  326. }).trigger('mouseover');
  327. }
  328. function clickEvents(thumb) {
  329. $(thumb).on('click', function (e) {
  330. if (showWrapper) {
  331. if (!isOverThumb) {
  332. if (magnifierOptions.status !== 0) {
  333. onThumbLeave();
  334. }
  335. handleEvents(e);
  336. isOverThumb = true;
  337. }
  338. }
  339. });
  340. }
  341. function bindEvents(eType, thumb) {
  342. switch (eType) {
  343. case 'hover':
  344. hoverEvents(thumb);
  345. break;
  346. case 'click':
  347. clickEvents(thumb);
  348. break;
  349. }
  350. }
  351. function handleEvents(e) {
  352. var src = e.target;
  353. curIdx = src.id;
  354. curThumb = src;
  355. onThumbEnter(src);
  356. setThumbData(curThumb, magnifierOptions);
  357. pos.x = e.clientX;
  358. pos.y = e.clientY;
  359. getMousePos();
  360. move();
  361. var handler = magnifierOptions.onthumbenter;
  362. if (handler !== null) {
  363. handler({
  364. thumb: curThumb,
  365. lens: curLens,
  366. large: curLarge,
  367. x: pos.x,
  368. y: pos.y
  369. });
  370. }
  371. }
  372. function set(options) {
  373. if (data[options.thumb.id] !== undefined) {
  374. curThumb = options.thumb;
  375. return false;
  376. }
  377. var thumbObj = new Image(),
  378. largeObj = new Image(),
  379. $thumb = options.thumb,
  380. thumb = $thumb.get(0),
  381. idx = thumb.id,
  382. largeUrl,
  383. largeWrapper = $(options.largeWrapper),
  384. zoom = options.zoom || thumb.getAttribute('data-zoom') || gZoom,
  385. zoomMin = options.zoomMin || gZoomMin,
  386. zoomMax = options.zoomMax || gZoomMax,
  387. mode = options.mode || thumb.getAttribute('data-mode') || gMode,
  388. eventType = options.eventType || thumb.getAttribute('data-eventType') || gEventType,
  389. onthumbenter = options.onthumbenter !== undefined ?
  390. options.onthumbenter
  391. : magnifierOptions.onthumbenter,
  392. onthumbleave = options.onthumbleave !== undefined ?
  393. options.onthumbleave
  394. : magnifierOptions.onthumbleave,
  395. onthumbmove = options.onthumbmove !== undefined ?
  396. options.onthumbmove
  397. : magnifierOptions.onthumbmove;
  398. largeUrl = $thumb.data('original') || customUserOptions.full || $thumb.attr('src');
  399. if (thumb.id === '') {
  400. idx = thumb.id = 'magnifier-item-' + gId;
  401. gId += 1;
  402. }
  403. createLens(thumb, idx);
  404. if (options.width) {
  405. largeWrapper.width(options.width);
  406. }
  407. if (options.height) {
  408. largeWrapper.height(options.height);
  409. }
  410. if (options.top) {
  411. if (typeof options.top == 'function') {
  412. var top = options.top() + 'px';
  413. } else {
  414. var top = options.top + 'px';
  415. }
  416. if (largeWrapper.length) {
  417. largeWrapper[0].style.top = top.replace('%px', '%');
  418. }
  419. }
  420. if (options.left) {
  421. if (typeof options.left == 'function') {
  422. var left = options.left() + 'px';
  423. } else {
  424. var left = options.left + 'px';
  425. }
  426. if (largeWrapper.length) {
  427. largeWrapper[0].style.left = left.replace('%px', '%');
  428. }
  429. }
  430. data[idx] = {
  431. zoom: zoom,
  432. zoomMin: zoomMin,
  433. zoomMax: zoomMax,
  434. mode: mode,
  435. eventType: eventType,
  436. thumbCssClass: thumb.className,
  437. zoomAttached: false,
  438. status: 0,
  439. largeUrl: largeUrl,
  440. largeWrapperId: mode === 'outside' ? largeWrapper.attr('id') : null,
  441. largeWrapperW: mode === 'outside' ? largeWrapper.width() : null,
  442. largeWrapperH: mode === 'outside' ? largeWrapper.height() : null,
  443. onthumbenter: onthumbenter,
  444. onthumbleave: onthumbleave,
  445. onthumbmove: onthumbmove
  446. };
  447. paddingX = ($thumb.parent().width() - $thumb.width()) / 2;
  448. paddingY = ($thumb.parent().height() - $thumb.height()) / 2;
  449. showWrapper = false;
  450. $(thumbObj).on('load', function () {
  451. data[idx].status = 1;
  452. $(largeObj).on('load', function () {
  453. if (largeObj.width > largeWrapper.width() || largeObj.height > largeWrapper.height()) {
  454. showWrapper = true;
  455. bindEvents(eventType, thumb);
  456. data[idx].status = 2;
  457. if (largeObj.width > largeObj.height) {
  458. data[idx].zoom = largeObj.width / largeWrapper.width();
  459. } else {
  460. data[idx].zoom = largeObj.height / largeWrapper.height();
  461. }
  462. setThumbData(thumb, data[idx]);
  463. updateLensOnLoad(idx, thumb, largeObj, largeWrapper);
  464. }
  465. });
  466. largeObj.src = data[idx].largeUrl;
  467. });
  468. thumbObj.src = thumb.src;
  469. }
  470. /**
  471. * Hide magnifier when mouse exceeds image bounds.
  472. */
  473. function onMouseLeave() {
  474. onThumbLeave();
  475. isOverThumb = false;
  476. $magnifierPreview.addClass(MagnifyCls.magnifyHidden);
  477. }
  478. function onMousemove(e) {
  479. pos.x = e.clientX;
  480. pos.y = e.clientY;
  481. getMousePos();
  482. if (gEventType === 'hover') {
  483. isOverThumb = inBounds;
  484. }
  485. if (inBounds && isOverThumb && gMode === 'outside') {
  486. $magnifierPreview.removeClass(MagnifyCls.magnifyHidden);
  487. move();
  488. }
  489. }
  490. function onScroll() {
  491. if (curThumb !== null) {
  492. setThumbData(curThumb, magnifierOptions);
  493. }
  494. }
  495. $(window).on('scroll', onScroll);
  496. $(window).resize(function () {
  497. _init($box, customUserOptions);
  498. });
  499. $box.on('mousemove', onMousemove);
  500. $box.on('mouseleave', onMouseLeave);
  501. _init($box, customUserOptions);
  502. }
  503. }(jQuery));