No description
Find a file
2026-07-21 22:45:52 -05:00
.github/workflows Generate real release notes instead of publishing an empty body 2026-07-21 14:24:49 -05:00
docs Document shrink-to-fit in README with demo GIF 2026-07-21 22:45:46 -05:00
src Fix shrink-to-fit: overflowing cards no longer scroll forever 2026-07-21 22:45:39 -05:00
test Fix shrink-to-fit: overflowing cards no longer scroll forever 2026-07-21 22:45:39 -05:00
.gitignore Initial build: Canvas Card Text Scale plugin 2026-07-21 10:37:42 -05:00
esbuild.config.mjs Initial build: Canvas Card Text Scale plugin 2026-07-21 10:37:42 -05:00
eslint.config.mts Initial build: Canvas Card Text Scale plugin 2026-07-21 10:37:42 -05:00
LICENSE Initial build: Canvas Card Text Scale plugin 2026-07-21 10:37:42 -05:00
manifest.json Bump version to 0.2.0 2026-07-21 22:45:52 -05:00
package-lock.json Initial build: Canvas Card Text Scale plugin 2026-07-21 10:37:42 -05:00
package.json Bump version to 0.2.0 2026-07-21 22:45:52 -05:00
README.md Document shrink-to-fit in README with demo GIF 2026-07-21 22:45:46 -05:00
tsconfig.json Initial build: Canvas Card Text Scale plugin 2026-07-21 10:37:42 -05:00
version-bump.mjs Initial build: Canvas Card Text Scale plugin 2026-07-21 10:37:42 -05:00
versions.json Bump version to 0.2.0 2026-07-21 22:45:52 -05:00

Canvas Text Scaler

Scales the text inside an Obsidian Canvas card to the card's own size as you resize it, instead of only scaling with canvas zoom.

Canvas Text Scaler in action

If this ever fights with your own theme over font-size, there's a toggle for that, see "The safety net" further down.

Why this is a plugin and not a CSS snippet

I tried this in CSS first, twice, in my Rathgar Gold theme's own theme.css. Both attempts failed, for reasons specific to how Obsidian's Canvas renders and virtualizes nodes, and I'm writing both up here so I don't walk the same dead ends twice.

Attempt 1: container-type: size

Here's roughly what I tried first. I didn't keep the exact rule, I reverted it the moment I saw it break:

.canvas-node,
.canvas-node-container {
  container-type: size;
}

Result: broke Canvas virtualization outright. Cards fell back to their raw placeholder text (the block-reference label, e.g. Dashboard Session Running > ^tonightsnpcs) instead of rendering their embedded content. container-type: size implies contain: size layout style, which interferes with whatever internal mechanism (likely a ResizeObserver/ IntersectionObserver pair against the real node) Obsidian's Canvas uses to decide when to swap a node from placeholder to fully-rendered embed.

Attempt 2: container-type: inline-size

Same situation, this is close to what I actually ran, not the literal rule:

.canvas-node-container {
  container-type: inline-size;
}

Result: did not break rendering, but text still didn't scale. DevTools showed font-size stuck at a flat 16px regardless of node size.

Root cause: Obsidian's core Canvas (not a plugin, I checked Advanced Canvas's own main.js myself, the string isn't in there) already has a built-in "font size relative to zoom" mechanism, a --zoom-multiplier custom property plus a data-disable-font-size-relative-to-zoom attribute on .canvas-wrapper. It compensates text size against canvas zoom level, not node size, a different axis than what I actually wanted, and it wins over a plain CSS font-size rule regardless of selector specificity.

The test that told me this was buildable

Before writing any lifecycle or observer code, I needed to answer one question: could a JS-set font-size survive that same zoom-compensation mechanism? If not, I'd need to fight Canvas's internals directly instead of just observing size and setting a style. I tested it live in a disposable canvas file:

const wrapper = document.querySelector('.canvas-node .markdown-preview-view, .canvas-node .markdown-embed-content');
const targets = wrapper.querySelectorAll('*');
wrapper.style.setProperty('font-size', '60px', 'important');
targets.forEach(el => el.style.setProperty('font-size', '60px', 'important'));

Result: the forced 60px held through repeated zoom in/out and panning, no snap-back, no flicker. Setting font-size with !important on the actual text-bearing container and every descendant (not just the outer wrapper) beats Obsidian's zoom-compensation cleanly. That's the technique this plugin builds on, driven by a ResizeObserver instead of a hardcoded value.

What it does

Watches each visible Canvas card's real rendered size with a ResizeObserver and scales its text proportionally, clamped to a min/max range you control in settings.

Shrink to fit (fixed in 0.2.0)

Shrink to fit demo

A card with more text than fits used to scroll no matter how big you made it, because font size and card size scaled at the same rate; this fix adds a correction pass that only kicks in on overflowing cards, binary-searching down to the largest font size that actually fits (down to your minimum font size setting) instead of letting it scroll, with a Shrink to fit toggle in settings if you'd rather turn it off.

export function findFittingFontSize(
	candidateFontSizePx: number,
	minFontSizePx: number,
	overflows: (fontSizePx: number) => boolean,
	iterations = 6,
): number {
	if (!overflows(candidateFontSizePx)) return candidateFontSizePx;
	if (overflows(minFontSizePx)) return minFontSizePx;

	let lo = minFontSizePx;
	let hi = candidateFontSizePx;
	for (let i = 0; i < iterations; i++) {
		const mid = (lo + hi) / 2;
		if (overflows(mid)) {
			hi = mid;
		} else {
			lo = mid;
		}
	}
	return lo;
}

Settings

  • Enable/disable
  • Base font size and base card size (the reference point scale is computed from)
  • Minimum and maximum font size
  • Sensitivity multiplier
  • Respect existing font-size CSS (the safety net, see below, skips cards a theme/snippet already styles)
  • Shrink to fit (see above, only affects cards that would otherwise overflow)

Installation

Manual, until this is accepted into the community plugin directory:

  1. Download main.js and manifest.json from the latest release.
  2. Create a folder named canvas-text-scaler inside your vault's .obsidian/plugins/ folder and put both files in it.
  3. Reload Obsidian (or disable/re-enable community plugins) and turn it on under Settings → Community plugins.

A known trade-off: inline styles, on purpose

Obsidian's Plugin Guidelines say not to hardcode inline styles, because it stops a user's theme or CSS snippet from overriding them. This plugin does exactly that, on the text inside a Canvas card, with !important.

Why: the test above proved it's the only thing that survives Obsidian's own zoom-compensation logic on Canvas. A plain stylesheet rule loses to that mechanism no matter how specific it is. There was no clean option, only "doesn't work" and "works via inline style."

I kept the scope deliberately narrow: this plugin only touches text inside Canvas card nodes. It never touches the editor, reading view, or anything outside a Canvas. Here's the exact code I use to decide what gets touched:

const TEXT_CONTAINER_SELECTOR = '.markdown-preview-view, .markdown-embed-content';

export function findTextContainer(canvasNodeEl: Element): Element | null {
	return canvasNodeEl.querySelector(TEXT_CONTAINER_SELECTOR);
}

canvasNodeEl is always a single .canvas-node element, found by ResizeObserver, never the whole document. Unless your theme or snippet sets font-size on .canvas-node .markdown-preview-view (or .markdown-embed-content inside a Canvas node), there's no conflict. Few themes target that selector, so most users will never notice this plugin is setting inline styles at all.

The safety net: Respect existing font-size CSS

If your theme does target it, turn this on. In settings, enable Respect existing font-size CSS and the plugin checks each card's font-size before it ever touches it. If that size doesn't match its own default, it leaves the card alone completely, no override, no fight.

There's also a plain Enabled toggle that turns the whole plugin off and clears every font-size it set, immediately. Between the two, you are never stuck with this plugin overruling your own styling.

Contributing

Issues and pull requests are welcome.

Support

If this plugin is useful to you, you can buy me a coffee.

License

MIT