我目前正在开发一个网站,遇到了一个小问题。
===
重现步骤:
1.
npm create svelte@latest test
(骨架、打字稿、eslint-prettier-playwright-vitest)
2.
cd test
3.
npm i
4.
npm run dev -- --open
(在浏览器中打开进行调试)
5.创建
src/components/Navbar.svelte
,包含以下内容:
<script lang="ts">
import { onMount } from 'svelte';
function toggleHamburgerMenu() {
var x = document.getElementById('myLinks') as HTMLDivElement;
if (x.style.display === 'block') {
x.style.display = 'none';
} else {
x.style.display = 'block';
}
}
function getTranslatedPagePath(): string {
switch (window.location.pathname) {
case '/':
return 'nav.about';
case '/services':
return 'nav.services';
case '/updates':
return 'nav.updates';
case '/photos':
return 'nav.photos';
case '/contact':
return 'nav.contact';
case '/links':
return 'nav.links';
default:
return '';
}
}
$: currentTab = 'nav.about';
onMount(() => {
currentTab = getTranslatedPagePath();
function closeTopnav(this: HTMLAnchorElement) {
var x = document.getElementById('myLinks') as HTMLDivElement;
x.style.display = 'none';
currentTab = this.innerHTML;
}
// topnav doesnt close automatically when clicking on a link
const links = document.getElementsByClassName('topnav_link') as unknown as HTMLAnchorElement[];
for (let i = 0; i < links.length; ++i) {
links[i].addEventListener('click', closeTopnav);
}
// hide topnav when clicking outside
document.addEventListener('click', (e: MouseEvent) => {
var x = document.getElementById('topnav-wrapper') as HTMLDivElement;
var y: Node = e.target as Node;
var z = document.getElementById('myLinks') as HTMLDivElement;
if (!x.contains(y) && z.style.display == 'block') {
z.style.display = 'none';
}
});
});
</script>
<head>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
/>
</head>
<div id="topnav-wrapper">
<div>
<a href="/"><img src="" alt="Logo" /></a>
</div>
<div class="topnav">
<div id="content">
<a href="" class="active">{currentTab}</a>
<a href="" class="icon" on:click={toggleHamburgerMenu}>
<i class="fa fa-bars"></i>
</a>
</div>
<div id="myLinks">
{#each 'about, services, updates, photos, contact, links'.split(', ') as link}
{#if `nav.${link}` !== currentTab}
<a class="topnav_link" href="/{link === 'about' ? '' : link}">{`nav.${link}`}</a>
{/if}
{/each}
</div>
</div>
</div>
<style>
#topnav-wrapper {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
overflow: visible;
z-index: 100;
/* background-color: rgba(255, 255, 255, 0.6); */
margin: 0;
padding: 0;
}
.topnav {
overflow: hidden;
/* background-color: #333; */
/* width: fit-content; */
/* height: fit-content; */
margin: 1.5rem;
}
/* Hide the links inside the navigation menu (except for logo/home) */
.topnav #myLinks {
display: none;
position: absolute;
top: inherit;
left: inherit;
background-color: #333;
pointer-events: all;
}
/* Style navigation menu links */
.topnav a {
color: white;
padding: 14px 16px;
text-decoration: none;
display: block;
/* width: fit-content; */
background-color: rgba(255, 255, 255, 0.74);
color: black;
font-size: 1.5rem;
}
/* Style the hamburger menu */
.topnav a.icon {
background: black;
display: block;
}
/* Add a grey background color on mouse-over */
.topnav a:hover {
background-color: #ddd;
color: black;
}
/* Style the active link (or home/logo) */
.active {
background-color: rgb(135, 206, 235);
color: white;
}
img {
position: absolute;
top: 0;
left: 0;
}
#content {
display: flex;
}
#logo {
background-color: white;
opacity: 70%;
}
@media screen and (min-width: 320px) {
#logo,
img {
width: 115px;
height: 50px;
margin-left: 15px;
}
img {
top: 20px;
}
}
@media screen and (min-width: 520px) {
#logo,
img {
width: 135px;
height: 55px;
}
img {
top: 20px;
}
}
@media screen and (min-width: 720px) {
#logo,
img {
width: 155px;
height: 65px;
margin-top: 10px;
}
img {
top: 15px;
}
}
@media screen and (min-width: 920px) {
#logo,
img {
width: 175px;
height: 70px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1120px) {
#logo,
img {
width: 195px;
height: 75px;
margin-top: 20px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1320px) {
#logo,
img {
width: 215px;
height: 80px;
}
img {
margin-top: 5px;
}
}
@media screen and (min-width: 1520px) {
#logo,
img {
width: 235px;
height: 90px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1720px) {
#logo,
img {
width: 255px;
height: 100px;
margin-top: 20px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1920px) {
#logo,
img {
width: 275px;
height: 110px;
margin-top: 20px;
}
img {
top: -10px;
}
}
img {
top: 0px; /* TODO: responsive */
margin-top: 0px;
}
#content a {
background-color: transparent;
color: black;
font-size: 1.5rem;
}
</style>
6.创建
src/routes/+layout.svelte
:
<script>
import Navbar from '../components/Navbar.svelte';
</script>
<Navbar />
<slot />
7.创建
/(about), services, updates, photos, contact, links
路线,使用简单的h1
提及页面标题,以便轻松了解我们所在的路线。
8.转到浏览器选项卡,打开右上角的汉堡菜单,单击每个链接。之后,如果您尝试单击其中任何一个,标题将不会更新,链接列表将不会更新并删除当前页面链接,并且汉堡菜单将不会关闭(但我们将转到正确的页面)。
===
我遇到的问题是,在导航栏中的每个链接第一次迭代之后,它们会被缓存(或者类似的东西,我不太确定 svelte 的底层机制),当我尝试再次单击它们时:
这两个问题的共同点是 currentTab,我试图将其设为一个简单的 var 来查看它是否改变任何内容,但它没有改变,所以我有点没有想法了。
有人知道如何解决这个问题吗?
预期:
<a>
链接点击时更新,因此标题和链接列表更新(并且汉堡菜单关闭)。好吧,首先感谢您提供的非常容易重现的示例和说明,它很有帮助。我将带您解决这些问题,然后留下我自己的意见。
您在
.content
div 中使用带有空 href 属性的 nchor 标签作为按钮,这会触发 SvelteKit 中的导航事件,这可能是问题的一部分。我的建议是简单地将其切换为简单的按钮并相应地设置它们的样式。
关于仅在第一次迭代中运行的问题有两个问题。首先,在 closeTopnav()
回调中声明的 onMount
函数可能会使其在首次调用后无法访问,因此我将其移至顶层。其次,在 onMount
中的每个链接上附加事件侦听器时的回调似乎是核心问题。通过使用链接本身中的 on:click
Svelte 处理程序调用该函数,可以轻松解决此问题,这是您的代码,其中包含我更改的内容的注释。
<script lang="ts">
import { onMount } from 'svelte';
function toggleHamburgerMenu() {
var x = document.getElementById('myLinks') as HTMLDivElement;
if (x.style.display === 'block') {
x.style.display = 'none';
} else {
x.style.display = 'block';
}
}
function closeTopnav(thisTab:string) {
var x = document.getElementById('myLinks') as HTMLDivElement;
x.style.display = 'none';
// I usually avoid .innerHTML and opt for .textContent, but here you can pass the string iself as an argument just like the name of the link itself
currentTab = thisTab;
}
let currentTab:string = 'nav.about';
onMount(() => {
//scoped this function to the onMount since this function is only used to find the path when it's mounted
function getTranslatedPagePath(): string {
switch (window.location.pathname) {
case '/':
return 'nav.about';
case '/services':
return 'nav.services';
case '/updates':
return 'nav.updates';
case '/photos':
return 'nav.photos';
case '/contact':
return 'nav.contact';
case '/links':
return 'nav.links';
default:
return '';
}
}
currentTab = getTranslatedPagePath();
// hide topnav when clicking outside
document.addEventListener('click', (e: MouseEvent) => {
var x = document.getElementById('topnav-wrapper') as HTMLDivElement;
var y: Node = e.target as Node;
var z = document.getElementById('myLinks') as HTMLDivElement;
if (!x.contains(y) && z.style.display == 'block') {
z.style.display = 'none';
}
});
});
</script>
<head>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
/>
</head>
<div id="topnav-wrapper">
<div>
<a href="/"><img src="" alt="Logo" /></a>
</div>
<div class="topnav">
<div id="content">
<div class="active">{currentTab}</div>
<!-- changed href-less anchors to button -->
<button class="icon" on:click={toggleHamburgerMenu}>
<i class="fa fa-bars"></i>
</button>
</div>
<div id="myLinks">
{#each 'about, services, updates, photos, contact, links'.split(', ') as link}
{#if `nav.${link}` !== currentTab}
<!-- added click handler in the link itself, passing the concatenated string as argument -->
<a class="topnav_link" href="/{link === 'about' ? '' : link}"
on:click={() => {closeTopnav(`nav.${link}`)}}
>
{`nav.${link}`}
</a>
{/if}
{/each}
</div>
</div>
</div>
<style>
#topnav-wrapper {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100%;
overflow: visible;
z-index: 100;
/* background-color: rgba(255, 255, 255, 0.6); */
margin: 0;
padding: 0;
}
.topnav {
overflow: hidden;
/* background-color: #333; */
/* width: fit-content; */
/* height: fit-content; */
margin: 1.5rem;
}
/* Hide the links inside the navigation menu (except for logo/home) */
.topnav #myLinks {
display: none;
position: absolute;
top: inherit;
left: inherit;
background-color: #333;
pointer-events: all;
}
/* Style navigation menu links */
/* added button selector */
.topnav button, .topnav a {
color: white;
padding: 14px 16px;
text-decoration: none;
display: block;
border: none;
/* width: fit-content; */
background-color: rgba(255, 255, 255, 0.74);
color: black;
font-size: 1.5rem;
}
/* Style the hamburger menu */
/* changed to button selector */
.topnav button.icon {
background: black;
display: block;
}
/* Add a grey background color on mouse-over */
/* changed to button selector */
.topnav button:hover {
background-color: #ddd;
color: black;
}
/* Style the active link (or home/logo) */
.active {
background-color: rgb(135, 206, 235);
color: white;
}
img {
position: absolute;
top: 0;
left: 0;
}
#content {
display: flex;
}
#logo {
background-color: white;
opacity: 70%;
}
@media screen and (min-width: 320px) {
#logo,
img {
width: 115px;
height: 50px;
margin-left: 15px;
}
img {
top: 20px;
}
}
@media screen and (min-width: 520px) {
#logo,
img {
width: 135px;
height: 55px;
}
img {
top: 20px;
}
}
@media screen and (min-width: 720px) {
#logo,
img {
width: 155px;
height: 65px;
margin-top: 10px;
}
img {
top: 15px;
}
}
@media screen and (min-width: 920px) {
#logo,
img {
width: 175px;
height: 70px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1120px) {
#logo,
img {
width: 195px;
height: 75px;
margin-top: 20px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1320px) {
#logo,
img {
width: 215px;
height: 80px;
}
img {
margin-top: 5px;
}
}
@media screen and (min-width: 1520px) {
#logo,
img {
width: 235px;
height: 90px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1720px) {
#logo,
img {
width: 255px;
height: 100px;
margin-top: 20px;
}
img {
top: 10px;
}
}
@media screen and (min-width: 1920px) {
#logo,
img {
width: 275px;
height: 110px;
margin-top: 20px;
}
img {
top: -10px;
}
}
img {
top: 0px; /* TODO: responsive */
margin-top: 0px;
}
/* changed to button selector */
#content button {
background-color: transparent;
color: black;
font-size: 1.5rem;
}
</style>
IMO,您在 Svelte 为您提供良好替代方案的地方使用 Vanilla JS,这绝对没有问题,但生活可以更好。其中一些是在 onMount 中使用 for 循环手动附加事件监听器,甚至可以通过在顶层声明相同的函数(如
function hideTopnavWhenClickingOutside() {...}
)并调用 Svelte 的文档伪元素来处理文档事件监听器,如下所示:
<svelte:document on:click={hideTopnavWhenClickingOutside} />
但是学习 Svelte 的一大乐趣在于意识到它可以使开发变得多么容易和快捷,所以我希望这会有所帮助,并且它会让您的编码之旅变得越来越愉快!