<div class="gallery-slider">
<div class="gallery-slider__rail">
<div class="gallery-slider__rail-inner">
<div class="grid grid--no-vertical-gutter gallery-slider__grid animation__target">
<div class="grid__col grid__col--xs-3 grid__col--sm-5 grid__col--md-4 gallery-slider__grid-col">
<figure class="image image--fluid image--overlay gallery-slider__image">
<img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20530%20530%22%3E%3C%2Fsvg%3E" data-srcset="//placehold.it/527x393" data-sizes="auto" alt="" class="image__img lazyload">
<span class="image__overlay"></span>
</figure>
</div>
<div class="grid__col grid__col--xs-3 grid__col--sm-5 grid__col--md-4 gallery-slider__grid-col">
<figure class="image image--fluid image--overlay gallery-slider__image">
<img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20530%20530%22%3E%3C%2Fsvg%3E" data-srcset="//placehold.it/666x746" data-sizes="auto" alt="" class="image__img lazyload">
<span class="image__overlay"></span>
</figure>
</div>
<div class="grid__col grid__col--xs-3 grid__col--sm-5 grid__col--md-4 gallery-slider__grid-col">
<figure class="image image--fluid image--overlay gallery-slider__image">
<img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20530%20530%22%3E%3C%2Fsvg%3E" data-srcset="//placehold.it/527x556" data-sizes="auto" alt="" class="image__img lazyload">
<span class="image__overlay"></span>
</figure>
</div>
<div class="grid__col grid__col--xs-3 grid__col--sm-5 grid__col--md-4 gallery-slider__grid-col">
<figure class="image image--fluid image--overlay gallery-slider__image">
<img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20530%20530%22%3E%3C%2Fsvg%3E" data-srcset="//placehold.it/666x592" data-sizes="auto" alt="" class="image__img lazyload">
<span class="image__overlay"></span>
</figure>
</div>
<div class="grid__col grid__col--xs-3 grid__col--sm-5 grid__col--md-4 gallery-slider__grid-col">
<figure class="image image--fluid image--overlay gallery-slider__image">
<img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20530%20530%22%3E%3C%2Fsvg%3E" data-srcset="//placehold.it/527x393" data-sizes="auto" alt="" class="image__img lazyload">
<span class="image__overlay"></span>
</figure>
</div>
<div class="grid__col grid__col--xs-3 grid__col--sm-5 grid__col--md-4 gallery-slider__grid-col">
<figure class="image image--fluid image--overlay gallery-slider__image">
<img loading="lazy" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20530%20530%22%3E%3C%2Fsvg%3E" data-srcset="//placehold.it/666x746" data-sizes="auto" alt="" class="image__img lazyload">
<span class="image__overlay"></span>
</figure>
</div>
</div>
</div>
</div>
</div>
<div class="gallery-slider">
<div class="gallery-slider__rail">
<div class="gallery-slider__rail-inner">
<div class="grid grid--no-vertical-gutter gallery-slider__grid animation__target">
{% if data.items %}
{% for item in data.items %}
<div class="grid__col grid__col--xs-3 grid__col--sm-5 grid__col--md-4 gallery-slider__grid-col">
{% include '@image' with {class: "gallery-slider__image", modifier: "image--fluid image--overlay", data: item|srcset('530x530f')} %}
</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
{
"language": "en-US",
"data": {
"items": [
{
"srcset": "//placehold.it/527x393"
},
{
"srcset": "//placehold.it/666x746"
},
{
"srcset": "//placehold.it/527x556"
},
{
"srcset": "//placehold.it/666x592"
},
{
"srcset": "//placehold.it/527x393"
},
{
"srcset": "//placehold.it/666x746"
}
]
}
}
.gallery-slider__rail {
position: relative;
cursor: grab; /* stylelint-disable-line plugin/no-unsupported-browser-features */
overflow: hidden;
width: var(--app-width);
}
.gallery-slider__rail-inner {
overflow-x: scroll;
margin: 0 0 -20px;
padding-bottom: 20px;
@include bp(sm-min) {
padding-right: calc(100vw / 7); /* stylelint-disable-line plugin/no-unsupported-browser-features */
}
@include bp(xxl-min) {
padding-right: calc(var(--app-width) - 1920px + (1920px / 7)); /* stylelint-disable-line plugin/no-unsupported-browser-features */
}
}
.gallery-slider__grid {
flex-wrap: nowrap;
}
.gallery-slider__grid-col {
pointer-events: none;
}
.gallery-slider__image {
overflow: hidden;
pointer-events: none;
transform: scale(1);
transform-origin: center top;
transition-property: transform;
transition-timing-function: $transition-easing-in;
transition-duration: $transition-duration;
will-change: transform; /* stylelint-disable-line plugin/no-unsupported-browser-features */
.gallery-slider.is-dragging & {
transform: scale(.9);
transition-timing-function: $transition-easing-out;
}
}
.image__img {
.gallery-slider & {
width: 100%;
}
}
import Component from '../component/component';
import Helpers from '../helpers/helpers';
import '../grid/grid';
import './gallery-slider.scss';
interface IGallerySliderSettings {
dragClass: string;
}
export default class GallerySlider extends Component {
static initSelector: string = '.gallery-slider';
settings: IGallerySliderSettings;
draggableArea: JQuery;
mouseDown: boolean;
mouseMoved: boolean;
startX: number;
dragSpeedMultiplier: number = 1.1;
velX: number;
momentumID: number;
animationDurationMultiplier: number = .93; // must be a value between 0 and 1;
constructor(element: HTMLElement) {
super(element);
this.settings = $.extend({
dragClass: 'is-dragging',
});
this.draggableArea = this.element.find('.gallery-slider__rail-inner');
this.mouseDown = false;
this.init();
}
init(): void {
$(window).on('mousemove', this.scroll.bind(this));
$(window).on('mouseup', this.dragEndHandler.bind(this));
$(window).on('mouseout', this.dragEndHandler.bind(this));
this.draggableArea.on('mousedown', this.dragStartHandler.bind(this));
this.draggableArea.on('touchstart', () => {
this.addDraggingClass();
});
this.draggableArea.on('touchend', () => {
this.removeDraggingClass();
});
}
dragEndHandler(): void {
this.mouseDown = false;
if (!Helpers.isMobileDevice) {
this.beginMomentumTracking();
}
this.removeDraggingClass();
}
dragStartHandler(event: MouseEvent): void {
this.mouseMoved = false;
this.mouseDown = true;
this.startX = event.pageX - this.draggableArea.offset().left;
if (!Helpers.isMobileDevice) {
this.cancelMomentumTracking();
}
this.addDraggingClass();
}
scroll(event: MouseEvent): void {
if (this.mouseDown && event !== undefined) {
this.mouseMoved = true;
const x: number = event.pageX - this.draggableArea.offset().left;
const walk: number = (x - this.startX);
// Store the previous scroll position
const prevScrollLeft: number = this.draggableArea.scrollLeft();
this.draggableArea.scrollLeft(this.draggableArea.scrollLeft() - walk * this.dragSpeedMultiplier);
this.startX = x;
// Compare change in position to work out drag speed
this.velX = this.draggableArea.scrollLeft() - prevScrollLeft;
}
}
addDraggingClass(): void {
this.element.addClass(this.settings.dragClass);
}
removeDraggingClass(): void {
this.element.removeClass(this.settings.dragClass);
}
beginMomentumTracking(): void {
this.cancelMomentumTracking();
this.momentumID = requestAnimationFrame(this.momentumLoop.bind(this));
}
cancelMomentumTracking(): void {
cancelAnimationFrame(this.momentumID);
}
momentumLoop(): void {
this.draggableArea.scrollLeft(this.draggableArea.scrollLeft() + this.velX);
this.velX *= this.animationDurationMultiplier;
if (Math.abs(this.velX) > 0.5) {
this.momentumID = requestAnimationFrame(this.momentumLoop.bind(this));
}
}
}