Synchronization issue between codemirror viewport and a canvas viewport

Hello!

I am currently working on a canvas / codemirror editor combination where I need to synchronize the canvas’ viewport with codemirror’s viewport (you can find it here ).

My current approach is to scale the editor vertically and horizontally, but only translate the editor horizontally and use the scrollTop of the editor to sync the viewports’ vertical positions.

function updateTransforms() {
  editorView.requestMeasure({
    read: () => {},
    write: () => {
      const mat3d = Mat3dUtils.mul(
        Mat3dUtils.scale(scale, scale),
        Mat3dUtils.translate(-translateX, 0)
      );
      // vertical translation
      if (editorView.scrollDOM) {
        editorView.scrollDOM.scrollTop = translateY;
      }

      // horizontal translation and vertical/horizontal scale
      editorView.dom.style.transform = `matrix(${Mat3dUtils.elems(mat3d).join(',')})`;

      // Update canvas viewport
      updateCanvas()
    }
  });
}

However, when I scroll down far enough (e.g., 100 lines down), and try to zoom, the editor’s scroll starts jittering and its viewports get out of sync. This only happens when I zoom the editor, not when I pan the editor.
Here is an example where:

  • Canvas viewport: surrounded by a blue border
  • Editor: surrounded by a green border
  • Red squares: shapes within the canvas that should stay on the same position relative to the editor’s text
  • Use shift+scroll to zoom the viewport, and scroll/drag to move the viewport around
    Video of issue: https://imgur.com/a/7VZ2rP8
    Demo: codemirror demo

Furthermore, I noticed that this only happens after a requestMeasure happened, and when, for example, the height of the editor is changed. I am guessing the internal content height somehow changes after a requestMeasure, which causes the new scrollTop to be slightly off.

Any ideas on how to solve this problem? If I need to provide anything else, please let me know!

Kind regards

You’re not supposed to change the editor’s scroll position, or the CSS transform of the editor, in a measure request write phase. That’s just for aligning stuff to the editor’s current content positions, not for messing with those positions. You do want to call requestMeasure after you’ve messed with something like the transform, but the scrolling and such should be happening before that.

1 Like

Thank you for the fast response!

I have changed the “scrollTop” to be updated before the request measure:

 // vertical translation
 editorView.scrollDOM.scrollTop = translateY;

 // horizontal translation and vertical/horizontal scale (transforms editor, so inside requestMeasure)
 editorView.requestMeasure({
   read: () => {},
   write: () => {
     // only translate the editor horizontally
     const mat3d = Mat3dUtils.mul(
       Mat3dUtils.scale(scale, scale),
       Mat3dUtils.translate(-translateX, 0)
     );

     editorView.dom.style.transform = `matrix(${Mat3dUtils.elems(mat3d).join(',')})`;
   }
 });

However, the issue still remains. One more thing I have noticed is that, when scaling the editor, the gap element’s height is also scaled (the height slowly changes as I zoom in/out). But, if I understand correctly, the editor as a whole is already being scaled, which causes the gap element to be scaled by both 1: the height being changed, and 2: the parent editor being scaled through CSS.
Here is an example where I show that the height of the gap element changes as I scale the editor.

In this video, I observe that the height of the gap element is internally adjusted by codemirror. And, as the editor is being scaled further and further, the gap element’s height reverts to a previous value, and this cycle repeats as I keep scaling the editor.

I feel that the gap element’s height shouldn’t be scaled internally, since the entire editor is already being transformed, including the gap element. However, I might be overlooking something — is this the intended behavior?

Kind regards