Nuxt3 – Как создать одностраничный сайт-портфолио, где сообщения открываются во всплывающем окне div (без перезагрузки страницы), но каждый проект имеет уникальный URL-адрес?

Я создаю веб-сайт-портфолио с помощью nuxt3, Tailwindcss и Wordpress REST API.

Общий вид сайта-портфолио выглядит следующим образом:

<body>
  <div id = "page-wrapper">
    <section id = "landing-image" class = "w-screen h-screen">...</section>
    <section id = "about">...</section>
    <section id = "projects-grid">...</section>
  </div>
</body>

Цель:

Я хочу, чтобы веб-сайт выглядел так, будто это всего лишь одна страница, но поддерживаю возможность нажатия на проект в сетке проекта, чтобы узнать больше о проекте. При нажатии на проект контент должен загружаться без (видимой) перезагрузки страницы. См. этот сайт-портфолио как пример сетки проекта со всплывающим окном проекта, которого я пытаюсь достичь: https://www.tessarosejacksoncomposer.com/

Что я пробовал:

1.) Один из способов, которым я думал это сделать, — добавить на страницу div, содержащий содержимое сообщения, которое скрыто попутным ветром, а nuxt отслеживает состояние. Nuxt условно устанавливает «скрытый» класс, когда сообщение должно быть видно или нет, и загружается правильный контент проекта.

app.vue

<template>
  <div id = "page-wrapper">
    <section id = "landing-image" class = "w-screen h-screen">...</section>
    <section id = "about">...</section>
    <section id = "projects-grid">...</section>
  </div>
  <div id = "project-content-wrapper" class = "absolute h-screen w-screen" :class = "{'hidden': postHidden }">
    ... (dynamically load project content and unhide by nuxt state management) ...
  </div>
</template>

Это хорошая и простая реализация, но моя главная проблема в том, что каждый проект не имеет уникального URL-адреса. Когда я хочу поделиться конкретным проектом, я хочу отправить URL-адрес, который немедленно откроет проект. Теперь URL-адрес всегда остается на главной странице. Вторая проблема заключается в том, что при использовании сайта нет истории URL-адресов, что, на мой взгляд, не интуитивно понятно.

2.) Итак, я нашел второй способ сделать это, используя вложенные страницы nuxt и компонент <NuxtPage /> (ссылка: https://v2.nuxt.com/examples/routing/nested-pages/). Моя идея заключалась в том, чтобы поместить тег <NuxtPage /> в div содержимого проекта, а затем создать вложенную страницу, которая меняется в зависимости от URL-адреса, но не перезагружает всю страницу (видимо). В каталоге pages/ я создал каталог project/, файл project.vue, а в каталоге project/ я добавил файл [...slug].vue, который содержит макет проекта и отображает содержимое. Таким образом, у моих проектов есть URL example.com/project/project-slug. Файл project.vue является лишь промежуточным файлом для получения красивого URL-адреса и содержит только шаблон с <NuxtPage /> для вложения дочерних элементов проекта.

app.vue

<template>
  <div id = "page-wrapper">
    <section id = "landing-image" class = "w-screen h-screen">...</section>
    <section id = "about">...</section>
    <section id = "projects-grid">...</section>
  </div>
  <div id = "project-content-wrapper" class = "absolute h-screen w-screen" :class = "{'hidden': postHidden }">
    <NuxtPage />
  </div>
</template>

Этот второй метод решил проблему с URL-адресом, но страница явно перезагружается и нарушает эффект одной страницы. Это связано с тем, что страница может прокручиваться, и при переходе к проекту с помощью <NuxtLink> сайт прокручивается вверх. Это становится проблемой, когда я добавляю кросс-элемент пользовательского интерфейса для закрытия всплывающего окна проекта и возврата на домашнюю страницу. При нажатии на крестик страница оказывается сверху, что заставляет пользователя снова прокрутить вниз до сетки проекта. Мне нужна иллюзия, что всплывающее окно открывается над сайтом, и при нажатии на крестик пользователь продолжает с того места, где остановился.

Вопрос:

Кто-нибудь знает способ добиться эффекта всплывающего окна сетки проекта с уникальными URL-адресами для каждого проекта, сохраняя при этом ощущение пребывания на одной и той же странице?

Вы использовали эту страницу [[index]].vue в папке страницы? Он захватит все URL-адреса страниц. поэтому, когда пользователь нажимает на одну ссылку, вы можете использовать NavigationTo('/about') или указать, какую страницу вы хотите, без перезагрузки страницы. это приведет вас к /about без перезагрузки. у вас есть только одна страница. Я могу объяснить больше, если хочешь

Mojtaba 01.03.2024 10:15

@DengSihan Да, это моя попытка 2, которую я описал выше.

Geert Roks 01.03.2024 18:40

@Mojtaba Нет, я не пробовал. Я скоро посмотрю на это. В настоящее время я помещаю все в app.vue.

Geert Roks 01.03.2024 18:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
167
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете попробовать что-то вроде этого (примечание: у вас должен быть один корневой элемент):

<template>
<main>
<div v-if = "project_list">
    <div id = "project-wrapper" v-for = "item,index in project_list" :key = "'project'+index" @click = "handle(item)">
        <section id = "landing-image" class = "w-screen h-screen">...</section>
        <section id = "about">...</section>
        <section id = "projects-grid">...</section>
    </div>
</div>
    <div id = "project-content-wrapper" class = "absolute h-screen w-screen" :class = "{'hidden': postHidden }">
    ... (dynamically load project content and unhide by nuxt state management) ...
    </div>
</main>
            
</template>
    
<script setup>
import {ref,onMounted} from 'vue'

const route = useRoute()    
const postHidden = ref(true)
const postContent = ref(null)
const { data: project_list} = await useFetch('/path/to/myapi')//return an array of projects

const handle = (item)=>{
if (!window)return
window.history.pushState(null,'',`?p=${item.slug}`)
postHidden.value = false
postContent.value = item
}

onMounted(()=>{
    if (route.query.p){
        const post = route.query.p
        const {data,error} = 
        postHidden.value = false
        project_list.forEach(item=>{
            if (item.slug == p) postContent.value = item
        }) 
    }
})

</script>

Это грубая реализация, которая, например, не поддерживает ошибки. Идея состоит в том, чтобы использовать «window.history.pushState» для изменения URL-адреса с помощью строки запроса и использовать эту строку запроса, если она существует, при загрузке страницы.

Ответ принят как подходящий

Предложения @lousolverson и @Mojtaba, а также удачная находка термина «Модальный» вместо того, что я назвал «всплывающим окном», отправили меня в кроличью нору, и в конце концов я смог получить желаемое поведение.

Мое решение основано на моей первой попытке, но с использованием navigateTo компонуемого компонента Nuxt3. Этот составной элемент — более удобный способ программного изменения URL-адреса, чем window.history.pushstate, а также не перезагружает страницу при изменении URL-адреса.

Состояние постмодального объекта сохраняется с использованием составного объекта useState, который имеет значение false, если пользователь находится на домашней странице с сеткой проектов, а модальное окно закрыто. Затем, если пользователь нажмет на ProjectCard, это состояние изменится на фрагмент выбранного проекта, и откроется projectModal с содержимым выбранного проекта.

Если кто-то получит URL-адрес, который напрямую ведет к проекту, функция onMounted() установит правильное состояние и прокрутит базовую страницу до сетки проектов. Кроме того, история URL-адресов сохраняется. Таким образом, сайт будет работать плавно, без перезагрузки страниц, но с поддержкой уникальных URL-адресов для каждого проекта.

Спасибо за предложения, и я надеюсь, что кто-то еще найдет это полезным! :)

app.vue

<script setup>
  const wordpress_host = 'http://localhost/wp-json/wp/v2'
  const { data:projects } = await useFetch(wordpress_host+'/posts');

  const route = useRoute();

  const showProjectModal = useState('showProjectModal', () => false);

  onMounted(() => {
    if (route.params.slug) {
      showProjectModal.value = route.params.slug[0];
      document.getElementById('projects').scrollIntoView({ behavior: 'smooth' });
    }
  });
</script>

<template>
  <main class = "bg-black text-white"> 
    <div id = "page-wrapper" class = "h-screen w-screen overflow-scroll" :class = "{ 'overflow-hidden blur-sm': showProjectModal }">

      <section id = "hero" class = "relative h-screen w-screen">...</section>
      <section id = "about" class = "w-screen py-12">...</section>

      <section id = "projects" class = "w-screen py-12 sm:px-2">
        <div class = "min-w-screen-lg grid 2xl:grid-cols-6 xl:grid-cols-4 md:grid-cols-3 sm:grid-cols-2 grid-cols-1">
          <PostCard v-for = "project,index in projects" :key = "'project'+index" v-bind:project = "project" v-bind:wordpress_host = "wordpress_host" />
        </div>

      </section>

    </div>
      <ProjectModal :project = "projects.find(project => { return project.slug === showProjectModal })" :wordpress_host = "wordpress_host" />
  </main>
</template>

компоненты/ProjectCard.vue

<script setup>
  const props = defineProps(['project', 'wordpress_host']);

  const showProjectModal = useState('showProjectModal', () => false);
  const openProjectModal = (slug) => {
    showProjectModal.value = slug;
    navigateTo(`/project/${slug}`);
  };
</script>

<template>
  <article>
    <div class = "relative aspect-square">
      <img :src = "..." />
      <div @click = "openProjectModal(props.project.slug)" class = "p-4 absolute h-full inset-0 bg-gray-700 cursor-pointer hover:bg-black/60"">
       ... card content overlayed on top of image ...
      </div>
    </div>
  </article>
</template>

компоненты/ProjectModal.vue

<template>
  <div>
    <transition
        enter-active-class = "transition-opacity duration-200 ease-out"
        enter-from-class = "opacity-0"
        enter-to-class = "opacity-100"
        leave-active-class = "transition-opacity duration-100 ease-in"
        leave-from-class = "opacity-100"
        leave-to-class = "opacity-0"
    >
    <div id = "projectmodal-overlay" v-show = "showProjectModal" class = "fixed inset-0 z-50 grid place-items-center w-screen h-screen bg-black/80 px-2 py-4 md:px-16 md:py-9 lg:px-32 lg:py-18">
      <div id = "projectmodal-modalbox" class = "w-full h-full m-auto flex flex-col overflow-hidden">
        <header class = "bg-gray-900 h-16 px-4">
          <button @click = "closeProjectModal()" class = "h-full text-4xl">&#10005</button>
        </header>
        <div id = "projectmodal-contentbox" v-if = "showProjectModal">
          ... <!-- v-if needed when using transitions, because the url will already change and so the content is removed before the transition is done, which may result in errors -->
        </div>
      </div>
    </div>
    </transition>
  </div>
</template>

<script setup>
  const props = defineProps(['project', 'wordpress_host']);

  const route = useRoute();
  const showProjectModal = useState('showProjectModal', () => false);
  const closeProjectModal = () => {
    showProjectModal.value = false;
    navigateTo(`/`);
  };
</script>

Другие вопросы по теме