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 .workspace-leaf-content .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 .workspace-leaf-content .view-content {
transition: padding-top var(--headertransduration);
padding-top: var(--header-height);
}
.is-tablet .workspace-leaf-content .view-content {
padding-top: calc(var(--header-height) + var(--view-header-height));
}
.is-tablet .workspace-leaf-content .view-header {
top: var(--header-height);
}
body.is-scrolled .workspace-leaf-content .view-content {
padding-top: 0;
}
body.hide-view-header .workspace-leaf-content .view-header, body.hide-view-header .workspace-tab-header-container {
transform: translateY(-100%);
opacity: 0;
pointer-events: none;
}
You can change the
let scrollFlexibility = 1;
to things likelet scrollFlexibility = 0.54321;
or whatever else you want to dial it in to what feels right. ↩︎And, since the tablet tab bar and the app header are separate elements, it deals with that too. ↩︎
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. ↩︎
Different padding depending on if you’re in tablet mode or not. ↩︎
Change
const duration = 250;
to whatever you want to change the animation speed. It’s in milliseconds. ↩︎