diff options
Diffstat (limited to 'core/misc/displace.js')
-rw-r--r-- | core/misc/displace.js | 200 |
1 files changed, 169 insertions, 31 deletions
diff --git a/core/misc/displace.js b/core/misc/displace.js index ac1902b7130..6e673d2c949 100644 --- a/core/misc/displace.js +++ b/core/misc/displace.js @@ -1,58 +1,125 @@ /** -* DO NOT EDIT THIS FILE. -* See the following change record for more information, -* https://www.drupal.org/node/2815083 -* @preserve -**/ + * @file + * Manages elements that can offset the size of the viewport. + * + * Measures and reports viewport offset dimensions from elements like the + * toolbar that can potentially displace the positioning of other elements. + */ +/** + * @typedef {object} Drupal~displaceOffset + * + * @prop {number} top + * @prop {number} left + * @prop {number} right + * @prop {number} bottom + */ + +/** + * Triggers when layout of the page changes. + * + * This is used to position fixed element on the page during page resize and + * Toolbar toggling. + * + * @event drupalViewportOffsetChange + */ (function ($, Drupal, debounce) { + /** + * + * @type {Drupal~displaceOffset} + */ const cache = { right: 0, left: 0, bottom: 0, - top: 0 + top: 0, }; + /** + * The prefix used for the css custom variable name. + * + * @type {string} + */ const cssVarPrefix = '--drupal-displace-offset'; const documentStyle = document.documentElement.style; const offsetKeys = Object.keys(cache); + /** + * The object with accessors that update the CSS variable on value update. + * + * @type {Drupal~displaceOffset} + */ const offsetProps = {}; - offsetKeys.forEach(edge => { + offsetKeys.forEach((edge) => { offsetProps[edge] = { + // Show this property when using Object.keys(). enumerable: true, - get() { return cache[edge]; }, - set(value) { + // Only update the CSS custom variable when the value changed. if (value !== cache[edge]) { documentStyle.setProperty(`${cssVarPrefix}-${edge}`, `${value}px`); } - cache[edge] = value; - } - + }, }; }); + + /** + * Current value of the size of margins on the page. + * + * This property is read-only and the object is sealed to prevent key name + * modifications since key names are used to dynamically construct CSS custom + * variable names. + * + * @name Drupal.displace.offsets + * + * @type {Drupal~displaceOffset} + */ const offsets = Object.seal(Object.defineProperties({}, offsetProps)); + /** + * Calculates displacement for element based on its dimensions and placement. + * + * @param {HTMLElement} el + * The element whose dimensions and placement will be measured. + * + * @param {string} edge + * The name of the edge of the viewport that the element is associated + * with. + * + * @return {number} + * The viewport displacement distance for the requested edge. + */ function getRawOffset(el, edge) { const $el = $(el); const documentElement = document.documentElement; let displacement = 0; const horizontal = edge === 'left' || edge === 'right'; + // Get the offset of the element itself. let placement = $el.offset()[horizontal ? 'left' : 'top']; - placement -= window[`scroll${horizontal ? 'X' : 'Y'}`] || document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] || 0; - + // Subtract scroll distance from placement to get the distance + // to the edge of the viewport. + placement -= + window[`scroll${horizontal ? 'X' : 'Y'}`] || + document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] || + 0; + // Find the displacement value according to the edge. switch (edge) { + // Left and top elements displace as a sum of their own offset value + // plus their size. case 'top': + // Total displacement is the sum of the elements placement and size. displacement = placement + $el.outerHeight(); break; case 'left': + // Total displacement is the sum of the elements placement and size. displacement = placement + $el.outerWidth(); break; + // Right and bottom elements displace according to their left and + // top offset. Their size isn't important. case 'bottom': displacement = documentElement.clientHeight - placement; break; @@ -64,66 +131,137 @@ default: displacement = 0; } - return displacement; } + /** + * Gets a specific edge's offset. + * + * Any element with the attribute data-offset-{edge} e.g. data-offset-top will + * be considered in the viewport offset calculations. If the attribute has a + * numeric value, that value will be used. If no value is provided, one will + * be calculated using the element's dimensions and placement. + * + * @function Drupal.displace.calculateOffset + * + * @param {string} edge + * The name of the edge to calculate. Can be 'top', 'right', + * 'bottom' or 'left'. + * + * @return {number} + * The viewport displacement distance for the requested edge. + */ function calculateOffset(edge) { let edgeOffset = 0; - const displacingElements = document.querySelectorAll(`[data-offset-${edge}]`); + const displacingElements = document.querySelectorAll( + `[data-offset-${edge}]`, + ); const n = displacingElements.length; - for (let i = 0; i < n; i++) { const el = displacingElements[i]; - + // If the element is not visible, do consider its dimensions. if (el.style.display === 'none') { continue; } - + // If the offset data attribute contains a displacing value, use it. let displacement = parseInt(el.getAttribute(`data-offset-${edge}`), 10); - + // If the element's offset data attribute exits + // but is not a valid number then get the displacement + // dimensions directly from the element. + // eslint-disable-next-line no-restricted-globals if (isNaN(displacement)) { displacement = getRawOffset(el, edge); } - + // If the displacement value is larger than the current value for this + // edge, use the displacement value. edgeOffset = Math.max(edgeOffset, displacement); } return edgeOffset; } - function displace() { - let broadcast = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + /** + * Informs listeners of the current offset dimensions. + * + * Corresponding CSS custom variables are also updated. + * Corresponding CSS custom variables names are: + * - `--drupal-displace-offset-top` + * - `--drupal-displace-offset-right` + * - `--drupal-displace-offset-bottom` + * - `--drupal-displace-offset-left` + * + * @function Drupal.displace + * + * @prop {Drupal~displaceOffset} offsets + * + * @param {bool} [broadcast=true] + * When true, causes the recalculated offsets values to be + * broadcast to listeners. If none is given, defaults to true. + * + * @return {Drupal~displaceOffset} + * An object whose keys are the for sides an element -- top, right, bottom + * and left. The value of each key is the viewport displacement distance for + * that edge. + * + * @fires event:drupalViewportOffsetChange + */ + function displace(broadcast = true) { const newOffsets = {}; - offsetKeys.forEach(edge => { + // Getting the offset and setting the offset needs to be separated because + // of performance concerns. Only do DOM/style reading happening here. + offsetKeys.forEach((edge) => { newOffsets[edge] = calculateOffset(edge); }); - offsetKeys.forEach(edge => { + // Once we have all the values, write to the DOM/style. + offsetKeys.forEach((edge) => { + // Updating the value in place also update Drupal.displace.offsets. offsets[edge] = newOffsets[edge]; }); if (broadcast) { $(document).trigger('drupalViewportOffsetChange', offsets); } - return offsets; } + /** + * Registers a resize handler on the window. + * + * @type {Drupal~behavior} + */ Drupal.behaviors.drupalDisplace = { attach() { + // Mark this behavior as processed on the first pass. if (this.displaceProcessed) { return; } - this.displaceProcessed = true; $(window).on('resize.drupalDisplace', debounce(displace, 200)); - } - + }, }; + + /** + * Assign the displace function to a property of the Drupal global object. + * + * @ignore + */ Drupal.displace = displace; + + /** + * Expose offsets to other scripts to avoid having to recalculate offsets. + * + * @ignore + */ Object.defineProperty(Drupal.displace, 'offsets', { value: offsets, - writable: false + // Make sure other scripts don't replace this object. + writable: false, }); + + /** + * Expose method to compute a single edge offsets. + * + * @ignore + */ Drupal.displace.calculateOffset = calculateOffset; -})(jQuery, Drupal, Drupal.debounce);
\ No newline at end of file +})(jQuery, Drupal, Drupal.debounce); |