Hide Obsidian app header when scrolling and opening your softkeyboard

This merges Hide Obsidian app header when the softkeyboard is open - Share & showcase - Obsidian Forum and Hide Obsidian app header when scrolling - Share & showcase - Obsidian Forum into one script. It also makes some pretty major changes with how it works.

Save this as a .js file and point CodeScript ToolKit to it as your “Startup script path.” Also, add the CSS to your Obsidian CSS snippets. It’ll make the Obsidan app header hide when the softkeyboard is open and show when it’s not. It’ll also make the Obsidan app header hide when you scroll a note down a little[1] and then show it again when you scroll up.

How it works: it makes the Obsidian app header float over the editor/note content instead of being stacked above it[2][3]. Then, to avoid the header covering your note content, when you’re fully scrolled up, it adds some padding to the top of the note content[4]. It also has animations for when the header shows and hides[5].

JS

exports.invoke = async (app) => {
	const duration = 250;
	document.body.style.setProperty('--headertransduration', `${duration}ms`);
	const capacitorapp = window.Capacitor?.Plugins?.App;
	let keyboardopen = false;
	let headerhidden = false;

	function hideHeader() {
		if (headerhidden || !document.activeElement.classList.contains('cm-content') || document.activeElement.closest('.metadata-property-value') || document.activeElement.closest('.metadata-property-key')) return;
		document.body.classList.add('hide-view-header');
		headerhidden = true;
	}

	function showHeader() {
		if (!headerhidden) return;
		document.body.classList.remove('hide-view-header');
		headerhidden = false;
	}

	function elementDefocused() {
		setTimeout(() => {
			if(!document.activeElement.onblur) document.activeElement.onblur = elementDefocused;
			if (keyboardopen) {
				if (document.activeElement.classList.contains('cm-content')) {
					hideHeader();
				} else if (!document.activeElement.closest('.metadata-property-value') && !document.activeElement.closest('.metadata-property-key')) {
					showHeader();
				}
			}
		}, 100);
	}

	if (capacitorapp?.addListener) {
		window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillShow', () => {
			keyboardopen = true;
			hideHeader();
			if(!document.activeElement.onblur) document.activeElement.onblur = elementDefocused;
		});

		window.Capacitor?.Plugins?.Keyboard?.addListener('keyboardWillHide', () => {
			keyboardopen = false;
			showHeader();
		});
	}

	let lastScrollPosition = 0;
	let scrollFlexibility = 1;

	function hookScroll(thing) {
		if (window.__scroll_hook_installed || !app.isMobile || !thing) return;
		const proto = Object.getPrototypeOf(thing);
		if (!proto || typeof proto.trigger !== "function" || proto.trigger.__scroll_hooked) return;

		const orig = proto.trigger;
		proto.trigger = function (eventName, data) {
		try {
			if (eventName === "markdown-scroll") {
				if ((data.scroll ?? 0) === 0 || data.scroll <= (lastScrollPosition - scrollFlexibility)) {
					document.body.classList.remove('hide-view-header');
					headerhidden = false;
					lastScrollPosition = data.scroll;
				} else if (data.scroll > (lastScrollPosition + scrollFlexibility)) {
					document.body.classList.add('hide-view-header');
					headerhidden = true;
					lastScrollPosition = data.scroll;
				}
				document.body.classList.toggle('is-scrolled', data.scroll !== 0);
			}
		} catch (err) {
			console.error(err);
		}
		return orig.apply(this, arguments);
		};

		proto.trigger.__scroll_hooked = true;
	}

	const thingsToHookInto = [app?.workspace, app?.workspace?.rootSplit, app?.workspace?.activeLeaf];

	for (const thing of thingsToHookInto) hookScroll(thing);
	window.__scroll_hook_installed = true;

	app.workspace.on('active-leaf-change', () => {
		if ((app.workspace.activeLeaf?.view?.scroll ?? 0) === 0 || app.workspace.activeLeaf?.view?.file?.extension != "md") {
			document.body.classList.remove('is-scrolled');
			document.body.classList.remove('hide-view-header');
		} else {
			document.body.classList.add('is-scrolled');
		}
	});
}

CSS

body {
	--headertransduration: 0ms;
}

.is-mobile .mod-root .view-header, .is-mobile .workspace-tab-header-container {
	transition: transform var(--headertransduration), opacity var(--headertransduration);
	position: absolute;
	z-index: 2;
	left: 0;
	right: 0;
}

.is-mobile .mod-root .view-content {
	transition: padding-top var(--headertransduration);
	padding-top: var(--view-header-height);
}

.is-tablet .mod-root .view-content {
	padding-top: calc(var(--view-header-height) + var(--header-height));
}

.is-tablet .mod-root .view-header {
	top: var(--header-height);
}

body.is-scrolled .mod-root .view-content {
	padding-top: 0;
}

body.hide-view-header .mod-root .view-header, body.hide-view-header .workspace-tab-header-container {
	transform: translateY(-100%);
	opacity: 0;
	pointer-events: none;
}

Optionally, if you want the mobile navbar to also hide whenever the app header hides, add this CSS:

.mobile-navbar {
	transition: transform var(--headertransduration), opacity var(--headertransduration);
	position: fixed;
	z-index: 2;
	left: 0;
	right: 0;
	bottom: 0;
}

body.hide-view-header .mobile-navbar {
	transform: translateY(100%);
	opacity: 0;
	pointer-events: none;
}

  1. You can change the let scrollFlexibility = 1; to things like let scrollFlexibility = 0.54321; or whatever else you want to dial it in to what feels right. ↩︎

  2. And, since the tablet tab bar and the app header are separate elements, it deals with that too. ↩︎

  3. Also, this makes it so things don’t jump as much, so there’s no need to compensate for it with automatically scrolling like some of my older scripts were doing. ↩︎

  4. Different padding depending on if you’re in tablet mode or not. ↩︎

  5. Change const duration = 250; to whatever you want to change the animation speed. It’s in milliseconds. ↩︎

2 Likes

Thanks again for this excellent, complete workaround to Option to hide/show top bar(s) when note is scrolled & when keyboard appears/disappears!

1 Like

Noticed some issues with searching a file.

  1. The search would pop up over the app header if you weren’t at the top of the note and the header was not hidden
  2. If you use Advanced Canvas’ search, it would be somewhat hidden behind the app header

Here’s updated CSS that fixes both those things. If you added the CSS that hides the mobile navigation toolbar, don’t forget to re-add it below this stuff.

body {
	--headertransduration: 0ms;
}

body.is-mobile .mod-root .view-header, body.is-mobile .workspace-tab-header-container {
	transition: transform var(--headertransduration), opacity var(--headertransduration);
	position: absolute;
	z-index: 2;
	left: 0;
	right: 0;
}

body.is-mobile .mod-root .view-content {
	transition: padding-top var(--headertransduration);
	--content-top-padding: var(--view-header-height);
	padding-top: var(--content-top-padding);
}

body.is-tablet .mod-root .view-content {
	--content-top-padding: calc(var(--view-header-height) + var(--header-height));
	padding-top: var(--content-top-padding);
}

body.is-mobile .document-search-container {
	transition: transform var(--headertransduration);
}

body:not(.hide-view-header).is-scrolled.is-mobile .document-search-container, body.is-mobile .workspace-leaf-content[data-type="canvas"] .document-search-container {
	transform: translateY(var(--content-top-padding));
}

body.is-scrolled .mod-root .view-content {
	padding-top: 0;
}

body.hide-view-header .mod-root .view-header, body.hide-view-header .workspace-tab-header-container {
	transform: translateY(-100%);
	opacity: 0;
	pointer-events: none;
}

Also, on some (or all?) iOS devices, this line that should animate adding the padding when you scroll all the way to the top of a note and animate removing the padding when you scroll down causes jumpiness, so you can remove it if you want:
transition: padding-top var(--headertransduration);

Removing this can cause a different, lesser jump as the padding appears or disappears all at once. It’s not noticeable when scrolling quickly, but if the scroll loses momentum as it reaches the top of the note, the last bit of text will pop out from behind the header (or if you idly nudge the page upward when at the top, the text will pop upward). I definitely prefer it to the back-and forth jumpiness that sometimes happens with the animation, tho.