You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

516 lines
14 KiB

6 years ago
  1. /*jslint unparam: true, browser: true, indent: 2 */
  2. ;(function ($, window, document, undefined) {
  3. 'use strict';
  4. Foundation.libs.clearing = {
  5. name : 'clearing',
  6. version: '4.3.1',
  7. settings : {
  8. templates : {
  9. viewing : '<a href="#" class="clearing-close">&times;</a>' +
  10. '<div class="visible-img" style="display: none"><img src="//:0">' +
  11. '<p class="clearing-caption"></p><a href="#" class="clearing-main-prev"><span></span></a>' +
  12. '<a href="#" class="clearing-main-next"><span></span></a></div>'
  13. },
  14. // comma delimited list of selectors that, on click, will close clearing,
  15. // add 'div.clearing-blackout, div.visible-img' to close on background click
  16. close_selectors : '.clearing-close',
  17. // event initializers and locks
  18. init : false,
  19. locked : false
  20. },
  21. init : function (scope, method, options) {
  22. var self = this;
  23. Foundation.inherit(this, 'set_data get_data remove_data throttle data_options');
  24. if (typeof method === 'object') {
  25. options = $.extend(true, this.settings, method);
  26. }
  27. if (typeof method !== 'string') {
  28. $(this.scope).find('ul[data-clearing]').each(function () {
  29. var $el = $(this),
  30. options = options || {},
  31. lis = $el.find('li'),
  32. settings = self.get_data($el);
  33. if (!settings && lis.length > 0) {
  34. options.$parent = $el.parent();
  35. self.set_data($el, $.extend({}, self.settings, options, self.data_options($el)));
  36. self.assemble($el.find('li'));
  37. if (!self.settings.init) {
  38. self.events().swipe_events();
  39. }
  40. }
  41. });
  42. return this.settings.init;
  43. } else {
  44. // fire method
  45. return this[method].call(this, options);
  46. }
  47. },
  48. // event binding and initial setup
  49. events : function () {
  50. var self = this;
  51. $(this.scope)
  52. .on('click.fndtn.clearing', 'ul[data-clearing] li',
  53. function (e, current, target) {
  54. var current = current || $(this),
  55. target = target || current,
  56. next = current.next('li'),
  57. settings = self.get_data(current.parent()),
  58. image = $(e.target);
  59. e.preventDefault();
  60. if (!settings) self.init();
  61. // if clearing is open and the current image is
  62. // clicked, go to the next image in sequence
  63. if (target.hasClass('visible') &&
  64. current[0] === target[0] &&
  65. next.length > 0 && self.is_open(current)) {
  66. target = next;
  67. image = target.find('img');
  68. }
  69. // set current and target to the clicked li if not otherwise defined.
  70. self.open(image, current, target);
  71. self.update_paddles(target);
  72. })
  73. .on('click.fndtn.clearing', '.clearing-main-next',
  74. function (e) { this.nav(e, 'next') }.bind(this))
  75. .on('click.fndtn.clearing', '.clearing-main-prev',
  76. function (e) { this.nav(e, 'prev') }.bind(this))
  77. .on('click.fndtn.clearing', this.settings.close_selectors,
  78. function (e) { Foundation.libs.clearing.close(e, this) })
  79. .on('keydown.fndtn.clearing',
  80. function (e) { this.keydown(e) }.bind(this));
  81. $(window).on('resize.fndtn.clearing',
  82. function () { this.resize() }.bind(this));
  83. this.settings.init = true;
  84. return this;
  85. },
  86. swipe_events : function () {
  87. var self = this;
  88. $(this.scope)
  89. .on('touchstart.fndtn.clearing', '.visible-img', function(e) {
  90. if (!e.touches) { e = e.originalEvent; }
  91. var data = {
  92. start_page_x: e.touches[0].pageX,
  93. start_page_y: e.touches[0].pageY,
  94. start_time: (new Date()).getTime(),
  95. delta_x: 0,
  96. is_scrolling: undefined
  97. };
  98. $(this).data('swipe-transition', data);
  99. e.stopPropagation();
  100. })
  101. .on('touchmove.fndtn.clearing', '.visible-img', function(e) {
  102. if (!e.touches) { e = e.originalEvent; }
  103. // Ignore pinch/zoom events
  104. if(e.touches.length > 1 || e.scale && e.scale !== 1) return;
  105. var data = $(this).data('swipe-transition');
  106. if (typeof data === 'undefined') {
  107. data = {};
  108. }
  109. data.delta_x = e.touches[0].pageX - data.start_page_x;
  110. if ( typeof data.is_scrolling === 'undefined') {
  111. data.is_scrolling = !!( data.is_scrolling || Math.abs(data.delta_x) < Math.abs(e.touches[0].pageY - data.start_page_y) );
  112. }
  113. if (!data.is_scrolling && !data.active) {
  114. e.preventDefault();
  115. var direction = (data.delta_x < 0) ? 'next' : 'prev';
  116. data.active = true;
  117. self.nav(e, direction);
  118. }
  119. })
  120. .on('touchend.fndtn.clearing', '.visible-img', function(e) {
  121. $(this).data('swipe-transition', {});
  122. e.stopPropagation();
  123. });
  124. },
  125. assemble : function ($li) {
  126. var $el = $li.parent();
  127. $el.after('<div id="foundationClearingHolder"></div>');
  128. var holder = $('#foundationClearingHolder'),
  129. settings = this.get_data($el),
  130. grid = $el.detach(),
  131. data = {
  132. grid: '<div class="carousel">' + this.outerHTML(grid[0]) + '</div>',
  133. viewing: settings.templates.viewing
  134. },
  135. wrapper = '<div class="clearing-assembled"><div>' + data.viewing +
  136. data.grid + '</div></div>';
  137. return holder.after(wrapper).remove();
  138. },
  139. // event callbacks
  140. open : function ($image, current, target) {
  141. var root = target.closest('.clearing-assembled'),
  142. container = root.find('div').first(),
  143. visible_image = container.find('.visible-img'),
  144. image = visible_image.find('img').not($image);
  145. if (!this.locked()) {
  146. // set the image to the selected thumbnail
  147. image
  148. .attr('src', this.load($image))
  149. .css('visibility', 'hidden');
  150. this.loaded(image, function () {
  151. image.css('visibility', 'visible');
  152. // toggle the gallery
  153. root.addClass('clearing-blackout');
  154. container.addClass('clearing-container');
  155. visible_image.show();
  156. this.fix_height(target)
  157. .caption(visible_image.find('.clearing-caption'), $image)
  158. .center(image)
  159. .shift(current, target, function () {
  160. target.siblings().removeClass('visible');
  161. target.addClass('visible');
  162. });
  163. }.bind(this));
  164. }
  165. },
  166. close : function (e, el) {
  167. e.preventDefault();
  168. var root = (function (target) {
  169. if (/blackout/.test(target.selector)) {
  170. return target;
  171. } else {
  172. return target.closest('.clearing-blackout');
  173. }
  174. }($(el))), container, visible_image;
  175. if (el === e.target && root) {
  176. container = root.find('div').first();
  177. visible_image = container.find('.visible-img');
  178. this.settings.prev_index = 0;
  179. root.find('ul[data-clearing]')
  180. .attr('style', '').closest('.clearing-blackout')
  181. .removeClass('clearing-blackout');
  182. container.removeClass('clearing-container');
  183. visible_image.hide();
  184. }
  185. return false;
  186. },
  187. is_open : function (current) {
  188. return current.parent().attr('style').length > 0;
  189. },
  190. keydown : function (e) {
  191. var clearing = $('.clearing-blackout').find('ul[data-clearing]');
  192. if (e.which === 39) this.go(clearing, 'next');
  193. if (e.which === 37) this.go(clearing, 'prev');
  194. if (e.which === 27) $('a.clearing-close').trigger('click');
  195. },
  196. nav : function (e, direction) {
  197. var clearing = $('.clearing-blackout').find('ul[data-clearing]');
  198. e.preventDefault();
  199. this.go(clearing, direction);
  200. },
  201. resize : function () {
  202. var image = $('.clearing-blackout .visible-img').find('img');
  203. if (image.length) {
  204. this.center(image);
  205. }
  206. },
  207. // visual adjustments
  208. fix_height : function (target) {
  209. var lis = target.parent().children(),
  210. self = this;
  211. lis.each(function () {
  212. var li = $(this),
  213. image = li.find('img');
  214. if (li.height() > self.outerHeight(image)) {
  215. li.addClass('fix-height');
  216. }
  217. })
  218. .closest('ul')
  219. .width(lis.length * 100 + '%');
  220. return this;
  221. },
  222. update_paddles : function (target) {
  223. var visible_image = target
  224. .closest('.carousel')
  225. .siblings('.visible-img');
  226. if (target.next().length > 0) {
  227. visible_image
  228. .find('.clearing-main-next')
  229. .removeClass('disabled');
  230. } else {
  231. visible_image
  232. .find('.clearing-main-next')
  233. .addClass('disabled');
  234. }
  235. if (target.prev().length > 0) {
  236. visible_image
  237. .find('.clearing-main-prev')
  238. .removeClass('disabled');
  239. } else {
  240. visible_image
  241. .find('.clearing-main-prev')
  242. .addClass('disabled');
  243. }
  244. },
  245. center : function (target) {
  246. if (!this.rtl) {
  247. target.css({
  248. marginLeft : -(this.outerWidth(target) / 2),
  249. marginTop : -(this.outerHeight(target) / 2)
  250. });
  251. } else {
  252. target.css({
  253. marginRight : -(this.outerWidth(target) / 2),
  254. marginTop : -(this.outerHeight(target) / 2)
  255. });
  256. }
  257. return this;
  258. },
  259. // image loading and preloading
  260. load : function ($image) {
  261. if ($image[0].nodeName === "A") {
  262. var href = $image.attr('href');
  263. } else {
  264. var href = $image.parent().attr('href');
  265. }
  266. this.preload($image);
  267. if (href) return href;
  268. return $image.attr('src');
  269. },
  270. preload : function ($image) {
  271. this
  272. .img($image.closest('li').next())
  273. .img($image.closest('li').prev());
  274. },
  275. loaded : function (image, callback) {
  276. // based on jquery.imageready.js
  277. // @weblinc, @jsantell, (c) 2012
  278. function loaded () {
  279. callback();
  280. }
  281. function bindLoad () {
  282. this.one('load', loaded);
  283. if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
  284. var src = this.attr( 'src' ),
  285. param = src.match( /\?/ ) ? '&' : '?';
  286. param += 'random=' + (new Date()).getTime();
  287. this.attr('src', src + param);
  288. }
  289. }
  290. if (!image.attr('src')) {
  291. loaded();
  292. return;
  293. }
  294. if (image[0].complete || image[0].readyState === 4) {
  295. loaded();
  296. } else {
  297. bindLoad.call(image);
  298. }
  299. },
  300. img : function (img) {
  301. if (img.length) {
  302. var new_img = new Image(),
  303. new_a = img.find('a');
  304. if (new_a.length) {
  305. new_img.src = new_a.attr('href');
  306. } else {
  307. new_img.src = img.find('img').attr('src');
  308. }
  309. }
  310. return this;
  311. },
  312. // image caption
  313. caption : function (container, $image) {
  314. var caption = $image.data('caption');
  315. if (caption) {
  316. container
  317. .html(caption)
  318. .show();
  319. } else {
  320. container
  321. .text('')
  322. .hide();
  323. }
  324. return this;
  325. },
  326. // directional methods
  327. go : function ($ul, direction) {
  328. var current = $ul.find('.visible'),
  329. target = current[direction]();
  330. if (target.length) {
  331. target
  332. .find('img')
  333. .trigger('click', [current, target]);
  334. }
  335. },
  336. shift : function (current, target, callback) {
  337. var clearing = target.parent(),
  338. old_index = this.settings.prev_index || target.index(),
  339. direction = this.direction(clearing, current, target),
  340. left = parseInt(clearing.css('left'), 10),
  341. width = this.outerWidth(target),
  342. skip_shift;
  343. // we use jQuery animate instead of CSS transitions because we
  344. // need a callback to unlock the next animation
  345. if (target.index() !== old_index && !/skip/.test(direction)){
  346. if (/left/.test(direction)) {
  347. this.lock();
  348. clearing.animate({left : left + width}, 300, this.unlock());
  349. } else if (/right/.test(direction)) {
  350. this.lock();
  351. clearing.animate({left : left - width}, 300, this.unlock());
  352. }
  353. } else if (/skip/.test(direction)) {
  354. // the target image is not adjacent to the current image, so
  355. // do we scroll right or not
  356. skip_shift = target.index() - this.settings.up_count;
  357. this.lock();
  358. if (skip_shift > 0) {
  359. clearing.animate({left : -(skip_shift * width)}, 300, this.unlock());
  360. } else {
  361. clearing.animate({left : 0}, 300, this.unlock());
  362. }
  363. }
  364. callback();
  365. },
  366. direction : function ($el, current, target) {
  367. var lis = $el.find('li'),
  368. li_width = this.outerWidth(lis) + (this.outerWidth(lis) / 4),
  369. up_count = Math.floor(this.outerWidth($('.clearing-container')) / li_width) - 1,
  370. target_index = lis.index(target),
  371. response;
  372. this.settings.up_count = up_count;
  373. if (this.adjacent(this.settings.prev_index, target_index)) {
  374. if ((target_index > up_count)
  375. && target_index > this.settings.prev_index) {
  376. response = 'right';
  377. } else if ((target_index > up_count - 1)
  378. && target_index <= this.settings.prev_index) {
  379. response = 'left';
  380. } else {
  381. response = false;
  382. }
  383. } else {
  384. response = 'skip';
  385. }
  386. this.settings.prev_index = target_index;
  387. return response;
  388. },
  389. adjacent : function (current_index, target_index) {
  390. for (var i = target_index + 1; i >= target_index - 1; i--) {
  391. if (i === current_index) return true;
  392. }
  393. return false;
  394. },
  395. // lock management
  396. lock : function () {
  397. this.settings.locked = true;
  398. },
  399. unlock : function () {
  400. this.settings.locked = false;
  401. },
  402. locked : function () {
  403. return this.settings.locked;
  404. },
  405. // plugin management/browser quirks
  406. outerHTML : function (el) {
  407. // support FireFox < 11
  408. return el.outerHTML || new XMLSerializer().serializeToString(el);
  409. },
  410. off : function () {
  411. $(this.scope).off('.fndtn.clearing');
  412. $(window).off('.fndtn.clearing');
  413. this.remove_data(); // empty settings cache
  414. this.settings.init = false;
  415. },
  416. reflow : function () {
  417. this.init();
  418. }
  419. };
  420. }(Foundation.zj, this, this.document));