Як додати динамічні секції в Nuxtjs3

Іноді не хочеться підключати тисячі компонентів, та кожен з них виводити в template. Хочеться зробити так, щоб сторінка рендерилася явтоматично, а код був чистий та свіжий. В цьому матеріалі я хочу показати вам, як просто створити динамічні секції, які навіть можуть підгружатися з анімацією, та або lazy

Спочатку подивимося на template, який ми створимо pages/index.vue:

<template lang="pug">
.main-page
  vsection(v-for="(item, ind) in sections" :data="item" :key="'section-'+ind")
</template>

<script setup>
const sections = [
  {  id: "headblock", name: "Age", data: "some data" },
  {  id: "ourgames", name: "Our Games" },
  {  id: "market", name: "Market" },
  {  id: "tokensale", name: "Tokensale" },
  {  id: "news", name: "News" },
]
</script>

Це буде template всієї нашої сторінки, як ви бачите все достатньо чисто та гармонічно.

Тепер поговоримо, як створити компонент динамічних секцій, та як підгружати секції components/vsection.vue:

<template lang="pug">
comp.vsection(:id="data.id" :data="data?.data)
</template>

<script setup>
const props = defineProps({
  data: Object
})

const comp = defineAsyncComponent(() => import(`@/components/sections/${props.data.id}.vue`))
</script>

В цьому прикладі ми використовуємо defineAsyncComponent, який імпортує існуючі компоненти секцій з папки sections. Таким чином можна легко створити систему динамічних секцій. Біль того, як ви бачите ми можете передавати data з масиву головної сторінки. Тобто ви легко можете підвантажувати дані на головній, та передавати їх всередену секцій прямо через масив:

<script setup>
const fetchedData = await useFetch('/url');

const sections = [
  {  id: "headblock", name: "Age", data: fetchedData?.data },
  {  id: "ourgames", name: "Our Games" },
  //...
]
</script>

Lazy Load секцій, та анімація підвантаження:

Тепер розглянемо варіант, коли нам потрібно додати анімації плавного підвантаження секцій. Для цього ми будемо використовувати intersection observer, він буде виконувати дію, коли наша секція буле потрапляти в поле зору вікна браузера:

<template lang="pug">
comp.vsection(:id="data.id" ref="sectionRef" :class="{'obs-active': observerClass}")
</template>

<script setup>
const props = defineProps({
  data: Object
})

const comp = defineAsyncComponent(() => import(`@/components/sections/${props.data.id}.vue`))

const sectionRef = ref(null);
const observerClass = ref(null)
let observer;

onMounted(() => {
  observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        observerClass.value = true
      } else {
        observerClass.value = false
      }
    });
  }, { threshold: 0.1, rootMargin: '0% -20% 0% -20%' });
  observer.observe(sectionRef.value.$el);
});

onBeforeUnmount(() => {
  if(observer) observer.disconnect()
})
</script>

<style lang="stylus">
.vsection
  py 70px
  opacity 0
  tr .55 opacity
  &.obs-active
    opacity 1
</style>

Дивлячись на код, зверніть увагу. Ми додали ref="sectionRef" для того, щоб опрацювати секцію через observer, який ініціалізується в onMounted та знищується в onBeforeUnmount. Так як observer є проксируючивм івентом, який вішається на window, то при переході на іншу сторінку він нікуде не пропаде, якщо його не знищити, та буде їсти памʼять клієнта. Тому знищення дуже важливе, бо якщо користувач 10 разів змінить сторінку, то він заініціаліється 10 разів, від чого браузер може лягти.

Так, як observer є DOM методом, то він не буде доступний в setup, оскільки на момент завантаження setup, DOM ще не існує, тому ініціалізувати його потрібно в onMounted, але знищувати нам його потрібно в іншому методі. Щоб справитися з цією проблемою, ми створили пусту змінну в setup - let observer; Та назначаємо і знищуємо з неї observer вже в потрібних методах. 

sectionRef - це посилання на эллементу DOM, на який ми вже вішаємо наш observer.observe(sectionRef.value.$el), в середені котрого ми просто додаємо, або видаляємо клас з секції

Анімацію я показв просту, проста поява через opacity, але ви можете вигадити будь що. Я намагався показати масимально простий код, але ви можете його вдосконалити виходячи в ваших потреб.

Також хочу показати як вимкнути анімацію для деяких секцій. Для цього в масиві секцій передамо додаткову змінну:

<script setup>
const sections = [
  {  id: "headblock", name: "Age", data: "some data", static: true },
  // ...
]
</script>

І в коді секції:

1. Додамо умову для класу:

<template lang="pug">
comp.vsection(:id="data.id" ref="sectionRef" :class="{'obs-active': observerClass || data.static}")
</template>

2. Відключему ініціалізації обсерверу, якщо змінна передана:

onMounted(() => {
  if(props.data.static) return
  observer = new IntersectionObserver((entries) => {
    // ...

На цьому все, ось так просто можна реалізувати динамічні сеції в Nuxtjs 3