Add stop-at-bottom functionality

Detach all methods from sticky reference so they
can be attached to different objects.

Split sticky into stickAtTop and stickAtBottom and
make new versions of all methods and properties
specific to stickAtBottom.

Add CSS for stickAtBottom and call on load
This commit is contained in:
Tom Byers
2018-11-05 17:47:25 +00:00
parent ef76d4eeb3
commit deaa253e65
3 changed files with 263 additions and 134 deletions

View File

@@ -1,6 +1,7 @@
$(() => $("time.timeago").timeago()); $(() => $("time.timeago").timeago());
$(() => GOVUK.stickAtTopWhenScrolling.init()); $(() => GOVUK.stickAtTopWhenScrolling.init());
$(() => GOVUK.stickAtBottomWhenScrolling.init());
var showHideContent = new GOVUK.ShowHideContent(); var showHideContent = new GOVUK.ShowHideContent();
showHideContent.init(); showHideContent.init();

View File

@@ -5,28 +5,36 @@
var GOVUK = global.GOVUK || {} var GOVUK = global.GOVUK || {}
// Stick elements to top of screen when you scroll past, documentation is in the README.md // Stick elements to top of screen when you scroll past, documentation is in the README.md
var sticky = { var Sticky = function (selector) {
_hasScrolled: false, this._hasScrolled: false
_scrollTimeout: false, this._scrollTimeout: false
_hasResized: false, this._hasResized: false
_resizeTimeout: false, this._resizeTimeout: false
_els: [], this._els: []
getWindowDimensions: function () { this.CSS_SELECTOR: selector
}
Sticky.prototype.getWindowDimensions = function () {
return { return {
height: $(global).height(), height: $(global).height(),
width: $(global).width() width: $(global).width()
} }
}, }
getWindowPositions: function () { Sticky.prototype.getWindowPositions = function () {
return { return {
scrollTop: $(global).scrollTop() scrollTop: $(global).scrollTop()
} }
}, }
getElementOffset: function ($el) { Sticky.prototype.setFixedTop = function (el) {
return $el.offset() var $siblingEl = $('<div></div>')
}, $siblingEl.insertBefore(el.$fixedEl)
setElHeight: function (el) { var fixedTop = $siblingEl.offset().top - $siblingEl.position().top
$siblingEl.remove()
el.fixedTop = fixedTop
}
Sticky.prototype.setElHeight = function (el) {
var self = this
var fixedOffset = parseInt(el.$fixedEl.css('top'), 10) var fixedOffset = parseInt(el.$fixedEl.css('top'), 10)
var $el = el.$fixedEl var $el = el.$fixedEl
var $img = $el.find('img') var $img = $el.find('img')
@@ -37,77 +45,90 @@
var image = new global.Image() var image = new global.Image()
image.onload = function () { image.onload = function () {
el.height = $el.outerHeight() + fixedOffset el.height = $el.outerHeight() + fixedOffset
el.scrolledTo = self.getScrollingTo(el)
} }
image.src = $img.attr('src') image.src = $img.attr('src')
} else { } else {
el.height = $el.outerHeight() + fixedOffset el.height = $el.outerHeight() + fixedOffset
el.scrolledTo = self.getScrollingTo(el)
} }
}, }
init: function () { Sticky.prototype.init = function () {
var $els = $('.js-stick-at-top-when-scrolling') var self = this
var $els = $(self.CSS_SELECTOR)
if ($els.length > 0) { if ($els.length > 0) {
$els.each(function (i, el) { $els.each(function (i, el) {
var $el = $(el) var $el = $(el)
var el = { var el = {
$fixedEl: $el $fixedEl: $el,
scrolledFrom: self.getScrolledFrom($el),
stopped: false
} }
sticky.setElHeight(el) self.setFixedTop(el)
sticky._els.push(el) self.setElHeight(el)
$el.data('scrolled-from', sticky.getElementOffset($el).top) self._els.push(el)
}) })
if (sticky._scrollTimeout === false) { if (self._scrollTimeout === false) {
$(global).scroll(sticky.onScroll) $(global).scroll(function (e) { self.onScroll() })
sticky._scrollTimeout = global.setInterval(sticky.checkScroll, 50) self._scrollTimeout = global.setInterval(function (e) { self.checkScroll() }, 50)
} }
if (sticky._resizeTimeout === false) { if (self._resizeTimeout === false) {
$(global).resize(sticky.onResize) $(global).resize(function (e) { self.onResize() })
sticky._resizeTimeout = global.setInterval(sticky.checkResize, 50) self._resizeTimeout = global.setInterval(function (e) { self.checkResize() }, 50)
} }
} }
}, }
onScroll: function () { Sticky.prototype.onScroll = function () {
sticky._hasScrolled = true this._hasScrolled = true
}, }
onResize: function () { Sticky.prototype.onResize = function () {
sticky._hasResized = true this._hasResized = true
}, }
scrolledFromInsideWindow: function (scrolledFrom) { Sticky.prototype.viewportIsWideEnough = function (windowWidth) {
var windowVerticalPosition = sticky.getWindowPositions().scrollTop return windowWidth > 768
}
Sticky.prototype.checkScroll = function () {
var self = this
return windowVerticalPosition < scrolledFrom if (self._hasScrolled === true) {
}, self._hasScrolled = false
checkScroll: function () {
if (sticky._hasScrolled === true) {
sticky._hasScrolled = false
var windowDimensions = sticky.getWindowDimensions() $.each(self._els, function (i, el) {
$.each(sticky._els, function (i, el) {
var $el = el.$fixedEl var $el = el.$fixedEl
var scrolledFrom = $el.data('scrolled-from')
if (scrolledFrom && sticky.scrolledFromInsideWindow(scrolledFrom)) { var windowDimensions = self.getWindowDimensions()
sticky.release($el)
} else if (windowDimensions.width > 768 && !sticky.scrolledFromInsideWindow(scrolledFrom)) { if (self.scrolledFromInsideWindow(el.scrolledFrom)) {
sticky.stick($el) self.release($el)
} else {
if (self.scrolledToOutsideWindow(el, windowDimensions.height)) {
self.stop(el)
} else if (self.viewportIsWideEnough(windowDimensions.width)) {
if (el.stopped) {
self.unstop(el)
}
self.stick($el)
}
} }
}) })
} }
}, }
checkResize: function () { Sticky.prototype.checkResize = function () {
if (sticky._hasResized === true) { var self = this
sticky._hasResized = false
var windowDimensions = sticky.getWindowDimensions() if (self._hasResized === true) {
self._hasResized = false
$.each(sticky._els, function (i, el) { var windowDimensions = self.getWindowDimensions()
$.each(self._els, function (i, el) {
var $el = el.$fixedEl var $el = el.$fixedEl
var elResize = $el.hasClass('js-sticky-resize') var elResize = $el.hasClass('js-self-resize')
if (elResize) { if (elResize) {
var $shim = $('.shim') var $shim = $('.shim')
var $elParent = $el.parent('div') var $elParent = $el.parent('div')
@@ -117,30 +138,110 @@
} }
if (windowDimensions.width <= 768) { if (windowDimensions.width <= 768) {
sticky.release($el) self.release($el)
} }
}) })
} }
}, }
addShimForEl: function ($el, width, height) { Sticky.prototype.stick = function ($el) {
$el.before('<div class="shim" style="width: ' + width + 'px; height: ' + height + 'px">&nbsp;</div>')
},
stick: function ($el) {
if (!$el.hasClass('content-fixed')) { if (!$el.hasClass('content-fixed')) {
var height = Math.max($el.height(), 1) var height = Math.max($el.height(), 1)
var width = $el.width() var width = $el.width()
sticky.addShimForEl($el, width, height) this.addShimForEl($el, width, height)
$el.css('width', width + 'px').addClass('content-fixed') $el.css('width', width + 'px').addClass('content-fixed')
} }
}, }
release: function ($el) { Sticky.prototype.release = function ($el) {
if ($el.hasClass('content-fixed')) { if ($el.hasClass('content-fixed')) {
$el.removeClass('content-fixed').css('width', '') $el.removeClass('content-fixed').css('width', '')
$el.siblings('.shim').remove() $el.siblings('.shim').remove()
} }
} }
var stickAtTop = new Sticky('.js-stick-at-top-when-scrolling')
stickAtTop.getScrolledFrom = function ($el) {
return $el.offset().top
} }
GOVUK.stickAtTopWhenScrolling = sticky stickAtTop.getScrollingTo = function (el) {
var footer = $('.js-footer:eq(0)')
if (footer.length === 0) {
return 0
}
return (footer.offset().top - 10) - el.height
}
stickAtTop.scrolledFromInsideWindow = function (scrolledFrom) {
var windowTop = this.getWindowPositions().scrollTop
return scrolledFrom > windowTop
}
stickAtTop.scrolledToOutsideWindow = function (el, windowHeight) {
var windowTop = this.getWindowPositions().scrollTop
return windowTop > el.scrolledTo
}
stickAtTop.addShimForEl = function ($el, width, height) {
$el.before('<div class="shim" style="width: ' + width + 'px height: ' + height + 'px">&nbsp</div>')
}
stickAtTop.stop = function (el) {
if (!el.stopped) {
el.$fixedEl.css({ 'position': 'absolute', 'top': el.scrolledTo })
el.stopped = true
}
}
stickAtTop.unstop = function (el) {
if (el.stopped) {
el.$fixedEl.css({ 'position': '', 'top': '' })
el.stopped = false
}
}
var stickAtBottom = new Sticky('.js-stick-at-bottom-when-scrolling')
stickAtBottom.getScrolledFrom = function ($el) {
return $el.offset().top + $el.outerHeight()
}
stickAtBottom.getScrollingTo = function (el) {
var header = $('.js-header:eq(0)')
if (header.length === 0) {
return 0
}
return (header.offset().top + header.outerHeight() + 10) + el.height
}
stickAtBottom.scrolledFromInsideWindow = function (scrolledFrom) {
var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height
return scrolledFrom < windowBottom
}
stickAtBottom.scrolledToOutsideWindow = function (el, windowHeight) {
var windowBottom = this.getWindowPositions().scrollTop + this.getWindowDimensions().height
return windowBottom < el.scrolledTo
}
stickAtBottom.addShimForEl = function ($el, width, height) {
$el.after('<div class="shim" style="width: ' + width + 'px height: ' + height + 'px">&nbsp</div>')
}
stickAtBottom.stop = function (el) {
if (!el.stopped) {
el.$fixedEl.css({
'position': 'absolute',
'top': (el.scrolledTo - el.height),
'bottom': 'auto'
})
el.stopped = true
}
}
stickAtBottom.unstop = function (el) {
if (el.stopped) {
el.$fixedEl.css({
'position': '',
'top': '',
'bottom': ''
})
el.stopped = false
}
}
GOVUK.stickAtTopWhenScrolling = stickAtTop
GOVUK.stickAtBottomWhenScrolling = stickAtBottom
global.GOVUK = GOVUK global.GOVUK = GOVUK
})(window) })(window)

View File

@@ -1,16 +1,13 @@
// CSS adapted from // CSS adapted from
// https://github.com/alphagov/govuk_frontend_toolkit/blob/d9489a987086471fe30b4b925a81c12cd198c91d/docs/javascript.md#stick-at-top-when-scrolling // https://github.com/alphagov/govuk_frontend_toolkit/blob/d9489a987086471fe30b4b925a81c12cd198c91d/docs/javascript.md#stick-at-top-when-scrolling
.js-stick-at-top-when-scrolling { .js-stick-at-top-when-scrolling,
.js-stick-at-bottom-when-scrolling {
overflow: hidden; overflow: hidden;
margin-left: -$gutter-half; margin-left: -$gutter-half;
margin-top: -10px;
margin-bottom: 5px;
padding: 10px 0 0 $gutter-half; padding: 10px 0 0 $gutter-half;
position: relative; position: relative;
top: 5px;
transition: top 0.1s ease-out, box-shadow 1s ease-in-out;
.form-group { .form-group {
margin-bottom: 20px; margin-bottom: 20px;
@@ -32,17 +29,27 @@
} }
.js-stick-at-top-when-scrolling {
margin-top: -10px;
margin-bottom: 5px;
top: 5px;
transition: top 0.1s ease-out, box-shadow 1s ease-in-out;
}
.js-stick-at-top-when-scrolling {
transition: bottom 0.1s ease-out, box-shadow 1s ease-in-out;
}
.content-fixed { .content-fixed {
position: fixed; position: fixed;
top: 0;
background: $white; background: $white;
z-index: 100; z-index: 100;
margin-top: 0;
padding-right: $gutter-half; padding-right: $gutter-half;
border-bottom: 1px solid $border-colour;
box-shadow: 0 2px 0 0 rgba($border-colour, 0.2);
transition: background 0.6s ease-in-out, margin-top 0.4s ease-out;
.back-to-top-link { .back-to-top-link {
opacity: 1; opacity: 1;
@@ -51,6 +58,26 @@
} }
.js-stick-at-top-when-scrolling.content-fixed {
top: 0;
margin-top: 0;
border-bottom: 1px solid $border-colour;
box-shadow: 0 2px 0 0 rgba($border-colour, 0.2);
transition: background 0.6s ease-in-out, margin-top 0.4s ease-out;
}
.js-stick-at-bottom-when-scrolling.content-fixed {
top: auto; // cancel `top: 0;` inherited from govuk-template
bottom: 0;
border-top: 1px solid $border-colour;
box-shadow: 0 -2px 0 0 rgba($border-colour, 0.2);
transition: background 0.6s ease-in-out;
}
.shim { .shim {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;