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