Refactored vanilla carousel
@import "../../core/fonts";
@import "../../core/colors";
@import "../../core/mixins";
$number-of-slides-carousel: 4;
$number-of-slides-content-images: 3;
.refresh-content-images {
margin-bottom: 20px;
@include breakpoint(xp) {
margin-bottom: 80px;
}
&__carousel-wrapper, &__list-wrapper {
position: relative;
}
&__arrow {
position: absolute;
top: 45%;
transform: translateY(-100%);
width: 35px;
height: 35px;
background: $white-dark;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
cursor: pointer;
z-index: 10;
&--left {
left: 0;
}
&--right {
right: 0;
}
&--disabled {
display: none;
}
@at-root .touchevents & {
display: none;
}
}
&__title {
font-family: $avantgarde-gothic;
font-size: 46px;
font-weight: 300;
text-transform: uppercase;
text-align: center;
margin-bottom: 40px;
@include breakpoint(xp) {
font-size: 82px;
}
}
&__list, &__carousel {
position: relative;
display: flex;
transition: all .3s ease-in-out;
}
&__list-item {
display: flex;
flex-direction: column;
box-sizing: border-box;
&:hover {
.refresh-content-images__info-cta {
@at-root .no-touchevents & {
a {
color: $gold-refresh;
}
}
}
img {
@include breakpoint(xp) {
opacity: 0.8;
}
}
}
img {
display: block;
height: auto;
pointer-events: none;
}
}
&__info {
font-family: $avantgarde-gothic;
text-transform: uppercase;
color: $gray-refresh;
padding: 25px;
box-sizing: border-box;
@include breakpoint(xp) {
padding: 35px;
}
}
&__info-title {
font-size: 16px;
@include breakpoint(xp) {
line-height: 24px;
font-size: 24px;
}
}
&__info-cta {
font-size: 12px;
margin-top: 20px;
@include breakpoint(xp) {
font-size: 13px;
margin-top: 25px;
}
a {
color: lighten($gray-refresh, 20%);
&,
&:hover {
@include breakpoint(xp) {
text-decoration: underline;
}
}
}
&--active {
a {
color: $gold-refresh;
}
}
}
&__list {
.refresh-content-images__list-item {
.refresh-content-images__info,
img {
width: 1024px/$number-of-slides-carousel;
@include breakpoint(sm) {
width: 1024px/$number-of-slides-content-images;
}
@include breakpoint(xp) {
width: (100vw/$number-of-slides-content-images);
}
}
}
}
&__list {
text-align: center;
}
&__carousel {
.refresh-content-images__list-item {
&:nth-child(odd) {
background-color: $white-dark;
}
.refresh-content-images__info,
img {
width: 1024px/$number-of-slides-carousel;
@include breakpoint(xp) {
width: (100vw/$number-of-slides-carousel);
}
}
.refresh-content-images__info-title {
font-size: 13px;
@include breakpoint(xp) {
font-size: 16px;
}
}
.refresh-content-images__info-copy {
margin-top: 20px;
font-family: $helvetica-light;
font-size: 12px;
text-transform: none;
font-weight: 300;
@include breakpoint(xp) {
font-size: 14px;
margin-top: 25px;
}
}
}
}
}
import lazyLoad from './lazyLoad';
export const enableArrows = (com) => {
com.rightArrow.classList.remove('refresh-content-images__arrow--disabled');
com.leftArrow.classList.remove('refresh-content-images__arrow--disabled');
};
export const disableArrow = arrow => arrow.classList.add('refresh-content-images__arrow--disabled');
export const slide = ({com, position}) => {
com.carousel.style.transform = 'translateX(' + position + 'px)'
};
export const next = ({com, position, remainingSlidesWidth}) => {
if (-position <= remainingSlidesWidth) {
enableArrows(com);
}
if (Math.ceil(-position) >= Math.ceil(remainingSlidesWidth)) {
disableArrow(com.rightArrow);
}
};
export const previous = ({com, position}) => {
if (position <= 0) {
enableArrows(com);
}
if (position === 0) {
disableArrow(com.leftArrow);
}
};
export const nextHandler = metadata => {
const {
com,
rightmostSlide,
gallery,
slideWidth,
position,
numberOfSlides,
remainingSlidesWidth
} = metadata;
if (window.innerWidth <= 1024) {
metadata.slideWidth = gallery[0].children[0].children[0].width;
metadata.remainingSlidesWidth = gallery.length * slideWidth - window.innerWidth;
}
const rightMostSlidePos = rightmostSlide * slideWidth;
const positionPlus = position - numberOfSlides * slideWidth;
if (-position < remainingSlidesWidth) {
lazyLoad(rightmostSlide, metadata);
if (-rightMostSlidePos > positionPlus) {
lazyLoad(rightmostSlide + 1, metadata);
}
if (position - slideWidth > -remainingSlidesWidth) {
metadata.position -= slideWidth;
} else {
metadata.position = -remainingSlidesWidth;
}
metadata.rightmostSlide++;
slide({com, position: metadata.position});
}
next(metadata);
};
export const previousHandler = metadata => {
const {com, position, slideWidth} = metadata;
if (position <= 0) {
metadata.rightmostSlide--;
metadata.position += slideWidth;
if (metadata.position >= 0) {
metadata.position = 0;
}
slide({com, position: metadata.position});
}
previous(metadata);
};
export default element => {
const wrapper = element.parentElement;
const position = 0;
const numberOfSlides = parseInt(element.dataset.numberOfSlides);
const slideWidth = window.innerWidth / numberOfSlides;
const rightmostSlide = numberOfSlides;
const loadedSlides = [numberOfSlides];
const com = {
carousel: element,
titleList: element.querySelectorAll('.refresh-content-images__info-title'),
leftArrow: wrapper.querySelector('.refresh-content-images__arrow--left'),
rightArrow: wrapper.querySelector('.refresh-content-images__arrow--right')
};
const gallery = Array.prototype
.slice
.call(com.carousel.children)
.filter(e => e.classList.contains('refresh-content-images__list-item'));
const remainingSlidesWidth = gallery.length * slideWidth - numberOfSlides * slideWidth;
return {
wrapper,
position,
numberOfSlides,
slideWidth,
remainingSlidesWidth,
rightmostSlide,
loadedSlides,
com,
gallery
};
};
const getSrcSet = image => {
if (!image || !image.srcset) {
return [];
}
return image.srcset.split(',').map(link => {
const [url, ratio] = link.trim().split` `;
const splitLink = url.split`/`;
return {
ratio,
width: splitLink[splitLink.length - 1]
};
});
};
export default (slide, metadata) => {
const {gallery} = metadata;
const lastLoadedSlide = metadata.loadedSlides[metadata.loadedSlides.length - 1];
const [gallerySample] = gallery[0].children[0].children;
if (slide > gallery.length - 1) {
return;
}
for (let i = lastLoadedSlide; i <= slide; i++) {
const [slideElement] = gallery[i].children,
[slideImage] = slideElement.children,
kalturaEntry = slideImage.dataset.kalturaEntry;
const sampleSrc = gallerySample.src.trim().split`/`,
sampleSrcWidth = sampleSrc[sampleSrc.length - 1];
if (!slideImage.src) {
slideImage.src = `${window.GHD.config.kalturaThumbnailURL}/${kalturaEntry}/width/${sampleSrcWidth}`;
slideImage.srcset = getSrcSet(gallerySample).map(v =>
`${window.GHD.config.kalturaThumbnailURL}/${kalturaEntry}/quality/75/width/${v.width} ${v.ratio}`
);
slideImage.sizes = gallerySample.sizes;
metadata.loadedSlides.push(i);
}
}
};
import hammer from './hammer';
import {disableArrow, enableArrows, nextHandler, previousHandler} from './moves';
import {onResizeThrottler} from '../../tools/throttler';
const resizeTitleHeights = titleList => {
const titleArray = Array.from(titleList);
const titleHeights = titleArray.map(title => title.offsetHeight);
const greatestTitleHeight = titleHeights.sort()[0];
titleArray.forEach(title => title.style.height = greatestTitleHeight + 'px');
};
const addEventListeners = metadata => {
const {com} = metadata;
com.leftArrow.addEventListener('click', () => previousHandler(metadata));
com.rightArrow.addEventListener('click', () => nextHandler(metadata));
};
export default metadata => {
const {gallery, numberOfSlides, com} = metadata;
if (gallery.length > numberOfSlides) {
enableArrows(com);
}
disableArrow(com.leftArrow);
addEventListeners(metadata);
onResizeThrottler(() => resizeTitleHeights(com.titleList), 500);
hammer(metadata);
resizeTitleHeights(com.titleList);
}
import Hammer from 'hammerjs';
import lazyLoad from './lazyLoad';
import {next, previous, slide} from './moves';
import {throttle} from '../../tools/throttler';
const markActiveSlide = (gallery, slide) => {
gallery.forEach(elem => {
const slideElemCTA = elem.children[1].children[1];
slideElemCTA.classList.remove('refresh-content-images__info-cta--active');
});
const slideElemCTA = gallery[slide].children[1].children[1];
slideElemCTA.classList.add('refresh-content-images__info-cta--active');
};
export default metadata => {
const {gallery, com, numberOfSlides, wrapper} = metadata;
const sliderManager = new Hammer.Manager(wrapper);
const reposition = e => {
const slideWidth = gallery[0].children[0].children[0].width;
const remainingSlidesWidth = -(gallery.length * slideWidth - com.carousel.offsetWidth);
let carouselPosition = (metadata.position + e.deltaX);
if (carouselPosition >= 0) {
carouselPosition = 0;
}
if (carouselPosition < remainingSlidesWidth) {
carouselPosition = remainingSlidesWidth;
}
return carouselPosition;
};
sliderManager.add(new Hammer.Pan({
threshold: 10,
pointers: 1,
direction: Hammer.DIRECTION_HORIZONTAL
}));
sliderManager.on('panleft', e => {
const slide = numberOfSlides - Math.ceil((metadata.position + e.deltaX) / gallery[0].offsetWidth);
if (e.pointerType !== 'touch') {
return;
}
metadata.rightmostSlide = slide;
lazyLoad(slide, metadata);
next(metadata);
});
sliderManager.on('panright', e => {
if (e.pointerType !== 'touch') {
return;
}
previous(metadata);
});
sliderManager.on('panleft panright', e => {
const slideWidth = gallery[0].children[0].children[0].width,
offset = slideWidth / 2,
carouselPos = reposition(e),
activeSlide = Math.floor(Math.abs(carouselPos - offset) / slideWidth);
if (e.pointerType !== 'touch' || Math.abs(e.deltaX) < Math.abs(e.deltaY)) {
return;
}
throttle(() => markActiveSlide(gallery, activeSlide));
slide({com, position: carouselPos});
});
sliderManager.on('panstart', e => {
if (e.pointerType !== 'touch') {
return;
}
com.carousel.style.transition = 'none';
});
sliderManager.on('panend', e => {
if (e.pointerType !== 'touch') {
return;
}
com.carousel.style.transition = null;
metadata.position = reposition(e);
});
}
import carousel from './carousel';
export default (function () {
document.querySelectorAll('.refresh-carousel').forEach(carousel);
})();
import metadata from './utils/metadata';
import init from './utils/init';
export default element => {
const carouselData = metadata(element);
if (!carouselData.com.carousel) {
return;
}
init(carouselData);
};