foundation.clearing.js 13 KB

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