<section class="ef-sub-navigation">
<div class="sub-navigation-tiles">
<div class="sub-navigation-tiles__section">
<a class="navigation-tile" href="/">
<div class="navigation-tile__bg-image" style="background-image: url(/assets/example-content/tile-background-text.jpg);"></div>
<div class="navigation-tile__title">Latest News</div>
</a>
<a class="navigation-tile" href="/">
<div class="navigation-tile__bg-image" style="background-image: url(/assets/example-content/tile-background-text.jpg);"></div>
<div class="navigation-tile__title">Match Highlights</div>
</a>
<a class="navigation-tile" href="/">
<div class="navigation-tile__bg-image" style="background-image: url(/assets/example-content/tile-background-text.jpg);"></div>
<div class="navigation-tile__title">Find Football</div>
</a>
<a class="navigation-tile" href="/">
<div class="navigation-tile__bg-image" style="background-image: url(/assets/example-content/tile-background-text.jpg);"></div>
<div class="navigation-tile__title">Grassroots Football</div>
</a>
<a class="navigation-tile" href="/">
<div class="navigation-tile__bg-image-only" style="background-image: url(/assets/example-content/tile-background.jpg);"></div>
</a>
</div>
</div>
</section>
No notes defined.
{
"sub-navigation-title": false,
"tiles": [
{
"title": "Latest News",
"link": "/",
"alttext": "Latest News"
},
{
"title": "Match Highlights",
"link": "/",
"alttext": "Match Highlights"
},
{
"title": "Find Football",
"link": "/",
"alttext": "Find Football"
},
{
"title": "Grassroots Football",
"link": "/",
"alttext": "Grassroots Football"
},
{
"without-text": "true",
"link": "/",
"alttext": "Football Image"
}
]
}
/** SCROLLING PARALLAX FUNCTIONALITY
* Creates a horizontal parallax effect on image child elements
* Takes the following options:
* scrollContainer: the parent element that scrolls
* items: the items that should should have a parallaxed image
* minXPosition / maxX position: The distance which the image should be able to travel
*
* * */
export default class HorizontalScrollParallax {
constructor({
scrollContainer,
itemElements,
maxXPosition = 20,
minXPosition = -20,
}) {
this.maxXPosition = maxXPosition;
this.minXPosition = minXPosition;
this.scrollContainer = scrollContainer;
this.items = Array.from(itemElements).map(item => {
const img = item.querySelector('img');
// Adding a hack to stop jumpiness from debouncing the animation
// TODO: This could be replaced with something like VirtualScroll
// https://github.com/ayamflow/virtual-scroll
// TODO: This check for `img` is a stopgap to allow the JS to continue running if
// an image is missing. But, this may require more thought about what to do with the
// parallax when there are images missing.
if (img) {
img.style.transition = `transformx 0.1s`;
}
const { clientWidth, offsetLeft } = item;
const bounds = item.getBoundingClientRect();
// The positions at which the item should stop animating
// We are going to calculate an x position based on how far it is between these walls
const leftWall = bounds.left - window.innerWidth;
const rightWall = bounds.right;
return { bounds, clientWidth, offsetLeft, img, leftWall, rightWall };
});
this.parallaxRunning = false;
}
parallax() {
const lastScrollPosition = this.scrollContainer.scrollLeft;
window.requestAnimationFrame(() => {
const itemsInView = this.items.filter(
item =>
lastScrollPosition + window.innerWidth >= item.bounds.left &&
lastScrollPosition < item.bounds.right
);
itemsInView.forEach(({ leftWall, rightWall, img }) => {
// Calculate how far the item is along the screen
const percentage =
((lastScrollPosition - leftWall) * 100) / (rightWall - leftWall);
// The distance which the image should be able to travel
const x =
(percentage / 100) * (this.minXPosition - this.maxXPosition) +
this.maxXPosition;
if (img) {
// eslint-disable-next-line no-param-reassign
img.style.transform = `translateX(${x}px) `;
}
});
});
}
initParallax() {
if (this.parallaxRunning) {
return;
}
this.parallaxRunning = true;
this.parallax();
this.scrollContainer.addEventListener('scroll', () => this.parallax());
}
destroyParallaxScrolling() {
if (!this.parallaxRunning) {
return;
}
this.parallaxRunning = false;
this.scrollContainer.removeEventListener('scroll', () => this.parallax());
Array.from(this.items).forEach(item => {
// eslint-disable-next-line no-param-reassign
item.img.style.transform = `translateX(0)`;
});
}
}
import debounce from 'lodash.debounce';
import HorizontalScrollParallax from './scrolling-parallax';
export default ({ parentElement, maxScreenWidth = 820 }) => {
const tiles = parentElement.querySelectorAll('.navigation-tile');
// Set up the parallex
const tilesList = new HorizontalScrollParallax({
scrollContainer: parentElement,
itemElements: tiles,
});
if (parentElement.clientWidth < maxScreenWidth) {
tilesList.initParallax();
}
// Create the parallax when smaller than the max screensize
// Destroy any running parallax when the screen is bigger
window.addEventListener(
'resize',
debounce(() => {
if (parentElement.clientWidth >= maxScreenWidth) {
tilesList.destroyParallaxScrolling();
return;
}
tilesList.initParallax();
}, 250)
);
};
.sub-navigation-tiles {
overflow-x: auto;
max-width: 100vw;
&__section {
column-gap: 2rem;
display: flex;
overflow-x: scroll;
padding: 0 1.5rem 0.5rem 1.5rem;
.navigation-tile {
flex-shrink: 0;
}
}
@media screen and (min-width: $mq-medium) {
overflow-x: unset;
&__section {
display: flex;
column-gap: 3.6rem;
justify-content: center;
overflow-x: unset;
padding: 0;
.navigation-tile {
flex-shrink: initial;
}
}
}
}
@media screen and (max-width: $mq-medium) {
.ef-sub-navigation {
display: inherit;
}
}
.common-template__row-container,
.common-template__news-container {
@media screen and (max-width: $mq-medium) {
.sub-navigation-tiles__section {
padding: 0 0 0.5rem 0;
}
}
}
<section class="ef-sub-navigation">
<div class="sub-navigation-tiles">
{{#if sub-navigation-title}}
{{render '@optional-title'}}
{{/if}}
<div class="sub-navigation-tiles__section">
{{#each tiles}}
{{render '@navigation-tiles' this merge="true"}}
{{/each}}
</div>
</div>
</section>