foundation.clearing.js 16 KB

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