Choosing an Efficient Decoration Handling Approach

I’m working on a feature where hovering over an item in a separate component highlights a corresponding word in a ProseMirror document. I have raw text indexes (from, to), but they need to be converted to ProseMirror positions for accurate highlighting. I’m considering two approaches:

Approach 1: Precompute and Communicate Decorations

In this approach, I calculate all the necessary decorations at the start and store them. These decorations are then communicated to another component that handles interactions. When a user hovers over an item in that component, I update the class of the corresponding decoration based on its from and to values.

However, this requires sending the entire decoration set to the other component every time the user types—even if it’s just a single letter. My concern is whether this constant communication of decorations would introduce performance issues, especially in large documents.

Approach 2: Compute Decorations on Hover

Here, I don’t precompute anything. Instead, when a user hovers over an element, I dynamically convert raw indexes (from, to) to ProseMirror indexes and add a decoration. When the hover ends, I remove the decoration.

The challenge with this approach is that converting raw indexes to ProseMirror indexes requires iterating over the document using doc.descendants(), which might become a performance bottleneck if done frequently.

Moreover, if a user types some text, the original raw indexes become invalid, and therefore recalculating also doesn’t give me accurate ProseMirror positions.

Question

Which approach is more efficient, especially for large documents? How can I handle index shifts reliably without excessive recalculations?

It probably won’t. Documents size is limited by browser DOM performance. Computers (even through JavaScript) are really fast. Any size that remains responsive in the browser, you can iterate over and do superficial processing on in almost no time.

So yeah, if you’re not going to recompute these every frame, you’re almost certainly fine doing it on-demand.

1 Like

Thanks for the reply, Marijn.

Could you please also suggest some solution for this problem?

Because if I go with the first approach, then ProseMirror handles the decorations already. So if a user types some text, the decorations get updated automatically. But in the 2nd approach, this is a problem.

Where do you store the information of the words you want to highlight? I have a hunch that you don’t store it anywhere, and the source of truth is your ProseMirror document ( for ex. you highlight some words ).

In that case I think approach 1. is fine.

I agree with Marijn, 99% you won’t have any performance issues. But! If you do I suggest you make the sending “async” but adding await new Promise(res=>setImmediate(res)) in your loops, so the event queue will be open to receive user interactions. But again, you’ll probably won’t need this.

I’ve made https://www.npmjs.com/package/@emergence-engineering/prosemirror-text-map for a similar problem, maybe it helps you a bit.

1 Like

Thank you, Viktor!

I’ve been using a small function to convert raw indexes to ProseMirror indexes, but I see your library already handles that—nice! Thanks for sharing it.

Could you clarify whether your library supports the following case?

  1. The startPos in the ProseMirror document is at the beginning of the last word in a node.
  2. The endPos (or the length of the highlighted text) extends into the first word of the next node, meaning the highlight spans across two nodes.

Can your library handle this scenario?

By the way, I also went with approach 1. My solution was to assign unique IDs to decorations and pass them to a separate component. When a user hovers over an item, we find the corresponding decoration by its ID and update its class to "highlighted". Continuously updating decorations on every keystroke wasn’t necessary, and the performance is great.

Looking forward to your thoughts!

I think so. It’s been a while :slight_smile: The package has a test suite, you can check your usecase in no time by adding a new test.

Great, thanks !