foundation.clearing.js 15 KB

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