X-Git-Url: http://www.aleph1.co.uk/gitweb/?a=blobdiff_plain;ds=sidebyside;f=web%2Fcore%2Fmisc%2Fdisplace.es6.js;fp=web%2Fcore%2Fmisc%2Fdisplace.es6.js;h=0424d80fa51724d14f4fce50931c7bd548b1cbc6;hb=9917807b03b64faf00f6a1f29dcb6eafc454efa5;hp=0000000000000000000000000000000000000000;hpb=aea91e65e895364e460983b890e295aa5d5540a5;p=yaffs-website diff --git a/web/core/misc/displace.es6.js b/web/core/misc/displace.es6.js new file mode 100644 index 000000000..0424d80fa --- /dev/null +++ b/web/core/misc/displace.es6.js @@ -0,0 +1,218 @@ +/** + * @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) { + /** + * @name Drupal.displace.offsets + * + * @type {Drupal~displaceOffset} + */ + let offsets = { + top: 0, + right: 0, + bottom: 0, + left: 0, + }; + + /** + * 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)); + }, + }; + + /** + * Informs listeners of the current offset dimensions. + * + * @function Drupal.displace + * + * @prop {Drupal~displaceOffset} offsets + * + * @param {bool} [broadcast] + * When true or undefined, causes the recalculated offsets values to be + * broadcast to listeners. + * + * @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) { + offsets = Drupal.displace.offsets = calculateOffsets(); + if (typeof broadcast === 'undefined' || broadcast) { + $(document).trigger('drupalViewportOffsetChange', offsets); + } + return offsets; + } + + /** + * Determines the viewport offsets. + * + * @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. + */ + function calculateOffsets() { + return { + top: calculateOffset('top'), + right: calculateOffset('right'), + bottom: calculateOffset('bottom'), + left: calculateOffset('left'), + }; + } + + /** + * 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 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. + 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; + } + + /** + * Calculates displacement for element based on its dimensions and placement. + * + * @param {HTMLElement} el + * The jQuery 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']; + // 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; + + case 'right': + displacement = documentElement.clientWidth - placement; + break; + + default: + displacement = 0; + } + return displacement; + } + + /** + * Assign the displace function to a property of the Drupal global object. + * + * @ignore + */ + Drupal.displace = displace; + $.extend(Drupal.displace, { + + /** + * Expose offsets to other scripts to avoid having to recalculate offsets. + * + * @ignore + */ + offsets, + + /** + * Expose method to compute a single edge offsets. + * + * @ignore + */ + calculateOffset, + }); +}(jQuery, Drupal, Drupal.debounce));