<template> <div class="scroll"> <Expand v-if="expanded" :expanded="expanded" class="expand-light" @expand="(state) => $emit('expand', state)" /> <Expand v-if="expanded" :expanded="expanded" class="expand-dark" @expand="(state) => $emit('expand', state)" /> <div class="scrollable"> <div v-show="enabled && !expanded" class="scroll-button scroll-left noselect" :class="{ 'scroll-start': scrollAtStart }" @click="scroll('left')" ><Icon icon="arrow-left3" /></div> <div ref="content" class="scroll-content" > <slot :loaded="loaded" /> </div> <div v-show="enabled && !expanded" class="scroll-button scroll-right noselect" :class="{ 'scroll-end': scrollAtEnd }" @click="scroll('right')" ><Icon icon="arrow-right3" /></div> </div> <Expand v-if="expanded || (expandable && scrollable)" :expanded="expanded" class="expand-light" @expand="(state) => $emit('expand', state)" /> <Expand v-if="expanded || (expandable && scrollable)" :expanded="expanded" class="expand-dark" @expand="(state) => $emit('expand', state)" /> <button v-if="album && items && items.length > 0 && scrollable" class="album-toggle" @click="showAlbum = true" ><Icon icon="grid3" />View album</button> </div> </template> <script> import Expand from '../expand/expand.vue'; function updateScroll() { this.scrollable = this.$refs.content.scrollWidth > this.$refs.content.clientWidth; this.scrollAtStart = this.$refs.content.scrollLeft === 0; this.scrollAtEnd = this.$refs.content.scrollWidth - this.$refs.content.clientWidth === this.$refs.content.scrollLeft; } function scroll(direction) { if (direction === 'right') { this.$refs.content.scrollLeft = this.$refs.content.scrollLeft + this.$refs.content.clientWidth - 100; } if (direction === 'left') { this.$refs.content.scrollLeft = this.$refs.content.scrollLeft - this.$refs.content.clientWidth + 100; } } function loaded(_event) { // typically triggered by slotted component when an image loads, affecting scrollWidth this.updateScroll(); } function mounted() { this.$refs.content.addEventListener('scroll', () => this.updateScroll()); window.addEventListener('resize', this.updateScroll); this.updateScroll(); } function beforeUnmount() { this.$refs.content.removeEventListener('scroll', this.updateScroll); window.removeEventListener('resize', this.updateScroll); } function updated() { this.updateScroll(); } export default { components: { Expand, }, props: { enabled: { type: Boolean, default: true, }, expandable: { type: Boolean, default: false, }, expanded: { type: Boolean, default: false, }, album: { type: Boolean, default: false, }, items: { type: Array, default: null, }, }, data() { return { scrollable: true, scrollAtStart: true, scrollAtEnd: false, }; }, mounted, updated, beforeUnmount, methods: { scroll, loaded, updateScroll, }, }; </script> <style lang="scss" scoped> @import 'breakpoints'; .scroll.expanded { padding: 0; .scroll { display: none; } } .scrollable { position: relative; } .scroll-content { overflow-x: scroll; scroll-behavior: smooth; scrollbar-width: none; &::-webkit-scrollbar { display: none; } } .scroll-button { height: 100%; display: flex; align-items: center; box-sizing: border-box; position: absolute; top: 0; bottom: 0; z-index: 10; &.scroll-start, &.scroll-end { /* use over v-show so button stays visible while still hovered */ display: none; } &.scroll-start { left: 0; } &.scroll-end { right: 0; } .icon { width: 1.5rem; height: 1.5rem; fill: var(--lighten); } &.scroll-start .icon, &.scroll-end .icon { fill: var(--lighten-weak); } &:hover:not(.scroll-start):not(.scroll-end) .icon { fill: var(--lighten-strong); } &:hover { display: flex; cursor: pointer; } } .scroll-left { left: 0; padding: 1rem 2rem 1rem .5rem; } .scroll-right { right: 0; padding: 1rem .5rem 1rem 2rem; } .scroll .expand-light { display: none; } .scroll-light { .expand-light { display: block; } .expand-dark { display: none; } } .scroll-dark .expand-light { display: none; } @media(max-width: $breakpoint-micro) { /* buttons block swiping motion */ .scroll-button { display: none; } } </style>