Created
September 29, 2021 20:50
-
-
Save ten1seven/74fc8393fd10a3e1d3ed442f384d4dfc to your computer and use it in GitHub Desktop.
Carousel rotator JavaScript from Urban Land Institute
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import debounce from 'lodash.debounce' | |
export default class Rotator { | |
constructor(el) { | |
this.variables(el) | |
this.setup() | |
this.events() | |
} | |
variables(el) { | |
this.el = el | |
// container | |
this.container = this.el.querySelector('[data-rotator-container]') | |
this.containerWidth = this.container.offsetWidth | |
// buttons | |
this.nextButton = this.el.querySelector('[data-rotator-next]') | |
this.prevButton = this.el.querySelector('[data-rotator-prev]') | |
this.pauseButton = this.el.querySelector('[data-rotator-pause]') | |
// pages | |
this.bundlePages() | |
this.computePages() | |
// child pages | |
this.hasChildren = false | |
this.childContainers = this.el.querySelectorAll('[data-child-rotator-container]') | |
if (this.childContainers.length > 0) { | |
this.bundleChildPages() | |
this.computeChildPages() | |
} | |
// auto rotate | |
this.rotate = this.el.hasAttribute('data-rotator-auto') && this.el.getAttribute('data-rotator-auto') === 'true' ? true : null | |
this.rotateSpeed = this.el.hasAttribute('data-rotator-speed') ? parseInt(this.el.getAttribute('data-rotator-speed')) : null | |
} | |
setup() { | |
this.goToNextPage() | |
this.showButtons() | |
if (this.rotate && this.rotateSpeed > 0) { | |
this.rotateInterval = setInterval(this.goToNextPage, this.rotateSpeed) | |
} | |
} | |
events() { | |
this.nextButton.addEventListener('click', this.clickNext) | |
this.prevButton.addEventListener('click', this.clickPrev) | |
if (this.pauseButton) { | |
this.pauseButton.addEventListener('click', this.clickPause) | |
} | |
window.addEventListener('resize', debounce(this.resize, 200)) | |
} | |
bundlePages = () => { | |
this.pages = [] | |
for (let i = 0, len = this.container.childNodes.length; i < len; i++) { | |
const page = this.container.childNodes[i] | |
if (page.nodeName !== '#text') { | |
page.style.transition = 'opacity 200ms linear' | |
this.pages.push(page) | |
} | |
} | |
} | |
bundleChildPages = () => { | |
this.childPages = [] | |
for (let i = 0, len = this.childContainers.length; i < len; i++) { | |
const childContainer = this.childContainers[i] | |
let childPages = [] | |
for (let i = 0, len = childContainer.childNodes.length; i < len; i++) { | |
const childPage = childContainer.childNodes[i] | |
if (childPage.nodeName !== '#text') { | |
childPage.style.transition = 'opacity 200ms linear' | |
childPages.push(childPage) | |
} | |
} | |
this.childPages.push(childPages) | |
} | |
} | |
computePages = () => { | |
this.currentPage = -1 | |
this.currentPages = [] | |
this.pageWidth = this.pages[0].offsetWidth | |
this.pagesToShow = Math.max(Math.floor(this.containerWidth / this.pageWidth), 1) | |
this.totalPages = Math.ceil(this.pages.length / this.pagesToShow) | |
this.prevPage = 0 | |
this.nextPage = this.pagesToShow | |
} | |
computeChildPages = () => { | |
this.hasChildren = true | |
this.currentPage = 0 // bump up 1 to account for child page rotating first | |
this.currentChildPage = -1 | |
this.currentChildPages = [] | |
this.childPagesWidth = this.childPages[0][0].offsetWidth | |
this.childPagesToShow = Math.max(Math.floor(this.containerWidth / this.childPagesWidth), 1) | |
this.totalChildPages = Math.ceil(this.childPages[0].length / this.childPagesToShow) | |
this.prevChildPage = 0 | |
this.nextChildPage = this.childPagesToShow | |
} | |
showButtons = () => { | |
const morePages = this.totalPages > 1 || (this.hasChildren && this.totalChildPages && this.totalChildPages > 1) | |
// remove buttons if only one slide | |
if (morePages) { | |
this.nextButton.style.display = 'block' | |
this.prevButton.style.display = 'block' | |
if (this.pauseButton) { | |
this.pauseButton.style.display = 'block' | |
} | |
} else { | |
this.nextButton.style.display = 'none' | |
this.prevButton.style.display = 'none' | |
if (this.pauseButton) { | |
this.pauseButton.style.display = 'none' | |
} | |
} | |
} | |
resetPages = callback => { | |
for (let i=0, len=this.pages.length; i < len; i++) { | |
const page = this.pages[i] | |
page.style.display = 'block' | |
} | |
callback() | |
} | |
resetChildPages = callback => { | |
for (let i=0, ilen=this.childPages.length; i < ilen; i++) { | |
for (let j=0, jlen=this.childPages[i].length; j < jlen; j++) { | |
const childPage = this.childPages[i][j] | |
childPage.style.display = 'block' | |
} | |
} | |
callback() | |
} | |
resize = () => { | |
const newContainerWidth = this.container.offsetWidth | |
if (this.containerWidth !== newContainerWidth) { | |
this.containerWidth = this.container.offsetWidth | |
clearInterval(this.rotateInterval) | |
this.resetPages(this.computePages) | |
if (this.childContainers.length > 0) { | |
this.resetChildPages(this.computeChildPages) | |
} | |
this.setup() | |
} | |
} | |
clickPause = () => { | |
clearInterval(this.rotateInterval) | |
// .remove doesn't work in IE 11 - but we don't | |
// _really_ need to remove this anyway | |
if (this.pauseButton && this.pauseButton.remove) { | |
this.pauseButton.remove() | |
} | |
} | |
clickNext = () => { | |
this.clickPause() | |
this.goToNextPage() | |
} | |
clickPrev = () => { | |
this.clickPause() | |
this.goToPrevPage() | |
} | |
goToNextPage = () => { | |
if (this.hasChildren) { | |
if (this.currentChildPage <= 0) { | |
this.totalChildPages = Math.ceil(this.childPages[this.currentPage].length / this.childPagesToShow) | |
} | |
this.currentChildPage++ | |
if (this.currentChildPage >= this.totalChildPages) { | |
this.currentChildPage = 0 | |
this.currentPage++ | |
if (this.currentPage >= this.totalPages) { | |
this.currentPage = 0 | |
} | |
} | |
this.prevChildPage = this.childPagesToShow * this.currentChildPage | |
this.nextChildPage = this.childPagesToShow * (this.currentChildPage + 1) | |
this.updateSlides(this.childPages[this.currentPage], this.prevChildPage, this.nextChildPage) | |
} else { | |
this.currentPage++ | |
if (this.currentPage === this.totalPages) { | |
this.currentPage = 0 | |
} | |
} | |
this.prevPage = this.pagesToShow * this.currentPage | |
this.nextPage = this.pagesToShow * (this.currentPage + 1) | |
this.updateSlides(this.pages, this.prevPage, this.nextPage) | |
} | |
goToPrevPage = () => { | |
if (this.hasChildren) { | |
if (this.currentChildPage <= 0) { | |
this.currentPage-- | |
if (this.currentPage < 0) { | |
this.currentPage = this.totalPages - 1 | |
} | |
this.totalChildPages = Math.ceil(this.childPages[this.currentPage].length / this.childPagesToShow) | |
this.currentChildPage = this.totalChildPages | |
} | |
this.prevChildPage = this.childPagesToShow * this.currentChildPage | |
this.nextChildPage = this.childPagesToShow * (this.currentChildPage - 1) | |
this.currentChildPage-- | |
this.updateSlides(this.childPages[this.currentPage], this.nextChildPage, this.prevChildPage) | |
} else { | |
this.currentPage-- | |
if (this.currentPage < 0) { | |
this.currentPage = this.totalPages - 1 | |
} | |
} | |
this.prevPage = this.pagesToShow * this.currentPage | |
this.nextPage = this.pagesToShow * (this.currentPage + 1) | |
this.updateSlides(this.pages, this.prevPage, this.nextPage) | |
} | |
updateSlides = (items, firstPage, lastPage) => { | |
let currentItems = [] | |
if (lastPage <= firstPage) { | |
for (let i = firstPage; i < items.length; i++) { | |
currentItems.push(i) | |
} | |
for (let i = 0; i < lastPage; i++) { | |
currentItems.push(i) | |
} | |
} else { | |
for (let i = firstPage; i < lastPage; i++) { | |
currentItems.push(i) | |
} | |
} | |
for (let i = 0, len = items.length; i < len; i++) { | |
const item = items[i] | |
if (currentItems.indexOf(i) > -1) { | |
setTimeout(() => { | |
item.style.display = 'block' | |
}, 200) | |
setTimeout(() => { | |
item.style.opacity = '1' | |
}, 400) | |
} else { | |
item.style.opacity = '0' | |
setTimeout(() => { | |
item.style.display = 'none' | |
}, 200) | |
} | |
} | |
} | |
} | |
/* | |
Usage: | |
====== | |
<div | |
data-module="rotator" | |
data-rotator-auto="true/false" | |
data-rotator-speed="number" | |
> | |
<button data-rotator-pause></button> | |
<button data-rotator-prev></button> | |
Has width and overflow-x-auto | |
<div data-rotator-container> | |
-- items to rotate -- | |
<div data-child-rotator> | |
<div data-child-rotator-container> | |
-- child items to rotate -- | |
</div> | |
</div> | |
</div> | |
<button data-rotator-next></button> | |
</div> | |
options | |
-- | |
data-rotator-auto - auto rotate? | |
data-rotator-speed - auto rotate speed | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment