171 lines
3.9 KiB
Vue
171 lines
3.9 KiB
Vue
<!-- components/ArticleCarousel.vue -->
|
||
<template>
|
||
<div class="carousel-wrapper">
|
||
<button class="nav-button left" @click="prev">‹</button>
|
||
<div class="carousel">
|
||
<transition-group
|
||
:name="directionClass"
|
||
tag="div"
|
||
class="carousel-inner"
|
||
>
|
||
|
||
<div
|
||
v-for="(article, index) in visibleItems"
|
||
:key="article.id + '-' + currentIndex"
|
||
class="carousel-item"
|
||
>
|
||
<ArticleCard
|
||
:header="article.header"
|
||
:image="article.image"
|
||
:link="localePath({ name: 'article-link', params: { link: article.slug } })"
|
||
:readmore-text="$t('pages.magazin.readmore')"
|
||
/>
|
||
</div>
|
||
</transition-group>
|
||
</div>
|
||
<button class="nav-button right" @click="next">›</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import ArticleCard from './ArticleCard.vue'
|
||
import { useLocalePath } from '#i18n'
|
||
|
||
const props = defineProps({
|
||
articles: {
|
||
type: Array,
|
||
required: true
|
||
}
|
||
})
|
||
|
||
const localePath = useLocalePath()
|
||
|
||
const currentIndex = ref(0)
|
||
const itemsPerView = ref(3)
|
||
const direction = ref<'left' | 'right'>('right')
|
||
|
||
const directionClass = computed(() =>
|
||
direction.value === 'left' ? 'slide-left' : 'slide-right'
|
||
)
|
||
|
||
const updateItemsPerView = () => {
|
||
const width = window.innerWidth
|
||
if (width < 600) itemsPerView.value = 1
|
||
else if (width < 1024) itemsPerView.value = 2
|
||
else itemsPerView.value = 3
|
||
}
|
||
|
||
onMounted(() => {
|
||
updateItemsPerView()
|
||
window.addEventListener('resize', updateItemsPerView)
|
||
})
|
||
|
||
const visibleItems = computed(() => {
|
||
const result = []
|
||
for (let i = 0; i < itemsPerView.value; i++) {
|
||
const index = (currentIndex.value + i) % props.articles.length
|
||
result.push(props.articles[index])
|
||
}
|
||
return result
|
||
})
|
||
|
||
function prev() {
|
||
direction.value = 'left'
|
||
currentIndex.value = (currentIndex.value - 1 + props.articles.length) % props.articles.length
|
||
}
|
||
|
||
function next() {
|
||
direction.value = 'right'
|
||
currentIndex.value = (currentIndex.value + 1) % props.articles.length
|
||
}
|
||
</script>
|
||
|
||
<style lang="sass">
|
||
.carousel-wrapper
|
||
position: relative
|
||
display: flex
|
||
align-items: center
|
||
justify-content: center
|
||
gap: 1rem
|
||
|
||
.nav-button
|
||
all: unset
|
||
cursor: pointer
|
||
font-size: 4rem
|
||
color: lighten($darkgrey, 20%)
|
||
font-weight: bold
|
||
padding: 0.5rem 1rem
|
||
border-radius: 1rem
|
||
background-image: radial-gradient(white, transparent)
|
||
transition: 0.3s
|
||
|
||
&:hover
|
||
background-image: radial-gradient($beige, transparent)
|
||
|
||
.carousel
|
||
overflow-x: clip
|
||
overflow-y: visible
|
||
max-width: 100%
|
||
flex: 1
|
||
|
||
.carousel-inner
|
||
display: flex
|
||
gap: 2rem
|
||
transition: all 0.5s ease
|
||
align-items: stretch
|
||
|
||
.carousel-item
|
||
flex: 1 0 calc(100% / 3)
|
||
min-width: 0
|
||
transition: transform 0.3s ease
|
||
display: flex
|
||
|
||
// Slide nach rechts (Nächste)
|
||
.slide-right-enter-active
|
||
transition: transform 0.6s ease, opacity 0.6s ease
|
||
|
||
.slide-right-enter-from
|
||
transform: translateX(100%) scale(0.8)
|
||
opacity: 0
|
||
|
||
.slide-right-enter-to
|
||
transform: translateX(0) scale(1)
|
||
opacity: 1
|
||
|
||
.slide-right-leave-active
|
||
transition: transform 0.4s ease
|
||
|
||
.slide-right-leave-from
|
||
transform: translateX(0)
|
||
opacity: 1
|
||
|
||
.slide-right-leave-to
|
||
transform: translateX(-100%)
|
||
opacity: 1
|
||
|
||
|
||
// Slide nach links (Vorherige)
|
||
.slide-left-enter-active
|
||
transition: transform 0.6s ease, opacity 0.6s ease
|
||
|
||
.slide-left-enter-from
|
||
transform: translateX(-100%) scale(0.8)
|
||
opacity: 0
|
||
|
||
.slide-left-enter-to
|
||
transform: translateX(0) scale(1)
|
||
opacity: 1
|
||
|
||
.slide-left-leave-active
|
||
transition: transform 0.4s ease
|
||
|
||
.slide-left-leave-from
|
||
transform: translateX(0)
|
||
opacity: 1
|
||
|
||
.slide-left-leave-to
|
||
transform: translateX(100%)
|
||
opacity: 1
|
||
|
||
</style>
|
||
|