Я создаю веб-сайт-портфолио с помощью 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-адресами для каждого проекта, сохраняя при этом ощущение пребывания на одной и той же странице?
@DengSihan Да, это моя попытка 2, которую я описал выше.
@Mojtaba Нет, я не пробовал. Я скоро посмотрю на это. В настоящее время я помещаю все в app.vue.





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