foundation.equalizer.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. 'use strict';
  2. import $ from 'jquery';
  3. import { MediaQuery } from './foundation.util.mediaQuery';
  4. import { onImagesLoaded } from './foundation.util.imageLoader';
  5. import { GetYoDigits } from './foundation.core.utils';
  6. import { Plugin } from './foundation.core.plugin';
  7. /**
  8. * Equalizer module.
  9. * @module foundation.equalizer
  10. * @requires foundation.util.mediaQuery
  11. * @requires foundation.util.imageLoader if equalizer contains images
  12. */
  13. class Equalizer extends Plugin {
  14. /**
  15. * Creates a new instance of Equalizer.
  16. * @class
  17. * @name Equalizer
  18. * @fires Equalizer#init
  19. * @param {Object} element - jQuery object to add the trigger to.
  20. * @param {Object} options - Overrides to the default plugin settings.
  21. */
  22. _setup(element, options){
  23. this.$element = element;
  24. this.options = $.extend({}, Equalizer.defaults, this.$element.data(), options);
  25. this.className = 'Equalizer'; // ie9 back compat
  26. this._init();
  27. }
  28. /**
  29. * Initializes the Equalizer plugin and calls functions to get equalizer functioning on load.
  30. * @private
  31. */
  32. _init() {
  33. var eqId = this.$element.attr('data-equalizer') || '';
  34. var $watched = this.$element.find(`[data-equalizer-watch="${eqId}"]`);
  35. MediaQuery._init();
  36. this.$watched = $watched.length ? $watched : this.$element.find('[data-equalizer-watch]');
  37. this.$element.attr('data-resize', (eqId || GetYoDigits(6, 'eq')));
  38. this.$element.attr('data-mutate', (eqId || GetYoDigits(6, 'eq')));
  39. this.hasNested = this.$element.find('[data-equalizer]').length > 0;
  40. this.isNested = this.$element.parentsUntil(document.body, '[data-equalizer]').length > 0;
  41. this.isOn = false;
  42. this._bindHandler = {
  43. onResizeMeBound: this._onResizeMe.bind(this),
  44. onPostEqualizedBound: this._onPostEqualized.bind(this)
  45. };
  46. var imgs = this.$element.find('img');
  47. var tooSmall;
  48. if(this.options.equalizeOn){
  49. tooSmall = this._checkMQ();
  50. $(window).on('changed.zf.mediaquery', this._checkMQ.bind(this));
  51. }else{
  52. this._events();
  53. }
  54. if((typeof tooSmall !== 'undefined' && tooSmall === false) || typeof tooSmall === 'undefined'){
  55. if(imgs.length){
  56. onImagesLoaded(imgs, this._reflow.bind(this));
  57. }else{
  58. this._reflow();
  59. }
  60. }
  61. }
  62. /**
  63. * Removes event listeners if the breakpoint is too small.
  64. * @private
  65. */
  66. _pauseEvents() {
  67. this.isOn = false;
  68. this.$element.off({
  69. '.zf.equalizer': this._bindHandler.onPostEqualizedBound,
  70. 'resizeme.zf.trigger': this._bindHandler.onResizeMeBound,
  71. 'mutateme.zf.trigger': this._bindHandler.onResizeMeBound
  72. });
  73. }
  74. /**
  75. * function to handle $elements resizeme.zf.trigger, with bound this on _bindHandler.onResizeMeBound
  76. * @private
  77. */
  78. _onResizeMe(e) {
  79. this._reflow();
  80. }
  81. /**
  82. * function to handle $elements postequalized.zf.equalizer, with bound this on _bindHandler.onPostEqualizedBound
  83. * @private
  84. */
  85. _onPostEqualized(e) {
  86. if(e.target !== this.$element[0]){ this._reflow(); }
  87. }
  88. /**
  89. * Initializes events for Equalizer.
  90. * @private
  91. */
  92. _events() {
  93. var _this = this;
  94. this._pauseEvents();
  95. if(this.hasNested){
  96. this.$element.on('postequalized.zf.equalizer', this._bindHandler.onPostEqualizedBound);
  97. }else{
  98. this.$element.on('resizeme.zf.trigger', this._bindHandler.onResizeMeBound);
  99. this.$element.on('mutateme.zf.trigger', this._bindHandler.onResizeMeBound);
  100. }
  101. this.isOn = true;
  102. }
  103. /**
  104. * Checks the current breakpoint to the minimum required size.
  105. * @private
  106. */
  107. _checkMQ() {
  108. var tooSmall = !MediaQuery.is(this.options.equalizeOn);
  109. if(tooSmall){
  110. if(this.isOn){
  111. this._pauseEvents();
  112. this.$watched.css('height', 'auto');
  113. }
  114. }else{
  115. if(!this.isOn){
  116. this._events();
  117. }
  118. }
  119. return tooSmall;
  120. }
  121. /**
  122. * A noop version for the plugin
  123. * @private
  124. */
  125. _killswitch() {
  126. return;
  127. }
  128. /**
  129. * Calls necessary functions to update Equalizer upon DOM change
  130. * @private
  131. */
  132. _reflow() {
  133. if(!this.options.equalizeOnStack){
  134. if(this._isStacked()){
  135. this.$watched.css('height', 'auto');
  136. return false;
  137. }
  138. }
  139. if (this.options.equalizeByRow) {
  140. this.getHeightsByRow(this.applyHeightByRow.bind(this));
  141. }else{
  142. this.getHeights(this.applyHeight.bind(this));
  143. }
  144. }
  145. /**
  146. * Manually determines if the first 2 elements are *NOT* stacked.
  147. * @private
  148. */
  149. _isStacked() {
  150. if (!this.$watched[0] || !this.$watched[1]) {
  151. return true;
  152. }
  153. return this.$watched[0].getBoundingClientRect().top !== this.$watched[1].getBoundingClientRect().top;
  154. }
  155. /**
  156. * Finds the outer heights of children contained within an Equalizer parent and returns them in an array
  157. * @param {Function} cb - A non-optional callback to return the heights array to.
  158. * @returns {Array} heights - An array of heights of children within Equalizer container
  159. */
  160. getHeights(cb) {
  161. var heights = [];
  162. for(var i = 0, len = this.$watched.length; i < len; i++){
  163. this.$watched[i].style.height = 'auto';
  164. heights.push(this.$watched[i].offsetHeight);
  165. }
  166. cb(heights);
  167. }
  168. /**
  169. * Finds the outer heights of children contained within an Equalizer parent and returns them in an array
  170. * @param {Function} cb - A non-optional callback to return the heights array to.
  171. * @returns {Array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child
  172. */
  173. getHeightsByRow(cb) {
  174. var lastElTopOffset = (this.$watched.length ? this.$watched.first().offset().top : 0),
  175. groups = [],
  176. group = 0;
  177. //group by Row
  178. groups[group] = [];
  179. for(var i = 0, len = this.$watched.length; i < len; i++){
  180. this.$watched[i].style.height = 'auto';
  181. //maybe could use this.$watched[i].offsetTop
  182. var elOffsetTop = $(this.$watched[i]).offset().top;
  183. if (elOffsetTop!=lastElTopOffset) {
  184. group++;
  185. groups[group] = [];
  186. lastElTopOffset=elOffsetTop;
  187. }
  188. groups[group].push([this.$watched[i],this.$watched[i].offsetHeight]);
  189. }
  190. for (var j = 0, ln = groups.length; j < ln; j++) {
  191. var heights = $(groups[j]).map(function(){ return this[1]; }).get();
  192. var max = Math.max.apply(null, heights);
  193. groups[j].push(max);
  194. }
  195. cb(groups);
  196. }
  197. /**
  198. * Changes the CSS height property of each child in an Equalizer parent to match the tallest
  199. * @param {array} heights - An array of heights of children within Equalizer container
  200. * @fires Equalizer#preequalized
  201. * @fires Equalizer#postequalized
  202. */
  203. applyHeight(heights) {
  204. var max = Math.max.apply(null, heights);
  205. /**
  206. * Fires before the heights are applied
  207. * @event Equalizer#preequalized
  208. */
  209. this.$element.trigger('preequalized.zf.equalizer');
  210. this.$watched.css('height', max);
  211. /**
  212. * Fires when the heights have been applied
  213. * @event Equalizer#postequalized
  214. */
  215. this.$element.trigger('postequalized.zf.equalizer');
  216. }
  217. /**
  218. * Changes the CSS height property of each child in an Equalizer parent to match the tallest by row
  219. * @param {array} groups - An array of heights of children within Equalizer container grouped by row with element,height and max as last child
  220. * @fires Equalizer#preequalized
  221. * @fires Equalizer#preequalizedrow
  222. * @fires Equalizer#postequalizedrow
  223. * @fires Equalizer#postequalized
  224. */
  225. applyHeightByRow(groups) {
  226. /**
  227. * Fires before the heights are applied
  228. */
  229. this.$element.trigger('preequalized.zf.equalizer');
  230. for (var i = 0, len = groups.length; i < len ; i++) {
  231. var groupsILength = groups[i].length,
  232. max = groups[i][groupsILength - 1];
  233. if (groupsILength<=2) {
  234. $(groups[i][0][0]).css({'height':'auto'});
  235. continue;
  236. }
  237. /**
  238. * Fires before the heights per row are applied
  239. * @event Equalizer#preequalizedrow
  240. */
  241. this.$element.trigger('preequalizedrow.zf.equalizer');
  242. for (var j = 0, lenJ = (groupsILength-1); j < lenJ ; j++) {
  243. $(groups[i][j][0]).css({'height':max});
  244. }
  245. /**
  246. * Fires when the heights per row have been applied
  247. * @event Equalizer#postequalizedrow
  248. */
  249. this.$element.trigger('postequalizedrow.zf.equalizer');
  250. }
  251. /**
  252. * Fires when the heights have been applied
  253. */
  254. this.$element.trigger('postequalized.zf.equalizer');
  255. }
  256. /**
  257. * Destroys an instance of Equalizer.
  258. * @function
  259. */
  260. _destroy() {
  261. this._pauseEvents();
  262. this.$watched.css('height', 'auto');
  263. }
  264. }
  265. /**
  266. * Default settings for plugin
  267. */
  268. Equalizer.defaults = {
  269. /**
  270. * Enable height equalization when stacked on smaller screens.
  271. * @option
  272. * @type {boolean}
  273. * @default false
  274. */
  275. equalizeOnStack: false,
  276. /**
  277. * Enable height equalization row by row.
  278. * @option
  279. * @type {boolean}
  280. * @default false
  281. */
  282. equalizeByRow: false,
  283. /**
  284. * String representing the minimum breakpoint size the plugin should equalize heights on.
  285. * @option
  286. * @type {string}
  287. * @default ''
  288. */
  289. equalizeOn: ''
  290. };
  291. export {Equalizer};