Skip to content

fix(preview): zoom content by font size, and sync the preview to the editor only when the reader leads - #1072

Merged
PathGao merged 1 commit into
masterfrom
fix/content-zoom-font-size
Nov 8, 2026
Merged

PathGao merged 1 commit into
masterfrom
fix/content-zoom-font-size

Conversation

@PathGao

@PathGao PathGao commented Nov 8, 2026

Copy link
Copy Markdown
Collaborator

Closes #1071, reported by @Zman199 on Windows 10 at 135% zoom: in split view the minimap slider ran ahead of the pointer when dragged, and typing made the editor bounce up and down or keep scrolling.

Mechanism

Content zoom was CSS zoom on .markdown-container, which in split view wraps the editor pane as well, while the editor also multiplied its font size by the zoom. So the split-view editor was zoomed twice (a line 36.4px on screen at 140%, against 26px in the editor alone), and Monaco's minimap drag adds the pointer's pageY delta, which is in zoomed pixels, to a slider position in unzoomed ones: 100px of mouse moved the slider 140px.

The bounce is a loop. With sticky scroll on, Monaco keeps five lines above a cursor it reveals, so a keystroke near the top of the editor scrolls it up and the preview follows. The preview then re-renders, and Chromium's scroll anchoring nudges it a few pixels. handleScroll cannot tell that nudge from the reader scrolling, so sync moves the editor back down into the five lines, and the next keystroke starts it again. At 140% it happened at 6 of 10 scroll positions; at 100% at none.

Change

  • Content zoom scales font sizes instead of applying CSS zoom: the preview's body and code font sizes and its measure are multiplied by the zoom, as the editor's font already was. Nothing on screen carries a CSS zoom any more, so editor, preview and pointer share one pixel space. Text scales and spacing does not, which is how VS Code's markdown.preview.fontSize behaves; footnotes and kbd, the two pieces of body text with a fixed px size, are now em. Images and diagrams keep their size, and the editor toolbar no longer grows in split view.
  • The zoomed measure goes through previewLayout, so the outline's gutter check sees the width the text actually takes.
  • A zoom step keeps the line at the top of the preview. Font-size zoom reflows the text under a scrollTop that stays put, where CSS zoom scaled the offset too. The fold wrappers are re-measured first (measureNow), since placing the line before they caught up moved it a paragraph per step.
  • Preview scrolls reach the editor only while the reader's last wheel, pointer or key input in the panes was outside the editor: in the preview, its outline or find bar. Typing makes the editor lead, so a re-render's anchoring nudge stays in the preview; scrolling the preview hands the lead back; input in the title bar or a dialog leaves it as it was. Scroll anchoring stays on. The rule follows who acted, not a time window, so it holds through continuous typing.
  • The programmatic-scroll flag is cleared when a sync write or history jump leaves the preview where it was. No scroll event came to clear it, and it swallowed the reader's next scroll.

For reference, VS Code's preview ignores its own scroll events for 50ms after the editor moves it and has no guard for re-renders; its editor has no reveal margin to pull back, so the loop does not form there. Joplin restores a stored scroll percentage after every content change and marks its own scrolls with an expiring counter.

Exports are unaffected by zoom, as before. With a non-default preview font size, exported footnotes and kbd now keep their proportion to the body text instead of staying at 12px and 11px.

Tests

  • scripts/preview.test.ts: at 150% an 880px measure is 1320px of a 1400px pane and the outline counts as covering the text. Red without the previewLayout change.
  • scripts/layout.test.ts: measureNow publishes fold heights without waiting for a frame.

The input rule and the zoom-step restore live in MarkdownViewer.svelte, which the suites do not mount; both were checked in the app.

Verification

npm run check
npm test
npx vitest run

All pass (951 + 770). In a dev build on Windows 11 (WebView2 157, display at 200%), driven over the DevTools protocol at 140%:

  • a 60px minimap drag moved the slider 59.8px;
  • typing letters and Enter with the cursor at the top, middle and bottom of the editor moved neither pane;
  • a 3px nudge of the preview while typing left the editor where it was, also after a click in the title bar; after a click in the preview the same nudge moved it, as did the wheel;
  • stepping zoom from 150% to 200% and back in reading mode kept the same paragraph at the top, at 71–75% of its height;
  • at 100% every measured font size and spacing matched the current build; at 140% text scaled by 1.4 while table padding and paragraph margins stayed put.

Not verified: macOS and Linux; a trackpad or touch scroll of the preview on real hardware; and the flag-clearing path itself, which I could not provoke.

…editor only when the reader leads

Content zoom was CSS zoom around both panes, so in split view the editor
was zoomed twice and Monaco's minimap drag ran ahead of the pointer. It
now scales the preview's font sizes and measure, as the editor's font
already was, and a zoom step keeps the line at the top.

Typing near the top of the editor made it bounce: the preview re-render's
scroll anchoring nudge was synced back to the editor, whose cursor reveal
pulled it back. Preview scrolls now reach the editor only while the
reader's last input in the panes was outside the editor.

Closes #1071.
@PathGao
PathGao merged commit cfb4e65 into master Nov 8, 2026
4 checks passed
@PathGao
PathGao deleted the fix/content-zoom-font-size branch November 8, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

双屏界面编辑时的抖动问题

1 participant