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>