143 lines
3.6 KiB
Vue
143 lines
3.6 KiB
Vue
<template>
|
|
<div
|
|
class="mosaic-viewport"
|
|
v-intersect:once="{
|
|
callback: onIntersect,
|
|
rootMargin: '-25% 0px -25% 0px',
|
|
}"
|
|
>
|
|
<div class="mosaic-container">
|
|
<router-link
|
|
v-for="(cell, i) in content.cells"
|
|
:class="[
|
|
'mosaic-cell',
|
|
`layout-${cell.width * wWidth >= cell.height * wHeight ? 'landscape' : 'portrait'}`,
|
|
]"
|
|
:style="{
|
|
left: cell.x * 100 + 'vw',
|
|
top: cell.y * 100 + 'vh',
|
|
width: cell.width * 100 + 'vw',
|
|
height: cell.height * 100 + 'vh',
|
|
}"
|
|
:to="{
|
|
name: 'mosaic-item',
|
|
params: { id: cell.content.slug },
|
|
}"
|
|
:key="`${viewport.index}-${cell.id}`"
|
|
@mouseenter="onMouseEnter(i)"
|
|
ref="cells"
|
|
>
|
|
<div class="hover-bg" :style="{ background: hoverColors[i] }" />
|
|
<strapi-media
|
|
:image="cell.content.image"
|
|
:aspect="100"
|
|
fit="contain"
|
|
/>
|
|
</router-link>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
import gsap from 'gsap'
|
|
import { useWindowSize } from '@vueuse/core'
|
|
|
|
const THEME_S = 90
|
|
const THEME_L = 50
|
|
|
|
const props = defineProps({
|
|
viewport: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
content: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
})
|
|
|
|
const cells = ref([])
|
|
const hoverColors = ref(
|
|
cells.value.map(
|
|
() => `hsl(${Math.random() * 360}deg ${THEME_S}% ${THEME_L}%)`,
|
|
),
|
|
)
|
|
|
|
const { width: wWidth, height: wHeight } = useWindowSize()
|
|
|
|
const onMouseEnter = (i) => {
|
|
hoverColors.value[i] =
|
|
`hsl(${Math.random() * 360}deg ${THEME_S}% ${THEME_L}%)`
|
|
}
|
|
|
|
const onIntersect = (isIntersecting) => {
|
|
if (isIntersecting) {
|
|
if (!cells.value) return
|
|
|
|
const cellGifs = cells.value
|
|
.map((cell) => cell.$el?.children?.[1])
|
|
.filter(Boolean)
|
|
|
|
gsap.to(cellGifs, { opacity: 1, duration: 0, stagger: 0.1 })
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.mosaic-viewport {
|
|
height: calc(100 * var(--vh));
|
|
display: flex;
|
|
position: relative;
|
|
overflow: hidden;
|
|
|
|
.mosaic-container {
|
|
flex: 1;
|
|
position: relative;
|
|
|
|
.mosaic-cell {
|
|
position: absolute;
|
|
overflow: hidden;
|
|
border: 0.5px solid var(--theme-fg);
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
background: var(--theme-bg);
|
|
|
|
&.layout-portrait {
|
|
.strapi-media {
|
|
width: 40%;
|
|
aspect-ratio: 1;
|
|
}
|
|
}
|
|
&.layout-landscape {
|
|
.strapi-media {
|
|
height: 40%;
|
|
aspect-ratio: 1;
|
|
}
|
|
}
|
|
.hover-bg {
|
|
position: absolute;
|
|
inset: 0;
|
|
clip-path: circle(0% at 50% 50%);
|
|
transition: clip-path 0.3s ease-in-out;
|
|
}
|
|
.strapi-media {
|
|
opacity: 0;
|
|
position: relative;
|
|
z-index: 5;
|
|
}
|
|
|
|
&:hover {
|
|
.strapi-media {
|
|
@include bonk-animation(0s, 1s, 10deg);
|
|
}
|
|
.hover-bg {
|
|
clip-path: circle(100% at 50% 50%);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|