Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

564 строки
21 KiB

  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. define([
  6. 'jquery',
  7. 'fotorama/fotorama',
  8. 'underscore',
  9. 'matchMedia',
  10. 'mage/template',
  11. 'text!mage/gallery/gallery.html',
  12. 'uiClass',
  13. 'mage/translate'
  14. ], function ($, fotorama, _, mediaCheck, template, galleryTpl, Class, $t) {
  15. 'use strict';
  16. /**
  17. * Retrieves index if the main item.
  18. * @param {Array.<Object>} data - Set of gallery items.
  19. */
  20. var getMainImageIndex = function (data) {
  21. var mainIndex;
  22. if (_.every(data, function (item) {
  23. return _.isObject(item);
  24. })
  25. ) {
  26. mainIndex = _.findIndex(data, function (item) {
  27. return item.isMain;
  28. });
  29. }
  30. return mainIndex > 0 ? mainIndex : 0;
  31. },
  32. /**
  33. * Helper for parse translate property
  34. *
  35. * @param {Element} el - el that to parse
  36. * @returns {Array} - array of properties.
  37. */
  38. getTranslate = function (el) {
  39. var slideTransform = $(el).attr('style').split(';');
  40. slideTransform = $.map(slideTransform, function (style) {
  41. style = style.trim();
  42. if (style.startsWith('transform: translate3d')) {
  43. return style.match(/transform: translate3d\((.+)px,(.+)px,(.+)px\)/);
  44. }
  45. return false;
  46. });
  47. return slideTransform.filter(Boolean);
  48. },
  49. /**
  50. * @param {*} str
  51. * @return {*}
  52. * @private
  53. */
  54. _toNumber = function (str) {
  55. var type = typeof str;
  56. if (type === 'string') {
  57. return parseInt(str); //eslint-disable-line radix
  58. }
  59. return str;
  60. };
  61. return Class.extend({
  62. defaults: {
  63. settings: {},
  64. config: {},
  65. startConfig: {}
  66. },
  67. /**
  68. * Checks if device has touch interface.
  69. * @return {Boolean} The result of searching touch events on device.
  70. */
  71. isTouchEnabled: (function () {
  72. return 'ontouchstart' in document.documentElement;
  73. })(),
  74. /**
  75. * Initializes gallery.
  76. * @param {Object} config - Gallery configuration.
  77. * @param {String} element - String selector of gallery DOM element.
  78. */
  79. initialize: function (config, element) {
  80. var self = this;
  81. this._super();
  82. _.bindAll(this,
  83. '_focusSwitcher'
  84. );
  85. /*turn off arrows for touch devices*/
  86. if (this.isTouchEnabled) {
  87. config.options.arrows = false;
  88. if (config.fullscreen) {
  89. config.fullscreen.arrows = false;
  90. }
  91. }
  92. config.options.width = _toNumber(config.options.width);
  93. config.options.height = _toNumber(config.options.height);
  94. config.options.thumbwidth = _toNumber(config.options.thumbwidth);
  95. config.options.thumbheight = _toNumber(config.options.thumbheight);
  96. config.options.swipe = true;
  97. this.config = config;
  98. this.settings = {
  99. $element: $(element),
  100. $pageWrapper: $('body>.page-wrapper'),
  101. currentConfig: config,
  102. defaultConfig: _.clone(config),
  103. fullscreenConfig: _.clone(config.fullscreen),
  104. breakpoints: config.breakpoints,
  105. activeBreakpoint: {},
  106. fotoramaApi: null,
  107. isFullscreen: false,
  108. api: null,
  109. data: _.clone(config.data)
  110. };
  111. config.options.ratio = config.options.width / config.options.height;
  112. config.options.height = null;
  113. $.extend(true, this.startConfig, config);
  114. this.initGallery();
  115. this.initApi();
  116. this.setupBreakpoints();
  117. this.initFullscreenSettings();
  118. this.settings.$element.on('click', '.fotorama__stage__frame', function () {
  119. if (
  120. !$(this).parents('.fotorama__shadows--left, .fotorama__shadows--right').length &&
  121. !$(this).hasClass('fotorama-video-container')
  122. ) {
  123. self.openFullScreen();
  124. }
  125. });
  126. if (this.isTouchEnabled && this.settings.isFullscreen) {
  127. this.settings.$element.on('tap', '.fotorama__stage__frame', function () {
  128. var translate = getTranslate($(this).parents('.fotorama__stage__shaft'));
  129. if (translate[1] === '0' && !$(this).hasClass('fotorama-video-container')) {
  130. self.openFullScreen();
  131. self.settings.$pageWrapper.hide();
  132. }
  133. });
  134. }
  135. },
  136. /**
  137. * Open gallery fullscreen
  138. */
  139. openFullScreen: function () {
  140. this.settings.api.fotorama.requestFullScreen();
  141. this.settings.$fullscreenIcon.css({
  142. opacity: 1,
  143. visibility: 'visible',
  144. display: 'block'
  145. });
  146. },
  147. /**
  148. * Gallery fullscreen settings.
  149. */
  150. initFullscreenSettings: function () {
  151. var settings = this.settings,
  152. self = this;
  153. settings.$gallery = this.settings.$element.find('[data-gallery-role="gallery"]');
  154. settings.$fullscreenIcon = this.settings.$element.find('[data-gallery-role="fotorama__fullscreen-icon"]');
  155. settings.focusableStart = this.settings.$element.find('[data-gallery-role="fotorama__focusable-start"]');
  156. settings.focusableEnd = this.settings.$element.find('[data-gallery-role="fotorama__focusable-end"]');
  157. settings.closeIcon = this.settings.$element.find('[data-gallery-role="fotorama__fullscreen-icon"]');
  158. settings.fullscreenConfig.swipe = true;
  159. settings.$gallery.on('fotorama:fullscreenenter', function () {
  160. settings.closeIcon.show();
  161. settings.focusableStart.attr('tabindex', '0');
  162. settings.focusableEnd.attr('tabindex', '0');
  163. settings.focusableStart.on('focusin', self._focusSwitcher);
  164. settings.focusableEnd.on('focusin', self._focusSwitcher);
  165. settings.api.updateOptions(settings.defaultConfig.options, true);
  166. settings.api.updateOptions(settings.fullscreenConfig, true);
  167. if (!_.isEqual(settings.activeBreakpoint, {}) && settings.breakpoints) {
  168. settings.api.updateOptions(settings.activeBreakpoint.options, true);
  169. }
  170. settings.isFullscreen = true;
  171. });
  172. settings.$gallery.on('fotorama:fullscreenexit', function () {
  173. settings.closeIcon.hide();
  174. settings.focusableStart.attr('tabindex', '-1');
  175. settings.focusableEnd.attr('tabindex', '-1');
  176. settings.api.updateOptions(settings.defaultConfig.options, true);
  177. settings.focusableStart.off('focusin', this._focusSwitcher);
  178. settings.focusableEnd.off('focusin', this._focusSwitcher);
  179. settings.closeIcon.hide();
  180. if (!_.isEqual(settings.activeBreakpoint, {}) && settings.breakpoints) {
  181. settings.api.updateOptions(settings.activeBreakpoint.options, true);
  182. }
  183. settings.isFullscreen = false;
  184. settings.$element.data('gallery').updateOptions({
  185. swipe: true
  186. });
  187. });
  188. },
  189. /**
  190. * Switcher focus.
  191. */
  192. _focusSwitcher: function (e) {
  193. var target = $(e.target),
  194. settings = this.settings;
  195. if (target.is(settings.focusableStart)) {
  196. this._setFocus('start');
  197. } else if (target.is(settings.focusableEnd)) {
  198. this._setFocus('end');
  199. }
  200. },
  201. /**
  202. * Set focus to element.
  203. * @param {String} position - can be "start" and "end"
  204. * positions.
  205. * If position is "end" - sets focus to first
  206. * focusable element in modal window scope.
  207. * If position is "start" - sets focus to last
  208. * focusable element in modal window scope
  209. */
  210. _setFocus: function (position) {
  211. var settings = this.settings,
  212. focusableElements,
  213. infelicity;
  214. if (position === 'end') {
  215. settings.$gallery.find(settings.closeIcon).trigger('focus');
  216. } else if (position === 'start') {
  217. infelicity = 3; //Constant for find last focusable element
  218. focusableElements = settings.$gallery.find(':focusable');
  219. focusableElements.eq(focusableElements.length - infelicity).trigger('focus');
  220. }
  221. },
  222. /**
  223. * Initializes gallery with configuration options.
  224. */
  225. initGallery: function () {
  226. var breakpoints = {},
  227. settings = this.settings,
  228. config = this.config,
  229. tpl = template(galleryTpl, {
  230. next: $t('Next'),
  231. previous: $t('Previous')
  232. }),
  233. mainImageIndex,
  234. $element = settings.$element,
  235. $fotoramaElement,
  236. $fotoramaStage;
  237. if (settings.breakpoints) {
  238. _.each(_.values(settings.breakpoints), function (breakpoint) {
  239. var conditions;
  240. _.each(_.pairs(breakpoint.conditions), function (pair) {
  241. conditions = conditions ? conditions + ' and (' + pair[0] + ': ' + pair[1] + ')' :
  242. '(' + pair[0] + ': ' + pair[1] + ')';
  243. });
  244. breakpoints[conditions] = breakpoint.options;
  245. });
  246. settings.breakpoints = breakpoints;
  247. }
  248. _.extend(config, config.options,
  249. {
  250. options: undefined,
  251. click: false,
  252. breakpoints: null
  253. }
  254. );
  255. settings.currentConfig = config;
  256. $element
  257. .css('min-height', settings.$element.height())
  258. .append(tpl);
  259. $fotoramaElement = $element.find('[data-gallery-role="gallery"]');
  260. $fotoramaStage = $fotoramaElement.find('.fotorama__stage');
  261. $fotoramaStage.css('position', 'absolute');
  262. $fotoramaElement.fotorama(config);
  263. $fotoramaElement.find('.fotorama__stage__frame.fotorama__active')
  264. .one('f:load', function () {
  265. // Remove placeholder when main gallery image loads.
  266. $element.find('.gallery-placeholder__image').remove();
  267. $element
  268. .removeClass('_block-content-loading')
  269. .css('min-height', '');
  270. $fotoramaStage.css('position', '');
  271. });
  272. settings.$elementF = $fotoramaElement;
  273. settings.fotoramaApi = $fotoramaElement.data('fotorama');
  274. $.extend(true, config, this.startConfig);
  275. mainImageIndex = getMainImageIndex(config.data);
  276. if (mainImageIndex) {
  277. this.settings.fotoramaApi.show({
  278. index: mainImageIndex,
  279. time: 0
  280. });
  281. }
  282. },
  283. /**
  284. * Creates breakpoints for gallery.
  285. */
  286. setupBreakpoints: function () {
  287. var pairs,
  288. settings = this.settings,
  289. config = this.config,
  290. startConfig = this.startConfig,
  291. isInitialized = {},
  292. isTouchEnabled = this.isTouchEnabled;
  293. if (_.isObject(settings.breakpoints)) {
  294. pairs = _.pairs(settings.breakpoints);
  295. _.each(pairs, function (pair) {
  296. var mediaQuery = pair[0];
  297. isInitialized[mediaQuery] = false;
  298. mediaCheck({
  299. media: mediaQuery,
  300. /**
  301. * Is triggered when breakpoint enties.
  302. */
  303. entry: function () {
  304. $.extend(true, config, _.clone(startConfig));
  305. settings.api.updateOptions(settings.defaultConfig.options, true);
  306. if (settings.isFullscreen) {
  307. settings.api.updateOptions(settings.fullscreenConfig, true);
  308. }
  309. if (isTouchEnabled) {
  310. settings.breakpoints[mediaQuery].options.arrows = false;
  311. if (settings.breakpoints[mediaQuery].options.fullscreen) {
  312. settings.breakpoints[mediaQuery].options.fullscreen.arrows = false;
  313. }
  314. }
  315. settings.api.updateOptions(settings.breakpoints[mediaQuery].options, true);
  316. $.extend(true, config, settings.breakpoints[mediaQuery]);
  317. settings.activeBreakpoint = settings.breakpoints[mediaQuery];
  318. isInitialized[mediaQuery] = true;
  319. },
  320. /**
  321. * Is triggered when breakpoint exits.
  322. */
  323. exit: function () {
  324. if (isInitialized[mediaQuery]) {
  325. $.extend(true, config, _.clone(startConfig));
  326. settings.api.updateOptions(settings.defaultConfig.options, true);
  327. if (settings.isFullscreen) {
  328. settings.api.updateOptions(settings.fullscreenConfig, true);
  329. }
  330. settings.activeBreakpoint = {};
  331. } else {
  332. isInitialized[mediaQuery] = true;
  333. }
  334. }
  335. });
  336. });
  337. }
  338. },
  339. /**
  340. * Creates gallery's API.
  341. */
  342. initApi: function () {
  343. var settings = this.settings,
  344. config = this.config,
  345. api = {
  346. /**
  347. * Contains fotorama's API methods.
  348. */
  349. fotorama: settings.fotoramaApi,
  350. /**
  351. * Displays the last image on preview.
  352. */
  353. last: function () {
  354. settings.fotoramaApi.show('>>');
  355. },
  356. /**
  357. * Displays the first image on preview.
  358. */
  359. first: function () {
  360. settings.fotoramaApi.show('<<');
  361. },
  362. /**
  363. * Displays previous element on preview.
  364. */
  365. prev: function () {
  366. settings.fotoramaApi.show('<');
  367. },
  368. /**
  369. * Displays next element on preview.
  370. */
  371. next: function () {
  372. settings.fotoramaApi.show('>');
  373. },
  374. /**
  375. * Displays image with appropriate count number on preview.
  376. * @param {Number} index - Number of image that should be displayed.
  377. */
  378. seek: function (index) {
  379. if (_.isNumber(index) && index !== 0) {
  380. if (index > 0) {
  381. index -= 1;
  382. }
  383. settings.fotoramaApi.show(index);
  384. }
  385. },
  386. /**
  387. * Updates gallery with new set of options.
  388. * @param {Object} configuration - Standart gallery configuration object.
  389. * @param {Boolean} isInternal - Is this function called via breakpoints.
  390. */
  391. updateOptions: function (configuration, isInternal) {
  392. var $selectable = $('a[href], area[href], input, select, ' +
  393. 'textarea, button, iframe, object, embed, *[tabindex], *[contenteditable]')
  394. .not('[tabindex=-1], [disabled], :hidden'),
  395. $focus = $(':focus'),
  396. index;
  397. if (_.isObject(configuration)) {
  398. //Saves index of focus
  399. $selectable.each(function (number) {
  400. if ($(this).is($focus)) {
  401. index = number;
  402. }
  403. });
  404. if (this.isTouchEnabled) {
  405. configuration.arrows = false;
  406. }
  407. configuration.click = false;
  408. configuration.breakpoints = null;
  409. if (!isInternal) {
  410. !_.isEqual(settings.activeBreakpoint, {} && settings.breakpoints) ?
  411. $.extend(true, settings.activeBreakpoint.options, configuration) :
  412. settings.isFullscreen ?
  413. $.extend(true, settings.fullscreenConfig, configuration) :
  414. $.extend(true, settings.defaultConfig.options, configuration);
  415. }
  416. $.extend(true, settings.currentConfig.options, configuration);
  417. settings.fotoramaApi.setOptions(settings.currentConfig.options);
  418. if (_.isNumber(index)) {
  419. $selectable.eq(index).trigger('focus');
  420. }
  421. }
  422. },
  423. /**
  424. * Updates gallery with specific set of items.
  425. * @param {Array.<Object>} data - Set of gallery items to update.
  426. */
  427. updateData: function (data) {
  428. var mainImageIndex;
  429. if (_.isArray(data)) {
  430. settings.fotoramaApi.load(data);
  431. mainImageIndex = getMainImageIndex(data);
  432. if (settings.fotoramaApi.activeIndex !== mainImageIndex) {
  433. settings.fotoramaApi.show({
  434. index: mainImageIndex,
  435. time: 0
  436. });
  437. }
  438. $.extend(false, settings, {
  439. data: data,
  440. defaultConfig: data
  441. });
  442. $.extend(false, config, {
  443. data: data
  444. });
  445. }
  446. },
  447. /**
  448. * Returns current images list
  449. *
  450. * @returns {Array}
  451. */
  452. returnCurrentImages: function () {
  453. var images = [];
  454. _.each(this.fotorama.data, function (item) {
  455. images.push(_.omit(item, '$navThumbFrame', '$navDotFrame', '$stageFrame', 'labelledby'));
  456. });
  457. return images;
  458. },
  459. /**
  460. * Updates gallery data partially by index
  461. * @param {Number} index - Index of image in data array to be updated.
  462. * @param {Object} item - Standart gallery image object.
  463. *
  464. */
  465. updateDataByIndex: function (index, item) {
  466. settings.fotoramaApi.spliceByIndex(index, item);
  467. }
  468. };
  469. settings.$element.data('gallery', api);
  470. settings.api = settings.$element.data('gallery');
  471. settings.$element.trigger('gallery:loaded');
  472. }
  473. });
  474. });