<template> <div> <div class="gallery-thumbs__spacer"></div> <ul ref="galleryThumbsList" class="gallery-thumbs__list list-shadow"> <li v-for="(gallery, galleryIndex) in galleries" :key="galleryIndex" :class="{ 'is-active': activeRow === galleryIndex }" class="gallery-thumbs__row-container"> <ul ref="galleryThumbsRows" class="gallery-thumbs__row" :style="{ 'transform': 'translate3d(' + thumbRowOffsets[galleryIndex] + 'px, 0, 0)' }"> <li class="featured-image-spacer mobile-only" :style="{ 'height': featuredHeight }"> <ThumbNav class="featured-nav mobile-only" direction="right" @navClick="handleNavClick(galleryIndex, 'right')" /> </li> <li v-for="(image, imageIndex) in gallery.images" :ref="galleryIndex == 0 && imageIndex == 0 ? 'thumbContainer' : null" class="thumb-container thumb-overlay selected-indicator" :class="{ 'is-active': imageIndex === activeIndex }" :style="{ 'background-image': 'url(' + image.thumbUrl + ')' }" :key="galleryIndex + '.' + imageIndex + '.' + image.url" @click="$emit('thumbClick', galleryIndex, imageIndex)"> <ThumbNav class="thumb-nav thumb-nav--left mobile-only" direction="left" @navClick="handleNavClick(galleryIndex, 'left')" /> <ThumbNav v-if="imageIndex < gallery.images.length - 1" class="thumb-nav mobile-only" direction="right" @navClick="handleNavClick(galleryIndex, 'right')" /> </li> </ul> </li> <ThumbNav class="thumb-nav thumb-nav--left mobile-hide shadow-deco" :class="{ 'is-active': isLeftDesktopNavActive }" direction="left" @navClick="handleNavClickDesktop(activeRow, 'left')"/> <ThumbNav class="thumb-nav thumb-nav--right mobile-hide shadow-deco" :class="{ 'is-active': isRightDesktopNavActive }" direction="right" @navClick="handleNavClickDesktop(activeRow, 'right')"/> </ul> </div> </template> <script> import ThumbNav from '@/components/ThumbNav' export default { components: { ThumbNav }, props: { galleries: { type: Array, required: true }, featuredHeight: { type: String, required: true }, activeRow: { type: Number, required: true }, // index of image to be highlighted activeIndex: { type: Number, required: false, default () { return 0 }, }, // scroll this image into view showImage: { type: Number, required: false, default () { return 0 }, }, }, data () { return { thumbRowOffsets: new Array(this.galleries.length), isLeftDesktopNavActive: false, resizeIsThrottling: false, isThumbsScrolling: false, touchDownPosition: null } }, computed: { thumbsVerticalOffset () { let rowHeight = this.$refs.thumbContainer ? this.$refs.thumbContainer[0].clientHeight : 0; return rowHeight * this.activeRow }, isRightDesktopNavActive () { return this.thumbRowOffsets[this.activeRow] > 0 } }, watch: { activeRow: function(val) { this.$nextTick(() => { this.updateElements(false) }) }, showImage () { this.scrollToThumb(this.showImage) } }, mounted () { window.addEventListener('resize', this.handleResize) this.$refs.galleryThumbsList.addEventListener('touchstart', this.handleTouch, { passive: true }) this.$refs.galleryThumbsList.addEventListener('touchend', this.handleTouch, { passive: true }) this.$refs.galleryThumbsList.addEventListener('wheel', this.handleThumbsScroll, { passive: true }) // firefox this.$refs.galleryThumbsList.addEventListener('mousewheel', this.handleThumbsScroll, { passive: true }) this.$nextTick(function () { this.updateElements() this.scrollToThumb(this.activeIndex) }) }, methods: { // TOOD move logic of getting sizes of elements into getter methods scrollToThumb (thumbIndex) { if (thumbIndex >= this.galleries[this.activeRow].images.length || thumbIndex < 0) { return } const containerWidth = this.$refs.galleryThumbsList.clientWidth const thumbWidth = this.$refs.thumbContainer[0].clientWidth let offset = this.thumbRowOffsets[this.activeRow] const thumbPosition = thumbWidth * (this.galleries[this.activeRow].images.length - thumbIndex) const rowWidth = this.$refs.galleryThumbsRows[this.activeRow].clientWidth if (containerWidth < thumbPosition - offset) { this.$set(this.thumbRowOffsets, this.activeRow, thumbPosition - containerWidth + 1) this.isLeftDesktopNavActive = containerWidth + offset > rowWidth } else if (thumbPosition <= offset) { this.$set(this.thumbRowOffsets, this.activeRow, thumbPosition - thumbWidth) this.isLeftDesktopNavActive = containerWidth + offset > rowWidth } }, updateElements (resetOffsets=true) { if (resetOffsets) {this.resetOffsets()} if (this.$refs.galleryThumbsRows[this.activeRow] && this.$refs.galleryThumbsList) { let totalRowWidth = this.$refs.galleryThumbsRows[this.activeRow].clientWidth let visibleRowWidth = this.$refs.galleryThumbsList.clientWidth this.initLeftNav(totalRowWidth, visibleRowWidth) if (visibleRowWidth > totalRowWidth) { this.centerThumbs(totalRowWidth, visibleRowWidth) } } }, initLeftNav (totalRowWidth, visibleRowWidth) { let offset = this.thumbRowOffsets[this.activeRow] let maxOffset = totalRowWidth - visibleRowWidth this.isLeftDesktopNavActive = (totalRowWidth > visibleRowWidth) && (offset < maxOffset) }, resetOffsets() { for (let index = 0; index < this.thumbRowOffsets.length; index++) { this.$set(this.thumbRowOffsets, index, 0) } }, centerThumbs(rowWidth, visibleWidth) { let offset = (visibleWidth - rowWidth) / -2 this.$set(this.thumbRowOffsets, [this.activeRow], offset) }, handleNavClick(source, direction) { let offset = this.thumbRowOffsets[source] const thumbContainerWidth = this.$refs.thumbContainer[0].clientWidth if (direction === 'left') { offset += thumbContainerWidth } else if (direction === 'right') { offset -= thumbContainerWidth } offset = offset > 0 ? 0 : offset this.$set(this.thumbRowOffsets, source, offset) }, handleNavClickDesktop(source, direction) { let offset = this.thumbRowOffsets[source] let thumbContainerWidth = this.$refs.thumbContainer[0].clientWidth let visibleRowWidth = this.$refs.galleryThumbsList.clientWidth let totalRowWidth = this.$refs.galleryThumbsRows[source].clientWidth let maxOffset = totalRowWidth - visibleRowWidth if (direction === 'left') { offset += thumbContainerWidth } else if (direction === 'right') { offset -= thumbContainerWidth if (offset != 0) { this.isLeftDesktopNavActive = true } } if (offset < 0) { offset = 0 } else if (offset >= maxOffset) { offset = maxOffset this.isLeftDesktopNavActive = false } this.$set(this.thumbRowOffsets, source, offset) }, handleResize() { if (!this.resizeIsThrottling) { this.resizeIsThrottling = true this.$nextTick(() => { this.updateElements() this.resizeIsThrottling = false }) } }, handleThumbsScroll(event) { if (event.wheelDeltaX) { if (event.wheelDeltaX > 0) { this.doThumbScroll('right') } else if (event.wheelDeltaX < 0) { this.doThumbScroll('left') } } else if (event.deltaY) { if (event.deltaY > 0) { this.doThumbScroll('left') } else if (event.deltaY < 0) { this.doThumbScroll('right') } } }, handleTouch(event) { if (event.type === 'touchstart') { this.touchDownPosition = event.changedTouches[0].clientX } else if (event.type === 'touchmove') { event.preventDefault() } else if (event.type === 'touchend') { let xPos = event.changedTouches[0].clientX let direction = this.touchDownPosition - xPos < 0 ? 'left' : 'right' this.doThumbScroll(direction) } }, doThumbScroll(direction) { if (this.isThumbsScrolling) { return } this.isThumbsScrolling = true if ((direction === 'left' && this.isLeftDesktopNavActive) || (direction === 'right' && this.isRightDesktopNavActive)) { this.handleNavClickDesktop(this.activeRow, direction) } this.$nextTick(() => { this.isThumbsScrolling = false }) } } } </script> <style lang="scss" scoped> .gallery-thumbs__row { display: flex; flex-direction: row; list-style: none; padding: 0; margin: 0; } .thumb-container { background-size: cover; background-position: center center; overflow: hidden; padding: 0 2px; background-clip: content-box; cursor: pointer; } @media (max-width: $bp__layout) { .gallery-thumbs__spacer { display:none; } .gallery-thumbs__row { position: relative; transition : transform .5s; } .featured-image-spacer { position: relative; height: calc(50vh - #{$site-menu__header-height / 2}) !important; // must override inline style set with prop width: 100vw; flex: 0 0 100vw; } .thumb-container { position: relative; height: calc(50vh - #{$site-menu__header-height / 2}); width: 100vw; background-size: cover; background-position: center center; } .featured-nav { z-index: 20; position: absolute; width: 100%; height: 100%; right: 0; top: 0; display: flex; align-items: flex-end; justify-content: flex-end; padding-left: 80%; padding-right: 16px; padding-bottom: 16px; opacity: .3; } .thumb-nav { z-index: 20; position: absolute; width: 8rem; height: 8rem; bottom: 1rem; right: 8px; padding: 0 1rem 0 3rem; display: flex; align-items: flex-end; opacity: .4; &--left { left: 8px; right: auto; padding: 0 3rem 0 1rem; } } .mobile-hide { display: none; } } @media (min-width: $bp__layout) { .mobile-only { display: none; } .mobile-hide { display: initial; } .featured-image-spacer { width: 100%; } .thumb-container { position: relative; z-index: 1; height: $gallery-thumbs-height--compact; width: $gallery-thumbs-height--compact * 1.6; flex: 0 0 $gallery-thumbs-height--compact * 1.6; } .gallery__thumbs { width: 100%; } .gallery-thumbs__list { position: relative; height: $gallery-thumbs-height--compact; flex: 0 0 $gallery-thumbs-height--compact; bottom: 8px; overflow: hidden; transition: opacity .3s; // TEMP } .list-shadow { $color: $color__neutral-100; box-shadow: 0 -3px 33px -9px $color, 0 0 8px -3px $color; background-color: rgba($color, .7); } .gallery-thumbs__row-container { position: absolute; width: 100%; height: 100%; transition: opacity .5s; opacity: 0; pointer-events: none; &.is-active { transition: opacity .5s .3s; opacity: 1; pointer-events: auto; } } .gallery-thumbs__row { position: absolute; height: 100%; top: 0; right: 0; transition: transform .5s; } .thumb-nav { position: absolute; top: 1rem; width: $gallery-thumbs-height--compact - 2rem; height: $gallery-thumbs-height--compact - 2rem; transition: opacity .5s; opacity: 0; pointer-events: none; &.is-active { opacity: 1; pointer-events: auto; } &--left { left: -.5rem; } &--right { right: -.5rem; } } } @media (min-width: $bp__gallery-compact) { .gallery-thumbs__list { height: $gallery-thumbs-height; flex: 0 0 $gallery-thumbs-height; } .thumb-container { height: $gallery-thumbs-height; width: $gallery-thumbs-height * 1.6; flex: 0 0 $gallery-thumbs-height * 1.6; } .gallery-thumbs__list { height: $gallery-thumbs-height; flex: 0 0 $gallery-thumbs-height; } .thumb-nav { width: $gallery-thumbs-height - 2rem; height: $gallery-thumbs-height - 2rem; } } </style>