The general idea is that you keep your decorations in a plugin state field, and have your decorations method just read it from there, rather than rebuild it on every transaction. The field’s apply method maps the old decoration set when there are any changes, and updates the regions touched by the transaction’s steps (which you can get by iterating over their step maps, as found in tr.mapping).
https://discuss.prosemirror.net/t/decorations-performance/2381/4
What you do is track the position of the element you are interested in, and on each transaction, map to its new position (possibly tracking both the start and end so that it’s easier to determine whether any of the step maps in the transaction touch it).
https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
You don’t need to use filterTransaction – you can simply react to transactions as they are dispatched. To see the extent of the document ‘touched’ by a given transaction, you can call forEach on each step map in its mapping property, but doing this is kind of subtle as each step will have its positions expressed relative to the document as it was when that step was applied, and later steps might move those positions again.
https://discuss.prosemirror.net/t/reacting-to-node-adding-removing-changing/676/2
It is probably preferable to iterate over the mapping of each step (step.getMap().forEach 17), and map those to the current document through any subsequent steps. You could then either just keep a minimum/maximum changed position, or track and merge individual ranges. And then finally iterate over the change range/ranges and send updates.
https://discuss.prosemirror.net/t/finding-out-what-changed-in-a-transaction/2372