Edit: Check my replies for more updated/fixed/alternate versions. Feel free to pick whichever one works best for you.
Save this as a .js file and point CodeScript ToolKit to it as your “Startup script path.” It’ll make the Obsidan app header hide when you scroll a note down a little and then show it again when you scroll up.
exports.invoke = async (app) => {
const duration = 250;
document.body.style.setProperty('--headertransduration', `${duration}ms`);
if (window.__scroll_hook_installed || !app.isMobile) return;
let lastScrollPosition = 0;
let scrollFlexibility = 1;
function hookScroll(thing) {
if (!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 || data.scroll <= (lastScrollPosition - scrollFlexibility)) {
document.body.classList.remove('hide-view-header');
lastScrollPosition = data.scroll;
} else if (data.scroll > (lastScrollPosition + scrollFlexibility)) {
document.body.classList.add('hide-view-header');
lastScrollPosition = data.scroll;
}
}
} 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;
}
You also need this in your CSS snippets.
body {
--headertransduration: 0ms;
}
.workspace-leaf-content[data-type="markdown"] .view-header {
transition: height var(--headertransduration), opacity var(--headertransduration);
}
body.hide-view-header .workspace-leaf-content[data-type="markdown"] .view-header {
border-bottom: 0;
height: 0;
opacity: 0;
overflow: hidden;
}
Relevant feature requests:
Here’s a version that also hides the Obsidian app header when the softkeyboard is open to make it cover more of what Option to hide/show top bar(s) when note is scrolled & when keyboard appears/disappears - Feature requests - Obsidian Forum is asking for. Don’t forget the CSS in the first post of this thread!
exports.invoke = async (app) => {
const duration = 250;
document.body.style.setProperty('--headertransduration', `${duration}ms`);
const capacitorapp = window.Capacitor?.Plugins?.App;
let viewHeaderHeight = 0;
let keyboardopen = false;
let headerhidden = false;
let isAutoScrolling = false;
function adjustScroll(delta) {
const editor = app.workspace?.activeLeaf?.view?.editor;
if (!editor || app.workspace?.activeLeaf?.view?.file?.extension != 'md') return;
const scrollInfo = editor.getScrollInfo();
const start = scrollInfo.top;
const end = start + delta;
const startTime = performance.now();
function animate(time) {
isAutoScrolling = true;
const elapsed = time - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = progress < 0.5
? 2 * progress * progress
: -1 + (4 - 2 * progress) * progress;
editor.scrollTo(scrollInfo.left, start + (end - start) * ease);
if (progress < 1) {
requestAnimationFrame(animate);
} else {
isAutoScrolling = false;
}
}
requestAnimationFrame(animate);
}
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');
adjustScroll(-viewHeaderHeight);
headerhidden = true;
}
function showHeader() {
if (!headerhidden) return;
document.body.classList.remove('hide-view-header');
adjustScroll(viewHeaderHeight);
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();
});
}
app.workspace.onLayoutReady(() => {
const leafContent = document.querySelector('.workspace-leaf-content:not([data-type="outgoing-link"]):not([data-type="backlink"]):not([data-type="undefined"])');
const viewHeader = leafContent?.querySelector('.view-header');
viewHeaderHeight = viewHeader ? viewHeader.getBoundingClientRect().height : 0;
});
if (window.__scroll_hook_installed || !app.isMobile) return;
let lastScrollPosition = 0;
let scrollFlexibility = 1;
function hookScroll(thing) {
if (!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 (isAutoScrolling) return;
if (data.scroll == 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;
}
}
} 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;
}
Updated version of the one that hides the app header in response to scrolling and the softkeyboard hiding/showing
This one now deals with tablets and foldables better. The previous version didn’t hide the tab list when hiding the header.
JS
exports.invoke = async (app) => {
const duration = 250;
document.body.style.setProperty('--headertransduration', `${duration}ms`);
const capacitorapp = window.Capacitor?.Plugins?.App;
let viewHeaderHeight = parseInt(window.getComputedStyle(document.body).getPropertyValue('--view-header-height'));
let tabHeaderHeight = parseInt(window.getComputedStyle(document.body).getPropertyValue('--header-height'));
let keyboardopen = false;
let headerhidden = false;
let isAutoScrolling = false;
function adjustScroll(delta) {
const editor = app.workspace?.activeLeaf?.view?.editor;
if (!editor || app.workspace?.activeLeaf?.view?.file?.extension != 'md') return;
const scrollInfo = editor.getScrollInfo();
const start = scrollInfo.top;
const end = start + delta;
const startTime = performance.now();
function animate(time) {
isAutoScrolling = true;
const elapsed = time - startTime;
const progress = Math.min(elapsed / duration, 1);
const ease = progress < 0.5
? 2 * progress * progress
: -1 + (4 - 2 * progress) * progress;
editor.scrollTo(scrollInfo.left, start + (end - start) * ease);
if (progress < 1) {
requestAnimationFrame(animate);
} else {
setTimeout(() => {
isAutoScrolling = false;
}, duration);
}
}
requestAnimationFrame(animate);
}
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');
document.body.classList.contains('is-tablet') ? adjustScroll(-(viewHeaderHeight + tabHeaderHeight)) : adjustScroll(-viewHeaderHeight);
headerhidden = true;
}
function showHeader() {
if (!headerhidden) return;
document.body.classList.remove('hide-view-header');
document.body.classList.contains('is-tablet') ? adjustScroll(viewHeaderHeight + tabHeaderHeight) : adjustScroll(viewHeaderHeight);
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 (isAutoScrolling) return;
if (data.scroll == 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;
}
}
} 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;
}
CSS
body {
--headertransduration: 0ms;
}
.workspace-leaf-content[data-type="markdown"] .view-header, .workspace-tab-header-container {
transition: height var(--headertransduration), opacity var(--headertransduration);
}
body.hide-view-header .workspace-leaf-content[data-type="markdown"] .view-header, body.hide-view-header .workspace-tab-header-container {
border-bottom: 0;
height: 0;
opacity: 0;
overflow: hidden;
}