foundation.clearing.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. ;(function ($, window, document, undefined) {
  2. 'use strict';
  3. Foundation.libs.clearing = {
  4. name : 'clearing',
  5. version : '5.5.3',
  6. settings : {
  7. templates : {
  8. viewing : '<a href="#" class="clearing-close">&times;</a>' +
  9. '<div class="visible-img" style="display: none"><div class="clearing-touch-label"></div><img src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D" alt="" />' +
  10. '<p class="clearing-caption"></p><a href="#" class="clearing-main-prev"><span></span></a>' +
  11. '<a href="#" class="clearing-main-next"><span></span></a></div>' +
  12. '<img class="clearing-preload-next" style="display: none" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D" alt="" />' +
  13. '<img class="clearing-preload-prev" style="display: none" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D" alt="" />'
  14. },
  15. // comma delimited list of selectors that, on click, will close clearing,
  16. // add 'div.clearing-blackout, div.visible-img' to close on background click
  17. close_selectors : '.clearing-close, div.clearing-blackout',
  18. // Default to the entire li element.
  19. open_selectors : '',
  20. // Image will be skipped in carousel.
  21. skip_selector : '',
  22. touch_label : '',
  23. // event initializer and locks
  24. init : false,
  25. locked : false
  26. },
  27. init : function (scope, method, options) {
  28. var self = this;
  29. Foundation.inherit(this, 'throttle image_loaded');
  30. this.bindings(method, options);
  31. if (self.S(this.scope).is('[' + this.attr_name() + ']')) {
  32. this.assemble(self.S('li', this.scope));
  33. } else {
  34. self.S('[' + this.attr_name() + ']', this.scope).each(function () {
  35. self.assemble(self.S('li', this));
  36. });
  37. }
  38. },
  39. events : function (scope) {
  40. var self = this,
  41. S = self.S,
  42. $scroll_container = $('.scroll-container');
  43. if ($scroll_container.length > 0) {
  44. this.scope = $scroll_container;
  45. }
  46. S(this.scope)
  47. .off('.clearing')
  48. .on('click.fndtn.clearing', 'ul[' + this.attr_name() + '] li ' + this.settings.open_selectors,
  49. function (e, current, target) {
  50. var current = current || S(this),
  51. target = target || current,
  52. next = current.next('li'),
  53. settings = current.closest('[' + self.attr_name() + ']').data(self.attr_name(true) + '-init'),
  54. image = S(e.target);
  55. e.preventDefault();
  56. if (!settings) {
  57. self.init();
  58. settings = current.closest('[' + self.attr_name() + ']').data(self.attr_name(true) + '-init');
  59. }
  60. // if clearing is open and the current image is
  61. // clicked, go to the next image in sequence
  62. if (target.hasClass('visible') &&
  63. current[0] === target[0] &&
  64. next.length > 0 && self.is_open(current)) {
  65. target = next;
  66. image = S('img', target);
  67. }
  68. // set current and target to the clicked li if not otherwise defined.
  69. self.open(image, current, target);
  70. self.update_paddles(target);
  71. })
  72. .on('click.fndtn.clearing', '.clearing-main-next',
  73. function (e) { self.nav(e, 'next') })
  74. .on('click.fndtn.clearing', '.clearing-main-prev',
  75. function (e) { self.nav(e, 'prev') })
  76. .on('click.fndtn.clearing', this.settings.close_selectors,
  77. function (e) { Foundation.libs.clearing.close(e, this) });
  78. $(document).on('keydown.fndtn.clearing',
  79. function (e) { self.keydown(e) });
  80. S(window).off('.clearing').on('resize.fndtn.clearing',
  81. function () { self.resize() });
  82. this.swipe_events(scope);
  83. },
  84. swipe_events : function (scope) {
  85. var self = this,
  86. S = self.S;
  87. S(this.scope)
  88. .on('touchstart.fndtn.clearing', '.visible-img', function (e) {
  89. if (!e.touches) { e = e.originalEvent; }
  90. var data = {
  91. start_page_x : e.touches[0].pageX,
  92. start_page_y : e.touches[0].pageY,
  93. start_time : (new Date()).getTime(),
  94. delta_x : 0,
  95. is_scrolling : undefined
  96. };
  97. S(this).data('swipe-transition', data);
  98. e.stopPropagation();
  99. })
  100. .on('touchmove.fndtn.clearing', '.visible-img', function (e) {
  101. if (!e.touches) {
  102. e = e.originalEvent;
  103. }
  104. // Ignore pinch/zoom events
  105. if (e.touches.length > 1 || e.scale && e.scale !== 1) {
  106. return;
  107. }
  108. var data = S(this).data('swipe-transition');
  109. if (typeof data === 'undefined') {
  110. data = {};
  111. }
  112. data.delta_x = e.touches[0].pageX - data.start_page_x;
  113. if (Foundation.rtl) {
  114. data.delta_x = -data.delta_x;
  115. }
  116. if (typeof data.is_scrolling === 'undefined') {
  117. data.is_scrolling = !!( data.is_scrolling || Math.abs(data.delta_x) < Math.abs(e.touches[0].pageY - data.start_page_y) );
  118. }
  119. if (!data.is_scrolling && !data.active) {
  120. e.preventDefault();
  121. var direction = (data.delta_x < 0) ? 'next' : 'prev';
  122. data.active = true;
  123. self.nav(e, direction);
  124. }
  125. })
  126. .on('touchend.fndtn.clearing', '.visible-img', function (e) {
  127. S(this).data('swipe-transition', {});
  128. e.stopPropagation();
  129. });
  130. },
  131. assemble : function ($li) {
  132. var $el = $li.parent();
  133. if ($el.parent().hasClass('carousel')) {
  134. return;
  135. }
  136. $el.after('<div id="foundationClearingHolder"></div>');
  137. var grid = $el.detach(),
  138. grid_outerHTML = '';
  139. if (grid[0] == null) {
  140. return;
  141. } else {
  142. grid_outerHTML = grid[0].outerHTML;
  143. }
  144. var holder = this.S('#foundationClearingHolder'),
  145. settings = $el.data(this.attr_name(true) + '-init'),
  146. data = {
  147. grid : '<div class="carousel">' + grid_outerHTML + '</div>',
  148. viewing : settings.templates.viewing
  149. },
  150. wrapper = '<div class="clearing-assembled"><div>' + data.viewing +
  151. data.grid + '</div></div>',
  152. touch_label = this.settings.touch_label;
  153. if (Modernizr.touch) {
  154. wrapper = $(wrapper).find('.clearing-touch-label').html(touch_label).end();
  155. }
  156. holder.after(wrapper).remove();
  157. },
  158. open : function ($image, current, target) {
  159. var self = this,
  160. body = $(document.body),
  161. root = target.closest('.clearing-assembled'),
  162. container = self.S('div', root).first(),
  163. visible_image = self.S('.visible-img', container),
  164. image = self.S('img', visible_image).not($image),
  165. label = self.S('.clearing-touch-label', container),
  166. error = false,
  167. loaded = {};
  168. // Event to disable scrolling on touch devices when Clearing is activated
  169. $('body').on('touchmove', function (e) {
  170. e.preventDefault();
  171. });
  172. image.error(function () {
  173. error = true;
  174. });
  175. function startLoad() {
  176. setTimeout(function () {
  177. this.image_loaded(image, function () {
  178. if (image.outerWidth() === 1 && !error) {
  179. startLoad.call(this);
  180. } else {
  181. cb.call(this, image);
  182. }
  183. }.bind(this));
  184. }.bind(this), 100);
  185. }
  186. function cb (image) {
  187. var $image = $(image);
  188. $image.css('visibility', 'visible');
  189. $image.trigger('imageVisible');
  190. // toggle the gallery
  191. body.css('overflow', 'hidden');
  192. root.addClass('clearing-blackout');
  193. container.addClass('clearing-container');
  194. visible_image.show();
  195. this.fix_height(target)
  196. .caption(self.S('.clearing-caption', visible_image), self.S('img', target))
  197. .center_and_label(image, label)
  198. .shift(current, target, function () {
  199. target.closest('li').siblings().removeClass('visible');
  200. target.closest('li').addClass('visible');
  201. });
  202. visible_image.trigger('opened.fndtn.clearing')
  203. }
  204. if (!this.locked()) {
  205. visible_image.trigger('open.fndtn.clearing');
  206. // set the image to the selected thumbnail
  207. loaded = this.load($image);
  208. if (loaded.interchange) {
  209. image
  210. .attr('data-interchange', loaded.interchange)
  211. .foundation('interchange', 'reflow');
  212. } else {
  213. image
  214. .attr('src', loaded.src)
  215. .attr('data-interchange', '');
  216. }
  217. image.css('visibility', 'hidden');
  218. startLoad.call(this);
  219. }
  220. },
  221. close : function (e, el) {
  222. e.preventDefault();
  223. var root = (function (target) {
  224. if (/blackout/.test(target.selector)) {
  225. return target;
  226. } else {
  227. return target.closest('.clearing-blackout');
  228. }
  229. }($(el))),
  230. body = $(document.body), container, visible_image;
  231. if (el === e.target && root) {
  232. body.css('overflow', '');
  233. container = $('div', root).first();
  234. visible_image = $('.visible-img', container);
  235. visible_image.trigger('close.fndtn.clearing');
  236. this.settings.prev_index = 0;
  237. $('ul[' + this.attr_name() + ']', root)
  238. .attr('style', '').closest('.clearing-blackout')
  239. .removeClass('clearing-blackout');
  240. container.removeClass('clearing-container');
  241. visible_image.hide();
  242. visible_image.trigger('closed.fndtn.clearing');
  243. }
  244. // Event to re-enable scrolling on touch devices
  245. $('body').off('touchmove');
  246. return false;
  247. },
  248. is_open : function (current) {
  249. return current.parent().prop('style').length > 0;
  250. },
  251. keydown : function (e) {
  252. var clearing = $('.clearing-blackout ul[' + this.attr_name() + ']'),
  253. NEXT_KEY = this.rtl ? 37 : 39,
  254. PREV_KEY = this.rtl ? 39 : 37,
  255. ESC_KEY = 27;
  256. if (e.which === NEXT_KEY) {
  257. this.go(clearing, 'next');
  258. }
  259. if (e.which === PREV_KEY) {
  260. this.go(clearing, 'prev');
  261. }
  262. if (e.which === ESC_KEY) {
  263. this.S('a.clearing-close').trigger('click.fndtn.clearing');
  264. }
  265. },
  266. nav : function (e, direction) {
  267. var clearing = $('ul[' + this.attr_name() + ']', '.clearing-blackout');
  268. e.preventDefault();
  269. this.go(clearing, direction);
  270. },
  271. resize : function () {
  272. var image = $('img', '.clearing-blackout .visible-img'),
  273. label = $('.clearing-touch-label', '.clearing-blackout');
  274. if (image.length) {
  275. this.center_and_label(image, label);
  276. image.trigger('resized.fndtn.clearing')
  277. }
  278. },
  279. // visual adjustments
  280. fix_height : function (target) {
  281. var lis = target.parent().children(),
  282. self = this;
  283. lis.each(function () {
  284. var li = self.S(this),
  285. image = li.find('img');
  286. if (li.height() > image.outerHeight()) {
  287. li.addClass('fix-height');
  288. }
  289. })
  290. .closest('ul')
  291. .width(lis.length * 100 + '%');
  292. return this;
  293. },
  294. update_paddles : function (target) {
  295. target = target.closest('li');
  296. var visible_image = target
  297. .closest('.carousel')
  298. .siblings('.visible-img');
  299. if (target.next().length > 0) {
  300. this.S('.clearing-main-next', visible_image).removeClass('disabled');
  301. } else {
  302. this.S('.clearing-main-next', visible_image).addClass('disabled');
  303. }
  304. if (target.prev().length > 0) {
  305. this.S('.clearing-main-prev', visible_image).removeClass('disabled');
  306. } else {
  307. this.S('.clearing-main-prev', visible_image).addClass('disabled');
  308. }
  309. },
  310. center_and_label : function (target, label) {
  311. if (!this.rtl && label.length > 0) {
  312. label.css({
  313. marginLeft : -(label.outerWidth() / 2),
  314. marginTop : -(target.outerHeight() / 2)-label.outerHeight()-10
  315. });
  316. } else {
  317. label.css({
  318. marginRight : -(label.outerWidth() / 2),
  319. marginTop : -(target.outerHeight() / 2)-label.outerHeight()-10,
  320. left: 'auto',
  321. right: '50%'
  322. });
  323. }
  324. return this;
  325. },
  326. // image loading and preloading
  327. load : function ($image) {
  328. var href,
  329. interchange,
  330. closest_a;
  331. if ($image[0].nodeName === 'A') {
  332. href = $image.attr('href');
  333. interchange = $image.data('clearing-interchange');
  334. } else {
  335. closest_a = $image.closest('a');
  336. href = closest_a.attr('href');
  337. interchange = closest_a.data('clearing-interchange');
  338. }
  339. this.preload($image);
  340. return {
  341. 'src': href ? href : $image.attr('src'),
  342. 'interchange': href ? interchange : $image.data('clearing-interchange')
  343. }
  344. },
  345. preload : function ($image) {
  346. this
  347. .img($image.closest('li').next(), 'next')
  348. .img($image.closest('li').prev(), 'prev');
  349. },
  350. img : function (img, sibling_type) {
  351. if (img.length) {
  352. var preload_img = $('.clearing-preload-' + sibling_type),
  353. new_a = this.S('a', img),
  354. src,
  355. interchange,
  356. image;
  357. if (new_a.length) {
  358. src = new_a.attr('href');
  359. interchange = new_a.data('clearing-interchange');
  360. } else {
  361. image = this.S('img', img);
  362. src = image.attr('src');
  363. interchange = image.data('clearing-interchange');
  364. }
  365. if (interchange) {
  366. preload_img.attr('data-interchange', interchange);
  367. } else {
  368. preload_img.attr('src', src);
  369. preload_img.attr('data-interchange', '');
  370. }
  371. }
  372. return this;
  373. },
  374. // image caption
  375. caption : function (container, $image) {
  376. var caption = $image.attr('data-caption');
  377. if (caption) {
  378. var containerPlain = container.get(0);
  379. containerPlain.innerHTML = caption;
  380. container.show();
  381. } else {
  382. container
  383. .text('')
  384. .hide();
  385. }
  386. return this;
  387. },
  388. // directional methods
  389. go : function ($ul, direction) {
  390. var current = this.S('.visible', $ul),
  391. target = current[direction]();
  392. // Check for skip selector.
  393. if (this.settings.skip_selector && target.find(this.settings.skip_selector).length != 0) {
  394. target = target[direction]();
  395. }
  396. if (target.length) {
  397. this.S('img', target)
  398. .trigger('click.fndtn.clearing', [current, target])
  399. .trigger('change.fndtn.clearing');
  400. }
  401. },
  402. shift : function (current, target, callback) {
  403. var clearing = target.parent(),
  404. old_index = this.settings.prev_index || target.index(),
  405. direction = this.direction(clearing, current, target),
  406. dir = this.rtl ? 'right' : 'left',
  407. left = parseInt(clearing.css('left'), 10),
  408. width = target.outerWidth(),
  409. skip_shift;
  410. var dir_obj = {};
  411. // we use jQuery animate instead of CSS transitions because we
  412. // need a callback to unlock the next animation
  413. // needs support for RTL **
  414. if (target.index() !== old_index && !/skip/.test(direction)) {
  415. if (/left/.test(direction)) {
  416. this.lock();
  417. dir_obj[dir] = left + width;
  418. clearing.animate(dir_obj, 300, this.unlock());
  419. } else if (/right/.test(direction)) {
  420. this.lock();
  421. dir_obj[dir] = left - width;
  422. clearing.animate(dir_obj, 300, this.unlock());
  423. }
  424. } else if (/skip/.test(direction)) {
  425. // the target image is not adjacent to the current image, so
  426. // do we scroll right or not
  427. skip_shift = target.index() - this.settings.up_count;
  428. this.lock();
  429. if (skip_shift > 0) {
  430. dir_obj[dir] = -(skip_shift * width);
  431. clearing.animate(dir_obj, 300, this.unlock());
  432. } else {
  433. dir_obj[dir] = 0;
  434. clearing.animate(dir_obj, 300, this.unlock());
  435. }
  436. }
  437. callback();
  438. },
  439. direction : function ($el, current, target) {
  440. var lis = this.S('li', $el),
  441. li_width = lis.outerWidth() + (lis.outerWidth() / 4),
  442. up_count = Math.floor(this.S('.clearing-container').outerWidth() / li_width) - 1,
  443. target_index = lis.index(target),
  444. response;
  445. this.settings.up_count = up_count;
  446. if (this.adjacent(this.settings.prev_index, target_index)) {
  447. if ((target_index > up_count) && target_index > this.settings.prev_index) {
  448. response = 'right';
  449. } else if ((target_index > up_count - 1) && target_index <= this.settings.prev_index) {
  450. response = 'left';
  451. } else {
  452. response = false;
  453. }
  454. } else {
  455. response = 'skip';
  456. }
  457. this.settings.prev_index = target_index;
  458. return response;
  459. },
  460. adjacent : function (current_index, target_index) {
  461. for (var i = target_index + 1; i >= target_index - 1; i--) {
  462. if (i === current_index) {
  463. return true;
  464. }
  465. }
  466. return false;
  467. },
  468. // lock management
  469. lock : function () {
  470. this.settings.locked = true;
  471. },
  472. unlock : function () {
  473. this.settings.locked = false;
  474. },
  475. locked : function () {
  476. return this.settings.locked;
  477. },
  478. off : function () {
  479. this.S(this.scope).off('.fndtn.clearing');
  480. this.S(window).off('.fndtn.clearing');
  481. },
  482. reflow : function () {
  483. this.init();
  484. }
  485. };
  486. }(jQuery, window, window.document));