Skip to content

Commit

Permalink
Merge pull request #2283 from alphagov/convert-sticky-element-to-module
Browse files Browse the repository at this point in the history
Convert sticky-element.js to use Module pattern
  • Loading branch information
gclssvglx authored Nov 19, 2021
2 parents dd3fc77 + b81240b commit e01c50c
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 143 deletions.
197 changes: 99 additions & 98 deletions app/assets/javascripts/modules/sticky-element-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,111 +7,112 @@
Use 'data-module="sticky-element-container"' to instantiate, and add
`[data-sticky-element]` to the child you want to position.
*/

window.GOVUK = window.GOVUK || {}
window.GOVUK.Modules = window.GOVUK.Modules || {};

(function (Modules) {
'use strict'
function StickyElementContainer (element) {
this.wrapper = element
this.stickyElement = this.wrapper.querySelector('[data-sticky-element]')
this.hasResized = true
this.hasScrolled = true
this.interval = 50
this.windowVerticalPosition = 1
this.startPosition = 0
this.stopPosition = 0
}

StickyElementContainer.prototype.init = function () {
if (!this.stickyElement) return

window.onresize = this.onResize
window.onscroll = this.onScroll
setInterval(this.checkResize.bind(this), this.interval)
setInterval(this.checkScroll.bind(this), this.interval)
this.checkResize()
this.checkScroll()
this.stickyElement.classList.add('sticky-element--enabled')
}

StickyElementContainer.prototype.getWindowDimensions = function () {
return {
height: window.innerHeight,
width: window.innerWidth
}
}

StickyElementContainer.prototype.getWindowPositions = function () {
return {
scrollTop: window.scrollY
}
}

StickyElementContainer.prototype.onResize = function () {
this.hasResized = true
}

StickyElementContainer.prototype.onScroll = function () {
this.hasScrolled = true
}

Modules.StickyElementContainer = function () {
var self = this
StickyElementContainer.prototype.checkResize = function () {
if (this.hasResized) {
this.hasResized = false
this.hasScrolled = true

self.getWindowDimensions = function () {
return {
height: window.innerHeight,
width: window.innerWidth
}
var windowDimensions = this.getWindowDimensions()
var elementHeight = this.wrapper.offsetHeight || parseFloat(this.wrapper.style.height.replace('px', ''))
this.startPosition = this.wrapper.offsetTop
this.stopPosition = this.wrapper.offsetTop + elementHeight - windowDimensions.height
}
}

StickyElementContainer.prototype.checkScroll = function () {
if (this.hasScrolled) {
this.hasScrolled = false
this.hasResized = true

this.windowVerticalPosition = this.getWindowPositions().scrollTop

this.updateVisibility()
this.updatePosition()
}
}

self.getWindowPositions = function () {
return {
scrollTop: window.scrollY
}
StickyElementContainer.prototype.updateVisibility = function () {
var isPastStart = this.startPosition < this.windowVerticalPosition
if (isPastStart) {
this.show()
} else {
this.hide()
}
}

self.start = function ($el) {
var wrapper = $el[0]
var stickyElement = wrapper.querySelector('[data-sticky-element]')

var hasResized = true
var hasScrolled = true
var interval = 50
var windowVerticalPosition = 1
var startPosition, stopPosition

initialise()

function initialise () {
window.onresize = onResize
window.onscroll = onScroll
setInterval(checkResize, interval)
setInterval(checkScroll, interval)
checkResize()
checkScroll()
stickyElement.classList.add('sticky-element--enabled')
}

function onResize () {
hasResized = true
}

function onScroll () {
hasScrolled = true
}

function checkResize () {
if (hasResized) {
hasResized = false
hasScrolled = true

var windowDimensions = self.getWindowDimensions()
var elementHeight = wrapper.offsetHeight || parseFloat(wrapper.style.height.replace('px', ''))
startPosition = wrapper.offsetTop
stopPosition = wrapper.offsetTop + elementHeight - windowDimensions.height
}
}

function checkScroll () {
if (hasScrolled) {
hasScrolled = false

windowVerticalPosition = self.getWindowPositions().scrollTop

updateVisibility()
updatePosition()
}
}

function updateVisibility () {
var isPastStart = startPosition < windowVerticalPosition
if (isPastStart) {
show()
} else {
hide()
}
}

function updatePosition () {
var isPastEnd = stopPosition < windowVerticalPosition
if (isPastEnd) {
stickToParent()
} else {
stickToWindow()
}
}

function stickToWindow () {
stickyElement.classList.add('sticky-element--stuck-to-window')
}

function stickToParent () {
stickyElement.classList.remove('sticky-element--stuck-to-window')
}

function show () {
stickyElement.classList.remove('sticky-element--hidden')
}

function hide () {
stickyElement.classList.add('sticky-element--hidden')
}
StickyElementContainer.prototype.updatePosition = function () {
var isPastEnd = this.stopPosition < this.windowVerticalPosition
if (isPastEnd) {
this.stickToParent()
} else {
this.stickToWindow()
}
}

StickyElementContainer.prototype.stickToWindow = function () {
this.stickyElement.classList.add('sticky-element--stuck-to-window')
}

StickyElementContainer.prototype.stickToParent = function () {
this.stickyElement.classList.remove('sticky-element--stuck-to-window')
}

StickyElementContainer.prototype.show = function () {
this.stickyElement.classList.remove('sticky-element--hidden')
}

StickyElementContainer.prototype.hide = function () {
this.stickyElement.classList.add('sticky-element--hidden')
}

Modules.StickyElementContainer = StickyElementContainer
})(window.GOVUK.Modules)
100 changes: 55 additions & 45 deletions spec/javascripts/modules/sticky-element-container.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,72 @@ describe('A sticky-element-container module', function () {
'use strict'

var GOVUK = window.GOVUK
var $ = window.$
var instance

beforeEach(function () {
instance = new GOVUK.Modules.StickyElementContainer()
})
describe('on desktop', function () {
var $element
var $footer
var instance

beforeEach(function () {
$element = $(
'<div data-module="sticky-element-container" style="height: 9001px; margin-bottom: 1000px">' +
'<div data-sticky-element>' +
'<span>Content</span>' +
'</div>' +
'</div>'
)

instance = new GOVUK.Modules.StickyElementContainer($element[0])
$footer = $element.find('[data-sticky-element]')

instance.getWindowDimensions = function () {
return {
height: 768,
width: 1024
}
}
})

describe('in a large parent element', function () {
var $element = $(
'<div data-module="sticky-element-container" style="height: 9001px; margin-bottom: 1000px">' +
'<div data-sticky-element>' +
'<span>Content</span>' +
'</div>' +
'</div>'
)
var $footer = $element.find('[data-sticky-element]')

describe('on desktop', function () {
beforeEach(function () {
instance.getWindowDimensions = function () {
return {
height: 768,
width: 1024
}
it('hides the element, when scrolled at the top', function () {
instance.getWindowPositions = function () {
return {
scrollTop: 0
}
})
}

it('hides the element, when scrolled at the top', function () {
instance.start($element)
instance.checkResize()
instance.checkScroll()

expect($footer.hasClass('sticky-element--hidden')).toBe(true)
})
expect($footer.hasClass('sticky-element--hidden')).toBe(true)
expect($footer.hasClass('sticky-element--stuck-to-window')).toBe(true)
})

it('shows the element, stuck to the window, when scrolled in the middle', function () {
instance.getWindowPositions = function () {
return {
scrollTop: 5000
}
it('shows the element, stuck to the window, when scrolled in the middle', function () {
instance.getWindowPositions = function () {
return {
scrollTop: 5000
}
instance.start($element)
}

expect($footer.hasClass('sticky-element--hidden')).toBe(false)
expect($footer.hasClass('sticky-element--stuck-to-window')).toBe(true)
})
instance.checkResize()
instance.checkScroll()

it('shows the element, stuck to the parent, when scrolled at the bottom', function () {
instance.getWindowPositions = function () {
return {
scrollTop: 9800
}
expect($footer.hasClass('sticky-element--hidden')).toBe(false)
expect($footer.hasClass('sticky-element--stuck-to-window')).toBe(true)
})

it('shows the element, stuck to the parent, when scrolled at the bottom', function () {
instance.getWindowPositions = function () {
return {
scrollTop: 9800
}
instance.start($element)
}

instance.checkResize()
instance.checkScroll()

expect($footer.hasClass('sticky-element--hidden')).toBe(false)
expect($footer.hasClass('sticky-element--stuck-to-window')).toBe(false)
})
expect($footer.hasClass('sticky-element--hidden')).toBe(false)
expect($footer.hasClass('sticky-element--stuck-to-window')).toBe(false)
})
})
})

0 comments on commit e01c50c

Please sign in to comment.