-
-
Save tobimori/770dcfb16c76caf61eecf39252b62997 to your computer and use it in GitHub Desktop.
This file contains 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 { Block, rQS } from '@/utils' | |
import { WheelGesturesPlugin } from '@/utils/WheelGesturesPlugin' | |
import EmblaCarousel, { EmblaCarouselType } from 'embla-carousel' | |
const noop = () => undefined | |
const numberWithinRange = (number: number, min: number, max: number): number => | |
Math.min(Math.max(number, min), max) | |
export default class SlideshowGalleryBlock extends Block { | |
embla: EmblaCarouselType | |
nodes: { | |
node: HTMLElement | |
slides: EmblaCarouselType | |
thumbs: EmblaCarouselType | |
}[] = [] | |
lastLocation: number | |
onPointerUp: () => void = noop | |
locked: boolean = false | |
constructor(el?: HTMLElement) { | |
super(el) | |
this.embla = EmblaCarousel(this.el, { | |
loop: true, | |
watchDrag: () => !this.locked | |
}) | |
this.embla | |
.on('init', this.applyTween.bind(this)) | |
.on('scroll', this.applyTween.bind(this)) | |
.on('reInit', this.applyTween.bind(this)) | |
const nextBtn = rQS<HTMLButtonElement>('.slideshow-gallery__controls__arrow.is-next', this.el) | |
const prevBtn = rQS<HTMLButtonElement>('.slideshow-gallery__controls__arrow.is-prev', this.el) | |
this.addNavigation(prevBtn, nextBtn) | |
this.addPagination(rQS('.slideshow-gallery__pagination', this.el)) | |
this.lastLocation = 0 | |
this.el | |
.querySelectorAll<HTMLElement>('.slideshow-gallery__slideshow') | |
.forEach(this.addChildrenEmbla.bind(this)) | |
} | |
addChildrenEmbla(node: HTMLElement) { | |
const slidesEmbla = EmblaCarousel(rQS('.slideshow-gallery__slideshow__primary', node), { | |
loop: true | |
}) | |
const thumbsEmbla = EmblaCarousel( | |
rQS('.slideshow-gallery__slideshow__thumbs', node), | |
{ | |
loop: false, | |
axis: 'y', | |
dragFree: true, | |
align: 'start', | |
containScroll: 'keepSnaps', | |
watchSlides: false, | |
breakpoints: { | |
'(max-width: 63.9375rem)': { | |
axis: 'x' | |
} | |
} | |
}, | |
[WheelGesturesPlugin()] | |
) | |
this.addThumbsClickHandler(slidesEmbla, thumbsEmbla) | |
this.addThumbsStateHandler(slidesEmbla, thumbsEmbla) | |
// lock when swiping | |
slidesEmbla.on('pointerDown', () => (this.locked = true)) | |
slidesEmbla.on('pointerUp', () => (this.locked = false)) | |
thumbsEmbla.on('pointerDown', () => (this.locked = true)) | |
thumbsEmbla.on('pointerUp', () => (this.locked = false)) | |
this.nodes.push({ | |
node, | |
slides: slidesEmbla, | |
thumbs: thumbsEmbla | |
}) | |
} | |
addThumbsClickHandler(main: EmblaCarouselType, thumbs: EmblaCarouselType) { | |
const slidesThumbs = thumbs.slideNodes() | |
const scrollToIndex = slidesThumbs.map((_, index) => (): void => { | |
main.scrollTo(index) | |
}) | |
thumbs.containerNode().addEventListener( | |
'click', | |
(e) => { | |
slidesThumbs.forEach((node, index) => { | |
if (node.contains(e.target as Node)) scrollToIndex[index]() | |
}) | |
}, | |
true | |
) | |
} | |
addThumbsStateHandler(main: EmblaCarouselType, thumbs: EmblaCarouselType) { | |
const slidesThumbs = thumbs.slideNodes() | |
const toggleThumbBtnsState = (): void => { | |
thumbs.scrollTo(main.selectedScrollSnap()) | |
const previous = main.previousScrollSnap() | |
const selected = main.selectedScrollSnap() | |
slidesThumbs[previous].classList.remove('is-active') | |
slidesThumbs[selected].classList.add('is-active') | |
} | |
main.on('select', toggleThumbBtnsState) | |
thumbs.on('init', toggleThumbBtnsState) | |
} | |
addNavigation = (prevBtn: HTMLElement, nextBtn: HTMLElement) => { | |
prevBtn.addEventListener('click', () => this.embla.scrollPrev(), false) | |
nextBtn.addEventListener('click', () => this.embla.scrollNext(), false) | |
} | |
addPagination = (dotsNode: HTMLElement) => { | |
let dotNodes: HTMLElement[] = [] | |
const addPaginationButtons = (): void => { | |
dotsNode.innerHTML = this.embla | |
.scrollSnapList() | |
.map(() => '<button class="slideshow-gallery__pagination__dot" type="button"></button>') | |
.join('') | |
dotNodes = Array.from(dotsNode.querySelectorAll('.slideshow-gallery__pagination__dot')) | |
dotNodes.forEach((dotNode, index) => { | |
dotNode.addEventListener('click', () => this.embla.scrollTo(index), false) | |
}) | |
} | |
const togglePagination = (): void => { | |
const previous = this.embla.previousScrollSnap() | |
const selected = this.embla.selectedScrollSnap() | |
dotNodes[previous].classList.remove('is-active') | |
dotNodes[selected].classList.add('is-active') | |
} | |
this.embla | |
.on('init', addPaginationButtons) | |
.on('reInit', addPaginationButtons) | |
.on('init', togglePagination) | |
.on('reInit', togglePagination) | |
.on('select', togglePagination) | |
.on('destroy', () => { | |
dotsNode.innerHTML = '' | |
}) | |
} | |
calculateTweenValues(factor: number): number[] { | |
const engine = this.embla.internalEngine() | |
const scrollProgress = this.embla.scrollProgress() | |
return this.embla.scrollSnapList().map((scrollSnap, index) => { | |
if (!this.embla.slidesInView().includes(index)) return 0 | |
let diffToTarget = scrollSnap - scrollProgress | |
engine.slideLooper.loopPoints.forEach((loopItem) => { | |
const target = loopItem.target().get() | |
if (index === loopItem.index && target !== 0) { | |
const sign = Math.sign(target) | |
if (sign === -1) diffToTarget = scrollSnap - (1 + scrollProgress) | |
if (sign === 1) diffToTarget = scrollSnap + (1 - scrollProgress) | |
} | |
}) | |
const tweenValue = 1 - Math.abs(diffToTarget * factor) | |
return numberWithinRange(tweenValue, 0, 1) | |
}) | |
} | |
applyTween() { | |
if (this.locked) return | |
const tweenNodes = this.embla.slideNodes() | |
const tweenValues = this.calculateTweenValues(3) | |
tweenValues.forEach((tweenValue, index) => { | |
const node = tweenNodes[index] | |
node.style.opacity = tweenValue.toString() | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment