viewport cells intersect animation
This commit is contained in:
parent
4b79a5af0f
commit
a99dfdb8ff
4 changed files with 63 additions and 3 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="infinite-scroll-container">
|
<div class="infinite-scroll-container">
|
||||||
<transition-group name="fade" tag="div" class="viewport-stage">
|
<div class="viewport-stage">
|
||||||
<div
|
<div
|
||||||
v-for="viewport in visibleViewports"
|
v-for="viewport in visibleViewports"
|
||||||
class="viewport"
|
class="viewport"
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</transition-group>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -97,6 +97,7 @@ watch(currentViewportIndex, (newIndex, oldIndex) => {
|
||||||
emit('viewport-change', { newIndex, oldIndex })
|
emit('viewport-change', { newIndex, oldIndex })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Expose to parent
|
||||||
defineExpose({
|
defineExpose({
|
||||||
reset,
|
reset,
|
||||||
retryViewport,
|
retryViewport,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mosaic-viewport">
|
<div
|
||||||
|
class="mosaic-viewport"
|
||||||
|
v-intersect:once="{
|
||||||
|
callback: onIntersect,
|
||||||
|
rootMargin: '-25% 0px -25% 0px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div class="mosaic-container">
|
<div class="mosaic-container">
|
||||||
<router-link
|
<router-link
|
||||||
v-for="cell in content.cells"
|
v-for="cell in content.cells"
|
||||||
|
|
@ -18,6 +24,7 @@
|
||||||
params: { id: cell.content.slug },
|
params: { id: cell.content.slug },
|
||||||
}"
|
}"
|
||||||
:key="`${viewport.index}-${cell.id}`"
|
:key="`${viewport.index}-${cell.id}`"
|
||||||
|
ref="cells"
|
||||||
>
|
>
|
||||||
<strapi-media
|
<strapi-media
|
||||||
:image="cell.content.image"
|
:image="cell.content.image"
|
||||||
|
|
@ -30,6 +37,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import gsap from 'gsap'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
viewport: {
|
viewport: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -40,6 +50,20 @@ const props = defineProps({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const cells = ref([])
|
||||||
|
|
||||||
|
const onIntersect = (isIntersecting) => {
|
||||||
|
if (isIntersecting) {
|
||||||
|
if (!cells.value) return
|
||||||
|
|
||||||
|
const cellGifs = cells.value
|
||||||
|
.map((cell) => cell.$el?.children?.[0])
|
||||||
|
.filter(Boolean)
|
||||||
|
|
||||||
|
gsap.to(cellGifs, { opacity: 1, duration: 0, stagger: 0.1 })
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -76,6 +100,7 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
.strapi-media {
|
.strapi-media {
|
||||||
mix-blend-mode: darken;
|
mix-blend-mode: darken;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,15 @@ import App from './App.vue'
|
||||||
import { ViteSSG } from 'vite-ssg'
|
import { ViteSSG } from 'vite-ssg'
|
||||||
import routes from './routes'
|
import routes from './routes'
|
||||||
import strapiPlugin from './plugins/strapi'
|
import strapiPlugin from './plugins/strapi'
|
||||||
|
import gsapPlugin from './plugins/gsap'
|
||||||
|
import intersectPlugin from './plugins/intersect'
|
||||||
import { strapi } from '@strapi/client'
|
import { strapi } from '@strapi/client'
|
||||||
|
|
||||||
export const createApp = ViteSSG(App, routes, ({ app }) => {
|
export const createApp = ViteSSG(App, routes, ({ app }) => {
|
||||||
// Plugins
|
// Plugins
|
||||||
app.use(strapiPlugin)
|
app.use(strapiPlugin)
|
||||||
|
app.use(gsapPlugin)
|
||||||
|
app.use(intersectPlugin)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Prerender mosaic item routes
|
// Prerender mosaic item routes
|
||||||
|
|
|
||||||
30
src/plugins/intersect.js
Normal file
30
src/plugins/intersect.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { useIntersectionObserver } from '@vueuse/core'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install: (app) => {
|
||||||
|
app.directive('intersect', {
|
||||||
|
mounted(el, binding) {
|
||||||
|
const { stop } = useIntersectionObserver(
|
||||||
|
el,
|
||||||
|
([{ isIntersecting }]) => {
|
||||||
|
binding.value?.callback?.(isIntersecting)
|
||||||
|
|
||||||
|
if (isIntersecting) {
|
||||||
|
el.classList.add('intersected')
|
||||||
|
|
||||||
|
if (binding.arg === 'once') {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
el.classList.remove('intersected')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: binding.value?.threshold || 0,
|
||||||
|
rootMargin: binding.value?.rootMargin || '0px',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue