Beispiel:Menue mit details.html

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche
<!DOCTYPE html>
<html lang='de'>
	<head>
		<meta charset='UTF-8'>
		<meta name='viewport' content='width=device-width, initial-scale=1.0'>
		<link rel='stylesheet' type='text/css' media='screen' href='./Beispiel:SELFHTML-Beispiel-Grundlayout.css'>
		<title>Beispiel: Seitennavigation mit Details / Summary</title>
		<style>
/* menu.css */
body { 
	margin: 1em; padding: 0;  
}
p { 
	max-width: 58em; 
	-webkit-hyphens: auto; 
	hyphens: auto; 
}
			
/* Anpassung an das Seitenlayout */
/* Platz für die Navigation */
body { 
	margin-top: 3.5em; 
}
/* Position der Navigation */
#sitenav { 
	top: 1em; 
	left: 2em;
}
#sitenav.wide { 
	top: 0; 
	left: 2em; 
	right: 2em;
	max-width: 58em;
}

/* Farben */
#sitenav *,
#sitenav a { 
	background-color: lightgray; 
	color: black;
}

/* Skip-Link */
#skip-link { 
	position: absolute; 
	left: 0; top: 0; background: black; 
	color: white; padding: 0.25em; 
	transform: translateY(-100%);
	transition: 0.2s transform; 
	z-index: 1001; 
} 
#skip-link:focus { 
	transform: translateY(0); 
}			

/* Für Assistive Technologien */
.visually-hidden {
	position: absolute !important; 
	clip: rect(1px, 1px, 1px, 1px) !important; 
	padding: 0 !important; 
	border: 0 !important; 
	height: 1px !important; 
	width: 1px !important; 
	overflow: hidden !important; 
	white-space: nowrap !important; 
}

/* Positionierung */
/* Bei Browsern, die Details/Summary unterstützen, und bei eingeschaltetem Javascript wird die Navigation absolut positioniert. Die Klasse withjs wird vom Sript gesetzt. */
@supports (width: max-content) { 
/* Nicht für IE und MS-Edge, indirekter Test auf Details/Summary */
#sitenav,
#sitenav ul { 
		position: absolute; 
	}
}
#sitenav.withjs,
#sitenav.withjs ul { 
	position: absolute; 
}

/* Regeln für schmale und breite Displays */
#sitenav ul { 
	margin: 0;
	padding-top: .2em; 
	padding-left: 1em; 
	padding-right: 1em; 
}
#sitenav > details { 
	padding: .2em; 
}
#sitenav > details > ul { 
	margin-left: -.5em; 
	margin-top: .3em; 
}
#sitenav ul ul { 
	margin-left: calc(100% - 1.2em);
	margin-top: -3.1em; 
	padding-left: .1em; 
}
#sitenav li a,
#sitenav li summary { 
	display: inline-block; 
	padding: .5em; 
	margin: .3em; 
	line-height: 1.4em; 
	max-height: 1.4em; 
}
#sitenav li { 
	padding: 0em; 
	white-space: nowrap; 
	vertical-align: middle; 
}

/* Anpassungen an breite Displays. Die Klasse wide wird vom Script gesetzt, wenn der Platz reicht. */
#sitenav.wide > details { 
	padding: 0; 
} 
#sitenav.wide > details > ul { 
	display: flex;
	flex-wrap: wrap; 
	width: 100%; 
	box-sizing: border-box; 
}
#sitenav.wide ul { 
	margin:0; 
	padding-top: .2em; 
	padding-left: 0; 
	padding-right: 0; 
}
#sitenav.wide ul ul { 
	margin-left: -.5em; 
	margin-top: -.1em; 
	padding-left: .5em; 
	padding-right: .5em; 
}
#sitenav.wide ul ul ul { 
	margin-left: calc(100% - 1.2em); 
	margin-top: -3.1em; 
	padding-left: .1em; 
}

/* Originalsymbole vor den Summary entfernen und eigene Symbole definieren */
#sitenav summary::-webkit-details-marker { 
	display: none; 
}
#sitenav summary { 
	list-style-type:  none; 
}
#sitenav *::before,
#sitenav *::after { 
	font-family: arial_unicode_ms; 
	vertical-align: top; 
	display: inline-block;
}
#sitenav details *::after {
	text-align: end;
	width: 1.5em;
}
#sitenav details *::before {
	text-align: center;
	width: 1.2em;
}
#sitenav ul details:not([open]) > summary::after { 
	content: '►'; 
}
#sitenav ul details[open] > summary::after { 
	content: '◄'; 
}
#sitenav > details:not([open]) > summary::before { 
	content: '☰';
} 
#sitenav > details[open] > summary::before { 
	content: '×';
} 
#sitenav.wide details[open] details:not([open]) summary::after { 
	content: '▼'; 
}
#sitenav.wide details[open] details[open] summary::after { 
	content: '▲'; 
}
#sitenav.wide details[open] details[open] details:not([open]) summary::after { 
	content: '►'; 
}
#sitenav.wide details[open] details[open] details[open] summary::after { 
	content: '◄'; 
}
html.no-details #sitenav ul summary::before { /* Für Details-Polyfill */
	content: ''; width: 0; 
} 

/* Link zur aktuellen Seite */
#sitenav li[aria-current] a[tabindex]::before { 
	content: '►';
	text-align: start;
}
#sitenav.withjs > details > ul > li[aria-current] > details > summary { 
	border-left: 2px solid black; 
}
#sitenav.withjs > details > ul > li[aria-current] > details > ul > li[aria-current] > details > summary { 
	border-left: 2px solid black; 
}
#sitenav.wide > details > ul > li[aria-current] > details > summary { 
	border-left: none; 
}
#sitenav.wide > details > ul > li[aria-current] { 
	border-bottom: 2px solid black; 
}

/* Sprachicon */
#sitenav .langselect img { 
	height: 2em; 
	transform: translateY(-.4em); 
}

/* Rahmen etc. */
#sitenav summary { 
	cursor: pointer; 
}
#sitenav ul { 
	list-style-type: none; 
	border-radius: .5em; 
}
#sitenav.wide ul ul { 
	border-radius: 0 0 .5em .5em;
}
#sitenav > details { 
	border: thin solid black; 
	border-radius: .2em; 
}
#sitenav.wide > details { 
	border: none; 
	border-radius: 0; 
} 
#sitenav.wide > details > ul { 
	border-radius: 0; 
}
#sitenav li a { 
	text-decoration: none; 
}
#sitenav li a,
#sitenav li summary { 
	outline: none; 
}
#sitenav li a:focus,
#sitenav li summary:focus { 
	outline: thin solid #5050ff; 
}
#sitenav li a:focus-visible, 
#sitenav li summary:focus-visible { 
	outline: thin solid #5050ff; 
}
#sitenav li a:focus:not(:focus-visible), 
#sitenav li summary:focus:not(:focus-visible) { 
	outline: none; 
}

/* Ende menu.css */
		</style>
		
		<script>
// details-polyfill.js
// Polyfill für IE und MS-Edge
(function() {
	'use strict';

	if (!('open' in document.createElement('details'))) {
		if (document.readyState === 'loading') {
			window.addEventListener('DOMContentLoaded',detailsPolyfill);
		}
		else {
			detailsPolyfill()
		}	
	}

	function detailsPolyfill() {
		console.info('Details-Polyfill aktiv!');

		const style = document.createElement('style');
		style.innerText = 'html.no-details details:not([open]) > :not(summary) { display: none !important; }' 
			+ 'html.no-details details > summary:before { content: "►"; }'
			+ 'html.no-details details[open] > summary:before { content: "▼"; }'

		document.getElementsByTagName('head')[0].appendChild(style);
		document.documentElement.className += ' no-details';

		const details = document.querySelectorAll('details');
		const summary = document.querySelectorAll('summary');

		var observer = new MutationObserver(function(mutations) {
			for(let i=0;i<mutations.length;i++)
				if (mutations[i].type == 'attributes' && mutations[i].attributeName == 'open') {
					mutations[i].target.querySelector('summary').setAttribute('aria-expanded',mutations[i].target.hasAttribute('open'));
				}
		});

		for(let i=0;i<details.length;i++) 
			observer.observe(details[i], {
				attributes: true //configure it to listen to attribute changes
			});

		for(let i=0,s;i<summary.length;i++) {
			summary[i].setAttribute('tabindex',0);
			summary[i].setAttribute('role','button');
			summary[i].setAttribute('aria-expanded',details[i].hasAttribute('open'));
		}

		window.addEventListener('click', function(e) {
			if (e.target.nodeName.toLowerCase() === 'summary') {
				var details = e.target.parentNode;
				if (!details) return;
				toggle_open(details);
			}
		}); 

		window.addEventListener('keydown', function(e) {
			var keyCode = e.keyCode;
			if(keyCode == 32 || keyCode == 13) {
				if (e.target.nodeName.toLowerCase() === 'summary') {
					e.preventDefault();
					e.target.click();
				}
			}
		});

		function toggle_open(details) {
			if (details.getAttribute('open')) {
				details.open = false;
				details.removeAttribute('open');
			} else {
				details.open = true;
				details.setAttribute('open', 'open');
			}
		}

	}
})();
// Ende details-polyfill.js			
		</script>
		
		<script>
// menu.js
window.addEventListener('DOMContentLoaded',function() { 
	'use strict';

	// Das Navigationselement
	const navele = document.querySelector('#sitenav');
	if(!navele) return;

	// Polyfill für IE und alte MS-Edge bei Bedarf laden
	/* const native_details = ('open' in document.createElement('details'));
	if (!native_details) {
		const script = document.createElement('script');
		script.src = 'details-polyfill.js';
		document.getElementsByTagName('head')[0].appendChild(script);
	} */

	// Alle details und summary
	const details = navele.querySelectorAll('details');
	const summary = navele.querySelectorAll('summary');

	// Fokussierbares Element hinter Navigation legen, für focusin-Event
	const button = document.createElement('button');
	button.className = 'visually-hidden';
	button.innerText = 'Untermenüs schließen';
	navele.appendChild(button);
	
	// Fürs css Klasse 'withjs' setzen
	sitenav.classList.add('withjs');
	
	// Auf Seitenbreitenänderung reagieren und bei genügend breiten Viewports Klasse 'wide' setzen.
	// Hierzu wird geprüft, ob die Navi noch in eine Zeile passt.
	let format, width=0;
	const firstUL = navele.querySelector('ul');
	const firstLI = firstUL.querySelector('li');
	window.addEventListener('resize',resizeHandler);
	resizeHandler();

	function resizeHandler() {
		const newWidth = document.body.offsetWidth;
		if(newWidth != width) {
			width = newWidth;
			sitenav.classList.add('wide');
			details[0].setAttribute('open', 'open');
			summary[0].setAttribute('hidden', 'hidden');
			format = 'wide';
			const ulHeight = firstUL.offsetHeight;
			const liHeight = firstLI.offsetHeight;
			if(liHeight*1.5 < ulHeight) {
				details[0].removeAttribute('open');
				summary[0].removeAttribute('hidden');
				format = 'small';
				sitenav.classList.remove('wide');
			}
		}
	}
	
	// Eventhandler
	window.addEventListener('pointerdown', klickOutsideNavigationHandler);
	navele.addEventListener('keydown', keyHandler);
	navele.addEventListener('focusin', focusinHandler);
	
	// Bei Klick oder Touch außerhalb der Navigation alle Submenüs schließen
	function klickOutsideNavigationHandler(event) {
		if(!isChildOf(event.target,details[0])) closeAll();
	}

	// Bei ESC-Taste aktuelles Submenü schließen
	function keyHandler(event) { 
		if(event.keyCode == 27) {
			if(isChildOf(document.activeElement,navele)) {
				let parentDetails = findDetailsElement(document.activeElement);
				if(parentDetails) {
					if(!parentDetails.hasAttribute('open')) parentDetails = findDetailsElement(parentDetails.parentNode);
					if(parentDetails && (format == 'small' || findDetailsElement(parentDetails.parentNode))) {
						parentDetails.removeAttribute('open');
						parentDetails.querySelector('summary').focus();
					}
					else {
						button.focus();
					}
				}
			}
		} 
	}
	
	// Bei Verlassen mit Tabulator Submenü schließen
	function focusinHandler(event) { 
		const is = format == 'small'?0:1;
		for(let i=is;i<details.length;i++) {
			if(details[i].hasAttribute('open') && !isChildOf(event.target,details[i])) details[i].removeAttribute('open');
		}
	}
	
	// Alle Submenüs schließen
	function closeAll() {
		const is = format == 'small'?0:1;
		for(let i=is;i<details.length;i++) details[i].removeAttribute('open');
	}
	
	// Ermitteln, ob child ein Kindelement von parent ist
	function isChildOf(child, parent) {
		while((child = child.parentNode) && child!==parent); 
		return !!child; 
	}
	
	// Details-Element in Parentkette suchen
	function findDetailsElement(child) {
		if(child && 'closest' in child) return child.closest('details');
		do {
			if(child.nodeName.toLowerCase()=='details') return child;
			child = child.parentNode;
		} while(child && child!=navele);
		return null;
	} 
	
});
// Ende menu.js
		</script>

	</head>

	<body>

		<a id='skip-link' href='#main'>zum Hauptinhalt</a>

		<nav id='sitenav' aria-labelledby='sitenav-label'>
			<h2 id='sitenav-label' class='visually-hidden'>Site-Navigation</h2>
			<details>
				<summary>Menü</summary>
				<ul>
					<li><a href='/'>Startseite</a></li>
					<li>
						<details>
							<summary>Bereich 1</summary>
							<ul>
								<li><a href=''>Seite 1.1</a></li>
								<li><a href=''>Seite 1.2</a></li>
								<li><a href=''>Seite 1.3</a></li>
							</ul>
						</details>
					</li>
					<li aria-current='page'>
						<details>
							<summary>Bereich 2</summary>
							<ul>
								<li><a href=''>Seite 2.1</a></li>
								<li><a href=''>Seite 2.2</a></li>
								<li><a href=''>Seite 2.3</a></li>
								<li aria-current='page'>
									<details>
										<summary>Bereich 2.4</summary>
										<ul>
											<li><a href=''>Seite 2.4.1</a></li>
											<li><a href=''>Seite 2.4.2</a></li>
											<li aria-current='page'><a tabindex=0>aktuelle Seite</a></li>
											<li><a href=''>Seite 2.4.4</a></li>
										</ul>
									</details>
								</li>
								<li>
									<details>
										<summary>Bereich 2.5</summary>
										<ul>
											<li><a href=''>Seite 2.5.1</a></li>
											<li><a href=''>Seite 2.5.2</a></li>
											<li><a href=''>Seite 2.5.3</a></li>
										</ul>
									</details>
								</li>
								<li><a href=''>Seite 2.6</a></li>
							</ul>
						</details>
					</li>
					<li><a href=''>Seite 3</a></li>
					<li><a href=''>Seite 4</a></li>
					<li><a href=''>Impressum</a></li>
					<li class='langselect'>
						<details>
							<summary><img src='/images/8/87/Language-Icon.svg' alt='Sprachwahl, Language selection, Sélection de la langue, Selección de idioma'></summary>
							<ul>
								<li><a href=''>Deutsch</a></li>
								<li><a href=''>English</a></li>
								<li><a href=''>Francais</a></li>
								<li><a href=''>Español</a></li>
							</ul>
						</details>
					</li>
				</ul>
			</details>
		</nav>

		<main id='main'>
			<h1>Beispiel: Seitennavigation mit Details / Summary</h1>
			<section>
				<h2>Was?</h2>
				<p>Die Navigation soll folgende Eigenschaften haben:</p>
				<ul>
					<li>Bis zu drei Ebenen.</li>
					<li>Auf schmalen Viewports vertikal angeordnet.</li>
					<li>Auf genügend breiten Viewports horizontal angeordnet.</li>
					<li>Bedienung mit Maus, Touch und Tastatur.</li>
				</ul>
			</section>
			<section>
				<h2>Wie?</h2>
				<p>Basis der Navigation ist eine verschachtelte Liste. Um die Liste und die Unterlisten ein- und ausblenden zu können, werden sie in ein details-Element gelegt. Die Bereichsüberschriften kommen in ein summary-Element.</p>
				<p>Vor die Navigation wird noch ein Skip-Link gesetzt, um Tastatur-Benutzern sowie Besuchern mit assistiven Techniken zu ermöglichen, direkt zum Inhalt zu springen. Per css wird dieser Link aus dem Viewport geschoben und nur bei Focus sichtbar.</p>
				<p>Um Besucher mit assistiven Techniken zu informieren, erhält die Navigation noch eine visuell versteckte Überschrift.</p>
				<p>Per css wird die Navigationsliste je nach Seitenbreite im Quer- oder Hochformat angeordnet. Da noch nicht alle Browser details/summary unterstützen und eine Feature-Detection und ein Polyfill nur per Javascript möglich sind, werden die Angaben für die absolute Positionierung und die Klasse für die Seitenbreite (wide) im Javascript gesetzt.</p>
				<p>Auf schmalen Viewports wird die Navigation hinter einem Menü-Button versteckt. Bei Klick öffnet sich die erste Ebene nach unten und die zweite und dritte nach rechts. Auf genügend breiten Viewports ist der Menü-Button versteckt und die erste Ebene ist immer sichtbar und waagrecht angeordnet. Die zweite Ebene öffnet nach unten, die dritte nach rechts.</p>
				<p>In die Navigation wurde auch ein Untermenü für die Sprachwahl eingebaut. Damit das Sprachwahlmenü unabhängig von der Sprache erkannt wird, wurde hier das durch die „Language Icon Initiative” vorgeschlagene Standard-Icon für Sprachwahlmenüs gewählt. </p>
				<p>Um die Bedienung der Navigation noch etwas komfortabler für die Besucher zu gestalten, wird im Javascript dafür gesorgt, das nur ein Navigationsbereich offen ist, und das bei Mausklick bzw. Touch außerhalb der Navigation und bei der Escapetaste alle Navigationslisten geschlossen werden.</p>
				<p>In der Beispieldatei sind HTML, CSS, Javascript für das Menü und Javascript für das Polyfill in einer Datei zusammengefasst. Im Einsatz sollten die Teile in getrennten Dateien untergebracht werden. Das Polyfill sollte auch nur bei Bedarf geladen werden. Dazu kann der auskommentierte Code (// Polyfill für IE und MS-Edge bei Bedarf laden) verwendet werden.</p>
				<p>Ohne Javascript werden alle Menüpunkte und Unterpunkte vertikal angezeigt.</p>
		  </section>
		</main>

	</body>
</html>