From a9b7a0d88780188b796649c20869208a400b35ee Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Fri, 18 Jan 2019 11:44:59 +0000 Subject: [PATCH] Add box shadow by JS When mode === 'dialog', sticky elements are stacked so we need to apply the box shadow to the top (when sticking to the bottom edge) and bottom (when sticking to the top edge) elements in the stack. From what I can see, we need the version of `nth-child` that supports targeting by selector. As of this date, support for this is only in Safari: https://caniuse.com/#feat=css-nth-child-of Until we can use this version of `nth-child`, we need to use JS to apply the styles. --- .../stick-to-window-when-scrolling.js | 97 +++++++++++++------ .../stick-at-top-when-scrolling.scss | 10 ++ 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/stick-to-window-when-scrolling.js b/app/assets/javascripts/stick-to-window-when-scrolling.js index 4630569d7..d18ccda41 100644 --- a/app/assets/javascripts/stick-to-window-when-scrolling.js +++ b/app/assets/javascripts/stick-to-window-when-scrolling.js @@ -5,6 +5,41 @@ var GOVUK = global.GOVUK || {}; var _mode = 'default'; + // Object collecting together methods for dealing with marking the edge of a sticky, or group of + // sticky elements (as seen in dialog mode) + var oppositeEdge = { + _classes: { + 'top': 'content-fixed__top', + 'bottom': 'content-fixed__bottom' + }, + _getClassForEdge: function (edge) { + return this._classes[edge]; + }, + mark: function (sticky) { + var edgeClass = this._getClassForEdge(sticky.edge); + var els; + + if (_mode === 'dialog') { + els = [dialog.getElementAtOppositeEnd(sticky)]; + } else { + els = sticky._els; + } + + els = $.grep(els, function (el) { return el.isStuck(); }); + + $.each(els, function (i, el) { + el.$fixedEl.addClass(edgeClass); + }); + }, + unmark: function (sticky) { + var edgeClass = this._getClassForEdge(sticky.edge); + + $.each(sticky._els, function (i, el) { + el.$fixedEl.removeClass(edgeClass); + }); + } + }; + // Constructor for objects holding data for each element to have sticky behaviour var StickyElement = function ($el, sticky) { this._sticky = sticky; @@ -23,19 +58,23 @@ StickyElement.prototype.appliedClass = function () { return this._appliedClass; }; - StickyElement.prototype.removeStickyClasses = function () { - this.$fixedEl.removeClass([this._initialFixedClass, this._fixedClass].join(' ')); + StickyElement.prototype.removeStickyClasses = function (sticky) { + this.$fixedEl.removeClass([ + this._initialFixedClass, + this._fixedClass + ].join(' ')); }; StickyElement.prototype.isStuck = function () { return this._appliedClass !== null; }; - StickyElement.prototype.stick = function () { + StickyElement.prototype.stick = function (sticky) { this._appliedClass = this.stickyClass(); this.$fixedEl.addClass(this._appliedClass); this._hasBeenCalled = true; }; - StickyElement.prototype.release = function () { + StickyElement.prototype.release = function (sticky) { this._appliedClass = null; + this.removeStickyClasses(sticky); this._hasBeenCalled = true; }; // When a sticky element is moved into the 'stuck' state, a shim is inserted into the @@ -101,16 +140,6 @@ _elsThatCanBeStuck: function (els) { return $.grep(els, function (el) { return el.canBeStuck(); }); }, - hasOppositeEdge: function (el, sticky) { - var els = this._elsThatCanBeStuck(sticky._els); - var idx; - - if (els.length < 2) { return true; } - - idx = els.indexOf(el); - - return (sticky.edge === 'top') ? idx === (els.length - 1) : idx === 0; - }, getOffsetFromEdge: function (el, sticky) { var els = this._elsThatCanBeStuck(sticky._els).slice(); var elIdx; @@ -157,11 +186,8 @@ var self = this; var els = sticky._els.slice(); var height = sticky.getWindowDimensions().height; - var elsThatCanBeStuck = function () { - return $.grep(els, function (el) { return el.canBeStuck(); }); - }; var totalStickyHeight = function () { - return self._getTotalHeight(elsThatCanBeStuck()); + return self._getTotalHeight(self._elsThatCanBeStuck(els)); }; var dialogFitsHeight = function () { return totalStickyHeight() <= height; @@ -176,8 +202,8 @@ // reset elements $.each(els, function (i, el) { el.canBeStuck(true); }); - while (elsThatCanBeStuck().length && !dialogFitsHeight()) { - var currentEl = elsThatCanBeStuck()[0]; + while (self._elsThatCanBeStuck(els).length && !dialogFitsHeight()) { + var currentEl = self._elsThatCanBeStuck(els)[0]; sticky.reset(currentEl); currentEl.canBeStuck(false); @@ -187,10 +213,21 @@ return this._getTotalHeight(els); }, - getInPageEdgePosition: function (sticky) { - var idx = (sticky.edge === 'top') ? 0 : sticky._els.length - 1; + getElementAtStickyEdge: function (sticky) { + var els = this._elsThatCanBeStuck(sticky._els); + var idx = (sticky.edge === 'top') ? 0 : els.length - 1; - return sticky._els[idx].inPageEdgePosition; + return els[idx]; + }, + // get element at the end opposite the sticky edge + getElementAtOppositeEnd: function (sticky) { + var els = this._elsThatCanBeStuck(sticky._els); + var idx = (sticky.edge === 'top') ? els.length - 1 : 0; + + return els[idx]; + }, + getInPageEdgePosition: function (sticky) { + return this.getElementAtStickyEdge(sticky).inPageEdgePosition; }, getHeight: function (els) { return this._getTotalHeight(this._elsThatCanBeStuck(els)); @@ -272,12 +309,18 @@ } }; + // clean up any existing styles marking the edges of sticky elements + oppositeEdge.unmark(self); + $.each(self._els, function (i, el) { if (el.canBeStuck()) { _setElementPosition(el); } }); + // add styles to mark the edge of sticky elements opposite to that stuck to the window + oppositeEdge.mark(self); + if (self._initialPositionsSet === false) { self._initialPositionsSet = true; } }; // Store all the dimensions for a sticky element to limit DOM queries @@ -489,13 +532,13 @@ if (el.isStuck()) { var $el = el.$fixedEl; - el.removeStickyClasses(); + el.removeStickyClasses(this); $el.css('width', ''); if (_mode === 'dialog') { dialog.releaseEl(el, this); } el.removeShim(); - el.release(); + el.release(this); } }; @@ -560,7 +603,7 @@ 'width': $el.width() + 'px', 'top': offset + 'px' }); - el.stick(); + el.stick(this); } }; stickAtTop.stop = function (el) { @@ -641,7 +684,7 @@ 'width': $el.width() + 'px', 'bottom': offset + 'px' }); - el.stick(); + el.stick(this); } }; stickAtBottom.stop = function (el) { diff --git a/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss b/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss index 9a6ced38a..9c5cea1f1 100644 --- a/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss +++ b/app/assets/stylesheets/components/stick-at-top-when-scrolling.scss @@ -86,6 +86,11 @@ top: 0; margin-top: 0; + +} + +.js-stick-at-top-when-scrolling.content-fixed__top { + border-bottom: 1px solid $border-colour; box-shadow: 0 2px 0 0 rgba($border-colour, 0.2); @@ -102,6 +107,11 @@ top: auto; // cancel `top: 0;` inherited from govuk-template bottom: 0; + +} + +.js-stick-at-bottom-when-scrolling.content-fixed__bottom { + border-top: 1px solid $border-colour; box-shadow: 0 -2px 0 0 rgba($border-colour, 0.2);