Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * Manages elements that can offset the size of the viewport.
|
Chris@0
|
4 *
|
Chris@0
|
5 * Measures and reports viewport offset dimensions from elements like the
|
Chris@0
|
6 * toolbar that can potentially displace the positioning of other elements.
|
Chris@0
|
7 */
|
Chris@0
|
8
|
Chris@0
|
9 /**
|
Chris@0
|
10 * @typedef {object} Drupal~displaceOffset
|
Chris@0
|
11 *
|
Chris@0
|
12 * @prop {number} top
|
Chris@0
|
13 * @prop {number} left
|
Chris@0
|
14 * @prop {number} right
|
Chris@0
|
15 * @prop {number} bottom
|
Chris@0
|
16 */
|
Chris@0
|
17
|
Chris@0
|
18 /**
|
Chris@0
|
19 * Triggers when layout of the page changes.
|
Chris@0
|
20 *
|
Chris@0
|
21 * This is used to position fixed element on the page during page resize and
|
Chris@0
|
22 * Toolbar toggling.
|
Chris@0
|
23 *
|
Chris@0
|
24 * @event drupalViewportOffsetChange
|
Chris@0
|
25 */
|
Chris@0
|
26
|
Chris@17
|
27 (function($, Drupal, debounce) {
|
Chris@0
|
28 /**
|
Chris@0
|
29 * @name Drupal.displace.offsets
|
Chris@0
|
30 *
|
Chris@0
|
31 * @type {Drupal~displaceOffset}
|
Chris@0
|
32 */
|
Chris@0
|
33 let offsets = {
|
Chris@0
|
34 top: 0,
|
Chris@0
|
35 right: 0,
|
Chris@0
|
36 bottom: 0,
|
Chris@0
|
37 left: 0,
|
Chris@0
|
38 };
|
Chris@0
|
39
|
Chris@0
|
40 /**
|
Chris@0
|
41 * Calculates displacement for element based on its dimensions and placement.
|
Chris@0
|
42 *
|
Chris@0
|
43 * @param {HTMLElement} el
|
Chris@0
|
44 * The jQuery element whose dimensions and placement will be measured.
|
Chris@0
|
45 *
|
Chris@0
|
46 * @param {string} edge
|
Chris@0
|
47 * The name of the edge of the viewport that the element is associated
|
Chris@0
|
48 * with.
|
Chris@0
|
49 *
|
Chris@0
|
50 * @return {number}
|
Chris@0
|
51 * The viewport displacement distance for the requested edge.
|
Chris@0
|
52 */
|
Chris@0
|
53 function getRawOffset(el, edge) {
|
Chris@0
|
54 const $el = $(el);
|
Chris@0
|
55 const documentElement = document.documentElement;
|
Chris@0
|
56 let displacement = 0;
|
Chris@17
|
57 const horizontal = edge === 'left' || edge === 'right';
|
Chris@0
|
58 // Get the offset of the element itself.
|
Chris@0
|
59 let placement = $el.offset()[horizontal ? 'left' : 'top'];
|
Chris@0
|
60 // Subtract scroll distance from placement to get the distance
|
Chris@0
|
61 // to the edge of the viewport.
|
Chris@17
|
62 placement -=
|
Chris@17
|
63 window[`scroll${horizontal ? 'X' : 'Y'}`] ||
|
Chris@17
|
64 document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] ||
|
Chris@17
|
65 0;
|
Chris@0
|
66 // Find the displacement value according to the edge.
|
Chris@0
|
67 switch (edge) {
|
Chris@0
|
68 // Left and top elements displace as a sum of their own offset value
|
Chris@0
|
69 // plus their size.
|
Chris@0
|
70 case 'top':
|
Chris@0
|
71 // Total displacement is the sum of the elements placement and size.
|
Chris@0
|
72 displacement = placement + $el.outerHeight();
|
Chris@0
|
73 break;
|
Chris@0
|
74
|
Chris@0
|
75 case 'left':
|
Chris@0
|
76 // Total displacement is the sum of the elements placement and size.
|
Chris@0
|
77 displacement = placement + $el.outerWidth();
|
Chris@0
|
78 break;
|
Chris@0
|
79
|
Chris@0
|
80 // Right and bottom elements displace according to their left and
|
Chris@0
|
81 // top offset. Their size isn't important.
|
Chris@0
|
82 case 'bottom':
|
Chris@0
|
83 displacement = documentElement.clientHeight - placement;
|
Chris@0
|
84 break;
|
Chris@0
|
85
|
Chris@0
|
86 case 'right':
|
Chris@0
|
87 displacement = documentElement.clientWidth - placement;
|
Chris@0
|
88 break;
|
Chris@0
|
89
|
Chris@0
|
90 default:
|
Chris@0
|
91 displacement = 0;
|
Chris@0
|
92 }
|
Chris@0
|
93 return displacement;
|
Chris@0
|
94 }
|
Chris@0
|
95
|
Chris@0
|
96 /**
|
Chris@17
|
97 * Gets a specific edge's offset.
|
Chris@17
|
98 *
|
Chris@17
|
99 * Any element with the attribute data-offset-{edge} e.g. data-offset-top will
|
Chris@17
|
100 * be considered in the viewport offset calculations. If the attribute has a
|
Chris@17
|
101 * numeric value, that value will be used. If no value is provided, one will
|
Chris@17
|
102 * be calculated using the element's dimensions and placement.
|
Chris@17
|
103 *
|
Chris@17
|
104 * @function Drupal.displace.calculateOffset
|
Chris@17
|
105 *
|
Chris@17
|
106 * @param {string} edge
|
Chris@17
|
107 * The name of the edge to calculate. Can be 'top', 'right',
|
Chris@17
|
108 * 'bottom' or 'left'.
|
Chris@17
|
109 *
|
Chris@17
|
110 * @return {number}
|
Chris@17
|
111 * The viewport displacement distance for the requested edge.
|
Chris@17
|
112 */
|
Chris@17
|
113 function calculateOffset(edge) {
|
Chris@17
|
114 let edgeOffset = 0;
|
Chris@17
|
115 const displacingElements = document.querySelectorAll(
|
Chris@17
|
116 `[data-offset-${edge}]`,
|
Chris@17
|
117 );
|
Chris@17
|
118 const n = displacingElements.length;
|
Chris@17
|
119 for (let i = 0; i < n; i++) {
|
Chris@17
|
120 const el = displacingElements[i];
|
Chris@17
|
121 // If the element is not visible, do consider its dimensions.
|
Chris@17
|
122 if (el.style.display === 'none') {
|
Chris@17
|
123 continue;
|
Chris@17
|
124 }
|
Chris@17
|
125 // If the offset data attribute contains a displacing value, use it.
|
Chris@17
|
126 let displacement = parseInt(el.getAttribute(`data-offset-${edge}`), 10);
|
Chris@17
|
127 // If the element's offset data attribute exits
|
Chris@17
|
128 // but is not a valid number then get the displacement
|
Chris@17
|
129 // dimensions directly from the element.
|
Chris@17
|
130 // eslint-disable-next-line no-restricted-globals
|
Chris@17
|
131 if (isNaN(displacement)) {
|
Chris@17
|
132 displacement = getRawOffset(el, edge);
|
Chris@17
|
133 }
|
Chris@17
|
134 // If the displacement value is larger than the current value for this
|
Chris@17
|
135 // edge, use the displacement value.
|
Chris@17
|
136 edgeOffset = Math.max(edgeOffset, displacement);
|
Chris@17
|
137 }
|
Chris@17
|
138
|
Chris@17
|
139 return edgeOffset;
|
Chris@17
|
140 }
|
Chris@17
|
141
|
Chris@17
|
142 /**
|
Chris@17
|
143 * Determines the viewport offsets.
|
Chris@17
|
144 *
|
Chris@17
|
145 * @return {Drupal~displaceOffset}
|
Chris@17
|
146 * An object whose keys are the for sides an element -- top, right, bottom
|
Chris@17
|
147 * and left. The value of each key is the viewport displacement distance for
|
Chris@17
|
148 * that edge.
|
Chris@17
|
149 */
|
Chris@17
|
150 function calculateOffsets() {
|
Chris@17
|
151 return {
|
Chris@17
|
152 top: calculateOffset('top'),
|
Chris@17
|
153 right: calculateOffset('right'),
|
Chris@17
|
154 bottom: calculateOffset('bottom'),
|
Chris@17
|
155 left: calculateOffset('left'),
|
Chris@17
|
156 };
|
Chris@17
|
157 }
|
Chris@17
|
158
|
Chris@17
|
159 /**
|
Chris@17
|
160 * Informs listeners of the current offset dimensions.
|
Chris@17
|
161 *
|
Chris@17
|
162 * @function Drupal.displace
|
Chris@17
|
163 *
|
Chris@17
|
164 * @prop {Drupal~displaceOffset} offsets
|
Chris@17
|
165 *
|
Chris@17
|
166 * @param {bool} [broadcast]
|
Chris@17
|
167 * When true or undefined, causes the recalculated offsets values to be
|
Chris@17
|
168 * broadcast to listeners.
|
Chris@17
|
169 *
|
Chris@17
|
170 * @return {Drupal~displaceOffset}
|
Chris@17
|
171 * An object whose keys are the for sides an element -- top, right, bottom
|
Chris@17
|
172 * and left. The value of each key is the viewport displacement distance for
|
Chris@17
|
173 * that edge.
|
Chris@17
|
174 *
|
Chris@17
|
175 * @fires event:drupalViewportOffsetChange
|
Chris@17
|
176 */
|
Chris@17
|
177 function displace(broadcast) {
|
Chris@17
|
178 offsets = calculateOffsets();
|
Chris@17
|
179 Drupal.displace.offsets = offsets;
|
Chris@17
|
180 if (typeof broadcast === 'undefined' || broadcast) {
|
Chris@17
|
181 $(document).trigger('drupalViewportOffsetChange', offsets);
|
Chris@17
|
182 }
|
Chris@17
|
183 return offsets;
|
Chris@17
|
184 }
|
Chris@17
|
185
|
Chris@17
|
186 /**
|
Chris@17
|
187 * Registers a resize handler on the window.
|
Chris@17
|
188 *
|
Chris@17
|
189 * @type {Drupal~behavior}
|
Chris@17
|
190 */
|
Chris@17
|
191 Drupal.behaviors.drupalDisplace = {
|
Chris@17
|
192 attach() {
|
Chris@17
|
193 // Mark this behavior as processed on the first pass.
|
Chris@17
|
194 if (this.displaceProcessed) {
|
Chris@17
|
195 return;
|
Chris@17
|
196 }
|
Chris@17
|
197 this.displaceProcessed = true;
|
Chris@17
|
198
|
Chris@17
|
199 $(window).on('resize.drupalDisplace', debounce(displace, 200));
|
Chris@17
|
200 },
|
Chris@17
|
201 };
|
Chris@17
|
202
|
Chris@17
|
203 /**
|
Chris@0
|
204 * Assign the displace function to a property of the Drupal global object.
|
Chris@0
|
205 *
|
Chris@0
|
206 * @ignore
|
Chris@0
|
207 */
|
Chris@0
|
208 Drupal.displace = displace;
|
Chris@0
|
209 $.extend(Drupal.displace, {
|
Chris@0
|
210 /**
|
Chris@0
|
211 * Expose offsets to other scripts to avoid having to recalculate offsets.
|
Chris@0
|
212 *
|
Chris@0
|
213 * @ignore
|
Chris@0
|
214 */
|
Chris@0
|
215 offsets,
|
Chris@0
|
216
|
Chris@0
|
217 /**
|
Chris@0
|
218 * Expose method to compute a single edge offsets.
|
Chris@0
|
219 *
|
Chris@0
|
220 * @ignore
|
Chris@0
|
221 */
|
Chris@0
|
222 calculateOffset,
|
Chris@0
|
223 });
|
Chris@17
|
224 })(jQuery, Drupal, Drupal.debounce);
|