Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Scroll Memory

An Obsidian plugin that puts you back where you stopped reading when you reopen a note.

Built because the existing options kept losing my place in long notes I was reading in preview mode rather than editing. It works the same in editing mode and reading mode, and it does not expire saved positions.

Install

Manually. Download main.js and manifest.json from the latest release, put them in YourVault/.obsidian/plugins/scroll-memory/, restart Obsidian, then enable Scroll Memory under Settings, Community plugins.

Via BRAT. Add onionviolet/obsidian-scroll-memory as a beta plugin.

There is no build step. main.js is the source, in plain CommonJS, and Obsidian loads it as-is.

Commands

Command Use
Restore scroll position for this note Jump back manually, even if you have already scrolled away
Save scroll position for this note now Pin the current spot without waiting for the debounce
Forget scroll position for this note Drop the saved spot for the current note

Settings

Setting Default What it does
Restore on open on Jump to the saved position when a note opens
Confirm each restore off Show a notice naming the line it jumped to. Useful for a day while you decide whether you trust it
Save delay (ms) 400 How long scrolling has to pause before the position is recorded
Expire after (days) 0 0 keeps positions forever

How it works

Four decisions carry the whole plugin.

One listener, on the capture phase. Scroll events do not bubble, which is why plugins in this space typically bind a listener directly to the scrolling element. Obsidian has two of them: .cm-scroller in editing mode and .markdown-preview-view in reading mode, and the pair swaps out whenever you toggle modes. Binding to one element means the plugin goes deaf in the other mode and after every mode switch.

Scroll events do not bubble, but they do capture. So this plugin registers a single listener on the view's container element with { capture: true } and catches scrolling from whichever scroller happens to be mounted:

this.registerDomEvent(view.contentEl, "scroll", handler, { capture: true, passive: true });

Positions are stored as line numbers, not pixels. A pixel offset is only meaningful for the exact window width, font size, and theme it was recorded in. Resize the window and a saved offset points somewhere else in the note. A line number survives all of that. In editing mode the top visible line is read back out of CodeMirror with posAtCoords against the top-left corner of the scroller; in reading mode Obsidian's own previewMode.getScroll() already returns a line-ish number.

Restoring retries. On a fresh open the document often is not laid out yet, so the first attempt has nothing to measure. Restore is attempted at 0, 50, 150, 400 and 900 ms, and each attempt gives up if the note is no longer at the top, so a link that targets a heading still wins over the saved position.

Writes are coalesced. Scrolling fires events continuously. Recording is debounced 400 ms after scrolling stops, and the write to data.json is coalesced to at most one per second, so a long scroll costs one disk write rather than dozens.

Scrolling back to the top of a note deletes its entry rather than storing a zero, which keeps the store to notes you actually left partway through.

Alternatives

Worth being honest about the neighborhood, because one of these may fit you better:

  • Remember cursor position is the established plugin here and restores the cursor as well as the scroll. If you want cursor restoration, use it.
  • Remember Scrollposition is the closest in intent. It binds to .cm-scroller only, so it does not record in reading mode, and it expires saved positions after 14 days by default.
  • Last Position covers similar ground.

Scroll Memory is deliberately small: scroll position only, both modes, no expiry, one file you can read end to end.

Development

node --check main.js

That is the whole toolchain. Edit main.js, syntax-check it, reload the plugin in Obsidian.

To cut a release, bump the version in manifest.json, add the matching row to versions.json, then push a tag with the same version string. The workflow in .github/workflows/release.yml verifies that the tag and the manifest agree, then publishes main.js and manifest.json as release assets.

License

MIT. See LICENSE.

About

Obsidian plugin: returns you to your last scroll position when you reopen a note. Works in both editing and reading mode.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages