Skip to content

Markdown Preview: scroll sync breaks when hidden elements (display:none) are present #281247

@kim135797531

Description

@kim135797531

Type: Bug

Summary

vscode/extensions/markdown-language-features/preview-src/scroll-sync.ts uses a binary search in getLineElementsAtPageOffset to map preview scroll positions back to editor line numbers. This algorithm assumes that all code-line elements have monotonically increasing bounding boxes.

When some lines are wrapped inside an element with display:none, their bounding boxes become (top = 0, height = 0). This breaks ordering assumptions and causes the binary search to return incorrect elements, resulting in scroll‑sync mismatches.

export function getLineElementsAtPageOffset(offset: number, documentVersion: number): { previous: CodeLineElement; next?: CodeLineElement } {
const lines = getCodeLineElements(documentVersion).filter(x => x.isVisible);
const position = offset - window.scrollY;
let lo = -1;
let hi = lines.length - 1;
while (lo + 1 < hi) {
const mid = Math.floor((lo + hi) / 2);
const bounds = getElementBounds(lines[mid]);
if (bounds.top + bounds.height >= position) {
hi = mid;
}
else {
lo = mid;
}
}

Steps to Reproduce

<div class="lang-ko">

- First page Korean AAA
- First page Korean BBB 
- First page Korean CCC
- First page Korean DDD

</div>

<div class="lang-en" style="display: none">

- First page English AAA
- First page English BBB
- First page English CCC
- First page English DDD

</div>

---

<div class="lang-ko">

- Second page Korean AAA
- Second page Korean BBB
- Second page Korean CCC
- Second page Korean DDD

</div>
<div class="lang-en"  style="display: none">

- Second page English AAA
- Second page English BBB
- Second page English CCC
- Second page English DDD

</div>

---

<div class="lang-ko">

- Third page Korean AAA
- Third page Korean BBB
- Third page Korean CCC
- Third page Korean DDD

</div>
<div class="lang-en"  style="display: none">

- Third page English AAA
- Third page English BBB
- Third page English CCC
- Third page English DDD

</div>
  1. Open Markdown preview.
  2. Move the cursor in the editor or double‑click inside the preview.
  3. Initially, scroll sync may appear to work, but after a few cursor moves or repeated clicking in the preview, the mapping gradually becomes increasingly incorrect and eventually jumps to wrong lines after the hidden section.

Expected Behavior

  • Hidden elements should not participate in scroll‑sync calculations.
  • Scroll sync should remain correct even if parts of the DOM are visually removed.

Proposed Fix

Related discussions:

Add a visual‑presence filter before binary search:

function isVisuallyPresent(line: CodeLineElement): boolean {
  const style = window.getComputedStyle(line.element);

  if (style.display === 'none' || style.visibility === 'hidden') {
    return false;
  }

  const bounds = getElementBounds(line);
  return bounds.height > 0 || bounds.width > 0;
}

const lines = getCodeLineElements(documentVersion)
  .filter(x => x.isVisible && isVisuallyPresent(x));

If calling getComputedStyle is considered too expensive, the check could rely solely on bounding‑box validation (maybe?).

I am not familiar with contributing to VSCode, and the fix described above is only a conceptual suggestion rather than a fully working implementation. I am sharing this analysis in case it may help guide a potential improvement 😄

VS Code version: Code 1.106.3 (bf9252a, 2025-11-25T22:28:18.024Z)
OS version: Windows_NT x64 10.0.26100
Modes:

System Info
Item Value
CPUs 12th Gen Intel(R) Core(TM) i9-12900H (20 x 2918)
GPU Status 2d_canvas: enabled
direct_rendering_display_compositor: disabled_off_ok
gpu_compositing: enabled
multiple_raster_threads: enabled_on
opengl: enabled_on
rasterization: enabled
raw_draw: disabled_off_ok
skia_graphite: disabled_off
trees_in_viz: disabled_off
video_decode: enabled
video_encode: enabled
vulkan: disabled_off
webgl: enabled
webgl2: enabled
webgpu: enabled
webnn: disabled_off
Load (avg) undefined
Memory (System) 63.71GB (26.37GB free)
Process Argv --disable-extensions --crash-reporter-id 759dca4a-1c6b-4855-9446-e749f4614f70
Screen Reader no
VM 0%
Extensions disabled

Metadata

Metadata

Assignees

Labels

bugIssue identified by VS Code Team member as probable buggood first issueIssues identified as good for first-time contributorshelp wantedIssues identified as good community contribution opportunitiesinsiders-releasedPatch has been released in VS Code InsidersmarkdownMarkdown support issuesverifiedVerification succeeded

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions