Skip to content

fix(image): reconcile mj-image DOM on render to avoid <img> reload/flicker#432

Open
geonsang-jo wants to merge 1 commit into
GrapesJS:masterfrom
geonsang-jo:fix/mj-image-preserve-dom
Open

fix(image): reconcile mj-image DOM on render to avoid <img> reload/flicker#432
geonsang-jo wants to merge 1 commit into
GrapesJS:masterfrom
geonsang-jo:fix/mj-image-preserve-dom

Conversation

@geonsang-jo

Copy link
Copy Markdown

Fixes #431

Problem

Editing any attribute/style of an mj-image makes the rendered <img> flicker: the browser refetches and repaints the image on every edit.

Open in StackBlitz

Side-by-side repro on grapesjs-mjml@1.0.8 — toggle the width and watch the Baseline image reload while the Patched one stays put.

Root cause

The base MJML view re-renders by replacing the whole subtree: this.el.innerHTML = this.getTemplateFromMjml() runs on every change:attributes / change:src. That destroys and recreates the <img> node each time, so the browser drops the decoded image and fetches/repaints it.

Fix

An mj-image-only render override that reconciles the freshly compiled subtree into the existing DOM instead of replacing innerHTML: matching nodes are kept (preserving the <img> identity) and only changed attributes are written in place. It mirrors coreMjmlView.render exactly except for that one substitution, and follows the existing per-component override pattern (Column, NavBar).

  • Non-src edits (alt/title/style/padding) never touch the <img>'s src, so no refetch/flicker.
  • A src edit updates the same node's src in place.

Scope & known limitation

Scoped to mj-image. Adding/removing a link wrapper changes the structure (td > imgtd > a > img), so the index-based reconciliation replaces the <img> on that transition rather than moving it — documented in code and locked by a test so the boundary is intentional.

Tests

New tests/specs/image-dom-identity.test.ts covering node-identity preservation across attribute/src/style edits, link-wrapper add/remove, and the core no-refetch invariant (a non-src edit must not re-set src). All existing tests pass; eslint and tsc --noEmit are clean. No public API change.

…icker

The base MJML view re-renders a component by replacing its entire subtree
(`this.el.innerHTML = getTemplateFromMjml()`) on every change:attributes /
change:src. For mj-image this destroys and recreates the <img> node, so the
browser drops the decoded image and refetches/repaints it — a visible flicker
even when src did not change.

Override the mj-image view's render to reconcile the freshly compiled subtree
into the existing DOM instead: matching nodes are kept (preserving the <img>
identity) and only changed attributes are written in place. The override
mirrors coreMjmlView.render exactly except for that one substitution.

Adding/removing a link wrapper (td > img <-> td > a > img) changes the
structure, so the index-based reconciliation intentionally replaces the <img>
on that transition; this is documented and locked by a test.

Add tests/specs/image-dom-identity.test.ts covering node-identity preservation
across attribute/src/style edits, link-wrapper add/remove, and the no-refetch
invariant (a non-src edit must not re-set src).
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.

BUG: mj-image flickers / reloads on every non-src attribute or style edit

1 participant