CSS导航和菜单组件
这是一个简单的导航。当导航没有足够的空间时(例如在手机上),带有白色渐变的溢出指示器(在右边和左边)会显示出来,表示菜单是可以滚动的。如果你不想要这些指示器,你可以把它们删除。不要忘了把它们的javascript和CSS也删除。如果菜单上有任何活动项目,它就会自动滚动到视图中。
<!--
stylify-variables
color: '#FFA585'
/stylify-variables
-->
<nav class="
js-nav
position:relative overflow:hidden
[.nav-overflow-indicator]{width:32px;transition:.3s;will-change:transform;height:100%;display:flex;position:absolute;top:0}
">
<div class="
s-hidden
js-nav-overflow-indicator nav-overflow-indicator
left:0 background:linear-gradient(90deg,#fff,rgba(255,255,255,0))
[&.s-hidden]{transform:translateX(-50px)}
"></div>
<div class="
js-nav-scrollbar
display:flex gap:16px overflow:auto
[a]{text-decoration:none;color:#000;font-weight:bold;white-space:nowrap}
[a:hover,a.s-selected]{color:$color}
">
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#" class="s-selected">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
</div>
<div class="
s-hidden
js-nav-overflow-indicator nav-overflow-indicator
right:0 background:linear-gradient(-90deg,#fff,rgba(255,255,255,0))
[&.s-hidden]{transform:translateX(50px)}
"></div>
</nav>
<script>
const scrollLinkIntoView = (link) => {
// TODO this is disabled because it cause scroll on Stylify web
// Uncomment this so the selected item scrolls into the view
//link.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
};
const getNavScrollBar = (nav) => nav.querySelector('.js-nav-scrollbar');
const getNavScrollIndicators = (nav) => {
const indicators = nav.querySelectorAll('.js-nav-overflow-indicator');
return {
left: indicators[0],
right: indicators[1]
}
}
const indicatorHiddenClass = 's-hidden';
const scrollIndicatorLimitToHide = 24;
const toggleNavScrollIndicators = (nav, scrollBar) => {
scrollBar = scrollBar ?? getNavScrollBar(nav);
const navWidth = parseInt(window.getComputedStyle(nav).width.match(/\d+/));
const scrollBarScrollWidth = scrollBar.scrollWidth;
const indicators = getNavScrollIndicators(nav);
if (scrollBarScrollWidth <= navWidth) {
indicators.left.classList.add(indicatorHiddenClass);
indicators.right.classList.add(indicatorHiddenClass);
return;
}
const leftOverflow = scrollBar.scrollLeft;
indicators.left.classList.toggle(indicatorHiddenClass, leftOverflow <= scrollIndicatorLimitToHide);
indicators.right.classList.toggle(
indicatorHiddenClass,
scrollBarScrollWidth - leftOverflow <= navWidth + scrollIndicatorLimitToHide
);
}
const navigations = document.querySelectorAll('.js-nav');
navigations.forEach((nav) => {
const scrollBar = getNavScrollBar(nav);
const indicators = getNavScrollIndicators(nav);
nav.querySelectorAll('a').forEach((link) => link.addEventListener('click', () => scrollLinkIntoView(link)));
for (const indicator of Object.values(indicators)) {
indicator.addEventListener('touchstart', () => indicator.classList.add(indicatorHiddenClass));
indicator.addEventListener('mouseover', () => indicator.classList.add(indicatorHiddenClass));
indicator.addEventListener('mouseout', () => toggleNavScrollIndicators(nav, scrollBar));
}
scrollBar.addEventListener('scroll', () => toggleNavScrollIndicators(nav, scrollBar));
toggleNavScrollIndicators(nav);
});
window.addEventListener('load', () => {
document.querySelectorAll('.s-selected').forEach((link) => scrollLinkIntoView(link));
});
window.addEventListener('resize', () => navigations.forEach((nav) => toggleNavScrollIndicators(nav)));
</script>
<!--
stylify-variables
color: '#FFA585'
/stylify-variables
stylify-components
navigation: `
position:relative overflow:hidden
.nav-overflow-indicator {
width:32px transition:.3s will-change:transform height:100% display:flex position:absolute top:0
}
.nav-overflow-indicator--left {
left:0 background:linear-gradient(90deg,#fff,rgba(255,255,255,0))
&.s-hidden {transform:translateX(-50px)}
}
.nav-overflow-indicator--right {
right:0 background:linear-gradient(-90deg,#fff,rgba(255,255,255,0))
&.s-hidden { transform:translateX(50px) }
}
.nav-scrollbar {
display:flex gap:16px overflow:auto
a {
text-decoration:none color:#000 font-weight:bold white-space:nowrap
&:hover, &.s-selected { color:$color }
}
}
`
/stylify-components
-->
<nav class="js-nav navigation position:relative overflow:hidden">
<div class="s-hidden js-nav-overflow-indicator nav-overflow-indicator nav-overflow-indicator--left"></div>
<div class="js-nav-scrollbar nav-scrollbar">
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#" class="s-selected">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
<a href="#">Link</a>
</div>
<div class="s-hidden js-nav-overflow-indicator nav-overflow-indicator nav-overflow-indicator--right"></div>
</nav>
<script>
const scrollLinkIntoView = (link) => {
link.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
};
const getNavScrollBar = (nav) => nav.querySelector('.js-nav-scrollbar');
const getNavScrollIndicators = (nav) => {
const indicators = nav.querySelectorAll('.js-nav-overflow-indicator');
return {
left: indicators[0],
right: indicators[1]
}
}
const indicatorHiddenClass = 's-hidden';
const scrollIndicatorLimitToHide = 24;
const toggleNavScrollIndicators = (nav, scrollBar) => {
scrollBar = scrollBar ?? getNavScrollBar(nav);
const navWidth = parseInt(window.getComputedStyle(nav).width.match(/\d+/));
const scrollBarScrollWidth = scrollBar.scrollWidth;
const indicators = getNavScrollIndicators(nav);
if (scrollBarScrollWidth <= navWidth) {
indicators.left.classList.add(indicatorHiddenClass);
indicators.right.classList.add(indicatorHiddenClass);
return;
}
const leftOverflow = scrollBar.scrollLeft;
indicators.left.classList.toggle(indicatorHiddenClass, leftOverflow <= scrollIndicatorLimitToHide);
indicators.right.classList.toggle(
indicatorHiddenClass,
scrollBarScrollWidth - leftOverflow <= navWidth + scrollIndicatorLimitToHide
);
}
const navigations = document.querySelectorAll('.js-nav');
navigations.forEach((nav) => {
const scrollBar = getNavScrollBar(nav);
const indicators = getNavScrollIndicators(nav);
nav.querySelectorAll('a').forEach((link) => link.addEventListener('click', () => scrollLinkIntoView(link)));
for (const indicator of Object.values(indicators)) {
indicator.addEventListener('touchstart', () => indicator.classList.add(indicatorHiddenClass));
indicator.addEventListener('mouseover', () => indicator.classList.add(indicatorHiddenClass));
indicator.addEventListener('mouseout', () => toggleNavScrollIndicators(nav, scrollBar));
}
scrollBar.addEventListener('scroll', () => toggleNavScrollIndicators(nav, scrollBar));
toggleNavScrollIndicators(nav);
});
window.addEventListener('load', () => {
document.querySelectorAll('.s-selected').forEach((link) => scrollLinkIntoView(link));
});
window.addEventListener('resize', () => navigations.forEach((nav) => toggleNavScrollIndicators(nav)));
</script>
简单的移动菜单,在点击汉堡包图标后打开。
<!--
stylify-variables
color: '#C81D77'
/stylify-variables
-->
Click to open the navigation
<a role="button" class="
js-mobile-nav-toggle
display:inline-flex align-items:center justify-content:center width:42px height:42px border-radius:50px cursor:pointer background:$color transition:background_.3s
hover:background:lighten($color,20)
focus:background:lighten($color,20)
">
<svg viewBox="0 0 100 80" fill="#fff" width="16" height="16">
<rect width="100" height="20" rx="10"></rect>
<rect y="30" width="100" height="20" rx="10"></rect>
<rect y="60" width="100" height="20" rx="10"></rect>
</svg>
</a>
<aside class="
js-mobile-nav s-hidden
will-change:transform
transition:transform_.3s display:flex align-items:flex-end position:fixed top:0 right:0 flex-direction:column width:100vw height:100vh z-index:2
[&.s-hidden]{transform:translateX(100%)}
">
<div class="js-mobile-nav-obfuscator position:absolute inset:0 background:rgba(0,0,0,.5)"></div>
<nav role="navigation" class="
display:flex flex-direction:column position:relative
background:#fff height:100vh width:70% max-width:300px
">
<div class="position:absolute width:100% border-bottom:1px_solid_#eee z-index:2 top:0 left:0 padding:12px display:flex justify-content:space-between align-items:center background:rgba(255,255,255,0.8) backdrop-filter:blur(3px)">
<a href="#" aria-label="Home Button" class="display:flex">
<img src="https://stylifycss.com/images/logo/horizontal.svg" loading="lazy" fetchpriority="low" width="100" height="28">
</a>
<a role="button" aria-label="Close Menu" class="cursor:pointer align-self:flex-end js-mobile-nav-toggle">
<svg width="40" height="40">
<path d="m10 10 20 20m0-20L10 30" stroke="#000" stroke-width="4"/>
</svg>
</a>
</div>
<div class="
gap:5% display:flex justify-content:flex-end
[a]{word-break:break-word;font-weight:bold;white-space:normal;text-decoration:none;color:#000;padding:8px_0;hover:color:$color}
[a:not(:last-of-type)]{border-bottom:1px_solid_#eee}
[ul]{list-style:none}
[li::before]{color:orange}
flex-direction:column gap:12px height:100vh padding:84px_12px_12px_12px overflow:auto justify-content:flex-start
">
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
</div>
</nav>
</aside>
<script>
const mobileNavHiddenClass = 's-hidden';
const mobileNavClassPrefix = 'js-mobile-nav';
const toggleMobileNavigationVisibility = () => {
const headerNavigation = document.querySelector(`.${mobileNavClassPrefix}`);
if (headerNavigation?.classList.contains(mobileNavHiddenClass)) {
headerNavigation.classList.remove(mobileNavHiddenClass);
} else {
headerNavigation?.classList.add(mobileNavHiddenClass);
}
}
document.querySelector(`.${mobileNavClassPrefix}-obfuscator`).addEventListener('click', () => toggleMobileNavigationVisibility());
document.querySelectorAll(`.${mobileNavClassPrefix}-toggle`).forEach((button) => {
button.addEventListener('click', () => toggleMobileNavigationVisibility());
});
</script>
<!--
stylify-variables
color: '#C81D77'
/stylify-variables
stylify-components
'mobile-navigation': `
will-change:transform
transition:transform_.3s display:flex align-items:flex-end position:fixed top:0 right:0 flex-direction:column width:100vw height:100vh z-index:2
&.s-hidden { transform:translateX(100%) }
`,
'mobile-navigation__header': `
position:absolute width:100% border-bottom:1px_solid_#eee z-index:2 top:0 left:0 padding:12px display:flex justify-content:space-between align-items:center background:rgba(255,255,255,0.8) backdrop-filter:blur(3px)
`,
'mobile-navigation__header-close': 'cursor:pointer align-self:flex-end',
'mobile-navigation__links': `
gap:5% display:flex justify-content:flex-end flex-direction:column gap:12px height:100vh padding:84px_12px_12px_12px overflow:auto justify-content:flex-start
a { word-break:break-word font-weight:bold white-space:normal text-decoration:none color:#000 padding:8px_0 hover:color:$color }
a:not(:last-of-type) { border-bottom:1px_solid_#eee }
ul { list-style:none }
li::before { color:orange }
`,
'mobile-navigation-hamburger': `
display:inline-flex align-items:center justify-content:center width:42px height:42px border-radius:50px cursor:pointer background:$color transition:background_.3s
hover:background:lighten($color,20)
focus:background:lighten($color,20)
`
/stylify-components
-->
Click to open the navigation
<a role="button" class="js-mobile-nav-toggle mobile-navigation-hamburger">
<svg viewBox="0 0 100 80" fill="#fff" width="16" height="16">
<rect width="100" height="20" rx="10"></rect>
<rect y="30" width="100" height="20" rx="10"></rect>
<rect y="60" width="100" height="20" rx="10"></rect>
</svg>
</a>
<aside class="js-mobile-nav s-hidden mobile-navigation">
<div class="js-mobile-nav-obfuscator position:absolute inset:0 background:rgba(0,0,0,.5)"></div>
<nav role="navigation" class="
display:flex flex-direction:column position:relative
background:#fff height:100vh width:70% max-width:300px
">
<div class="mobile-navigation__header">
<a href="#" aria-label="Home Button" class="display:flex">
<img src="https://stylifycss.com/images/logo/horizontal.svg" loading="lazy" fetchpriority="low" width="100" height="28">
</a>
<a role="button" aria-label="Close Menu" class="mobile-navigation__header-close js-mobile-nav-toggle">
<svg width="40" height="40">
<path d="m10 10 20 20m0-20L10 30" stroke="#000" stroke-width="4"/>
</svg>
</a>
</div>
<div class="mobile-navigation__links">
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
<a href="#" class="link">Menu link</a>
</div>
</nav>
</aside>
<script>
const mobileNavHiddenClass = 's-hidden';
const mobileNavClassPrefix = 'js-mobile-nav';
const toggleMobileNavigationVisibility = () => {
const headerNavigation = document.querySelector(`.${mobileNavClassPrefix}`);
if (headerNavigation?.classList.contains(mobileNavHiddenClass)) {
headerNavigation.classList.remove(mobileNavHiddenClass);
} else {
headerNavigation?.classList.add(mobileNavHiddenClass);
}
}
document.querySelector(`.${mobileNavClassPrefix}-obfuscator`).addEventListener('click', () => toggleMobileNavigationVisibility());
document.querySelectorAll(`.${mobileNavClassPrefix}-toggle`).forEach((button) => {
button.addEventListener('click', () => toggleMobileNavigationVisibility());
});
</script>