Animated cursor

Canvas-based cursor rendering with smooth movement and spring-damper smear trail. The cursor glides between positions and stretches into a smear shape during movement, matching the visual style of smear-cursor.nvim.

Fork mode recommended

The animated cursor works best with the bundled codemirror-vim fork (Obsidian’s built-in vim disabled). See recommended-setup.

Enable

Settings → Vim Motions → Animated cursor → Enable animated cursor

The animated cursor is disabled by default. When enabled, the plugin renders its own cursor on a <canvas> overlay and suppresses the fork’s vim cursor via the setCursorSuppressed API. Native CM6 cursor layers are always hidden by the fork regardless of animated cursor state — the fork renders its own cursor for every vim mode.

Smooth movement

When Smooth cursor movement is enabled, the cursor interpolates between positions using exponential decay instead of teleporting. The Cursor smoothness slider (0–1) controls how responsive the movement feels:

  • 0.0 — near-instant (83ms convergence)
  • 0.5 — smooth (167ms convergence, default)
  • 0.9 — dreamy (783ms convergence)

Smear trail

When Enable smear trail is enabled, the cursor shape deforms during movement using spring-damper physics on 4 corners. The leading corners arrive at the destination first while trailing corners lag behind, creating a stretching effect.

Configuration

SettingDefaultDescription
Trail stiffness0.6Head corner spring strength. Higher = snappier
Trail trailing stiffness0.3Tail corner spring strength. Lower = longer trail
Trail damping0.85Velocity decay. Lower = bouncier
Trail max length400pxMaximum trail length in pixels

Cursor shapes

The animated cursor respects per-mode cursor shapes from Settings → Cursor shapes:

  • Normal mode — block (filled rectangle with character overlay)
  • Insert mode — bar (2px vertical line)
  • Visual mode — block
  • Replace mode — underline (2px horizontal line at bottom)
  • Operator-pending — underline

Shape changes (e.g., entering insert mode) are instant — only position changes animate. Count-prefixed motions (4j, 2w) and multi-key motions (g$, gj) animate correctly — operator-pending detection is based on the actual operator state, not the chord display.

Cursor blinking

The animated cursor blinks matching CM6’s default behavior:

  • 1200ms cycle — 600ms visible, 600ms hidden (hard on/off, no fade)
  • 600ms reset delay — after any cursor movement, the cursor stays solid for 600ms before resuming blink
  • Focus-aware — blink only runs when the editor has focus; unfocused editors show a solid cursor
  • Suppressed during animation — while the smear trail or smooth movement is active, the cursor does not blink

Accessibility

The animated cursor respects the prefers-reduced-motion media query. When reduced motion is preferred, the cursor snaps instantly to its target position without animation.

Vimrc and Lua configuration

All animated cursor settings are available via vimrc and Lua. The option names use the smoothcursor prefix:

" Enable animated cursor
set smoothcursor
 
" Smooth movement (glide between positions)
set smoothcursorglide
set smoothcursorsmoothness=0.5
 
" Smear trail
set smoothcursorsmear
set smoothcursorstiffness=0.6
set smoothcursortrailstiffness=0.3
set smoothcursordamping=0.85
set smoothcursormaxlength=400
-- Lua equivalent
vim.opt.smoothcursor = true
vim.opt.smoothcursorglide = true
vim.opt.smoothcursorsmoothness = 0.3
vim.opt.smoothcursorsmear = true

See vimrc > Boolean options and lua-config > Supported vim.opt options for the full option reference.

Embeddable editors

The animated cursor renders in the oil explorer — the file manager editor shares the same canvas as regular editors.

Table cell editors, textarea vim overlays, footnote popovers, and modal editors use the native vim cursor (the fork’s BlockCursorPlugin) instead of the canvas. The global canvas (position: fixed on .app-container, z-index: 15) renders behind these elements due to higher z-index stacking contexts (e.g., popovers at z-index: 30). The native cursor — part of the editor’s own DOM — renders reliably above all content.

The CursorController detects editors inside .popover or .modal-container and automatically un-suppresses the fork’s cursor for those views. This covers Obsidian’s footnote editor, hover editors, and any third-party plugin editors created inside modals or popovers.

When navigating between table cells via h/j/k/l, a token-based position handoff seeds the new cell’s cursor controller with the previous cell’s screen position. The infrastructure for smooth cross-cell animation is in place, but the transition is not visible because the canvas draws behind cells. Within a single cell, cursor movement uses the native vim cursor’s standard rendering.

Known limitation

Cross-cell cursor movement snaps instead of animating. See known-limitations > Table cell vim modality for details.

Cross-platform resilience

The animation loop includes several defenses against platform-specific issues that can silently stop the cursor from rendering:

  • Error recovery: A single bad frame (e.g., a transient null during window refocus) cannot kill the animation loop. Errors are caught and logged; the loop continues on the next frame.
  • Heartbeat safety net: A background timer detects when the animation loop has stalled — due to OS-level throttling, Windows 11 Efficiency Mode, or sleep/wake transitions — and restarts it automatically.
  • Visibility recovery: When the Obsidian window is hidden and restored (e.g., switching apps), the animation loop re-wakes immediately.
  • Fractional DPI handling: Canvas dimensions are rounded to avoid sub-pixel artifacts on Windows displays with 125%/150% scaling.

Incompatibilities

The animated cursor is incompatible with other cursor animation plugins:

Disable these plugins when using the built-in animated cursor.

Tall line character positioning

On lines containing tall inline content (e.g., MathJax \dfrac expressions), the character beneath the block cursor previously shifted downward on some platforms and fonts. The renderer now uses the actual DOM character bounding rect for baseline calculation, which keeps the character at its correct vertical position regardless of line height.

Known limitations

See known-limitations > Animated cursor (smear + smooth movement).