3 * Manages elements that can offset the size of the viewport.
5 * Measures and reports viewport offset dimensions from elements like the
6 * toolbar that can potentially displace the positioning of other elements.
10 * @typedef {object} Drupal~displaceOffset
14 * @prop {number} right
15 * @prop {number} bottom
19 * Triggers when layout of the page changes.
21 * This is used to position fixed element on the page during page resize and
24 * @event drupalViewportOffsetChange
27 (function ($, Drupal, debounce) {
32 * @name Drupal.displace.offsets
34 * @type {Drupal~displaceOffset}
44 * Registers a resize handler on the window.
46 * @type {Drupal~behavior}
48 Drupal.behaviors.drupalDisplace = {
50 // Mark this behavior as processed on the first pass.
51 if (this.displaceProcessed) {
54 this.displaceProcessed = true;
56 $(window).on('resize.drupalDisplace', debounce(displace, 200));
61 * Informs listeners of the current offset dimensions.
63 * @function Drupal.displace
65 * @prop {Drupal~displaceOffset} offsets
67 * @param {bool} [broadcast]
68 * When true or undefined, causes the recalculated offsets values to be
69 * broadcast to listeners.
71 * @return {Drupal~displaceOffset}
72 * An object whose keys are the for sides an element -- top, right, bottom
73 * and left. The value of each key is the viewport displacement distance for
76 * @fires event:drupalViewportOffsetChange
78 function displace(broadcast) {
79 offsets = Drupal.displace.offsets = calculateOffsets();
80 if (typeof broadcast === 'undefined' || broadcast) {
81 $(document).trigger('drupalViewportOffsetChange', offsets);
87 * Determines the viewport offsets.
89 * @return {Drupal~displaceOffset}
90 * An object whose keys are the for sides an element -- top, right, bottom
91 * and left. The value of each key is the viewport displacement distance for
94 function calculateOffsets() {
96 top: calculateOffset('top'),
97 right: calculateOffset('right'),
98 bottom: calculateOffset('bottom'),
99 left: calculateOffset('left')
104 * Gets a specific edge's offset.
106 * Any element with the attribute data-offset-{edge} e.g. data-offset-top will
107 * be considered in the viewport offset calculations. If the attribute has a
108 * numeric value, that value will be used. If no value is provided, one will
109 * be calculated using the element's dimensions and placement.
111 * @function Drupal.displace.calculateOffset
113 * @param {string} edge
114 * The name of the edge to calculate. Can be 'top', 'right',
115 * 'bottom' or 'left'.
118 * The viewport displacement distance for the requested edge.
120 function calculateOffset(edge) {
122 var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']');
123 var n = displacingElements.length;
124 for (var i = 0; i < n; i++) {
125 var el = displacingElements[i];
126 // If the element is not visible, do consider its dimensions.
127 if (el.style.display === 'none') {
130 // If the offset data attribute contains a displacing value, use it.
131 var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10);
132 // If the element's offset data attribute exits
133 // but is not a valid number then get the displacement
134 // dimensions directly from the element.
135 if (isNaN(displacement)) {
136 displacement = getRawOffset(el, edge);
138 // If the displacement value is larger than the current value for this
139 // edge, use the displacement value.
140 edgeOffset = Math.max(edgeOffset, displacement);
147 * Calculates displacement for element based on its dimensions and placement.
149 * @param {HTMLElement} el
150 * The jQuery element whose dimensions and placement will be measured.
152 * @param {string} edge
153 * The name of the edge of the viewport that the element is associated
157 * The viewport displacement distance for the requested edge.
159 function getRawOffset(el, edge) {
161 var documentElement = document.documentElement;
162 var displacement = 0;
163 var horizontal = (edge === 'left' || edge === 'right');
164 // Get the offset of the element itself.
165 var placement = $el.offset()[horizontal ? 'left' : 'top'];
166 // Subtract scroll distance from placement to get the distance
167 // to the edge of the viewport.
168 placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0;
169 // Find the displacement value according to the edge.
171 // Left and top elements displace as a sum of their own offset value
174 // Total displacement is the sum of the elements placement and size.
175 displacement = placement + $el.outerHeight();
179 // Total displacement is the sum of the elements placement and size.
180 displacement = placement + $el.outerWidth();
183 // Right and bottom elements displace according to their left and
184 // top offset. Their size isn't important.
186 displacement = documentElement.clientHeight - placement;
190 displacement = documentElement.clientWidth - placement;
200 * Assign the displace function to a property of the Drupal global object.
204 Drupal.displace = displace;
205 $.extend(Drupal.displace, {
208 * Expose offsets to other scripts to avoid having to recalculate offsets.
215 * Expose method to compute a single edge offsets.
219 calculateOffset: calculateOffset
222 })(jQuery, Drupal, Drupal.debounce);