Initializing inside a transformed CSS block invalidates CodeMirror's positioning assumptions. #2443
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
CodeMirror assumes that it is being initialized in a non-transformed state. These assumptions have bearings on a lot of math within the editor for handling layout positioning.
mentioned in issue #1
mentioned in issue #2444
Yes, this is a known limitation. The editor assumes it is not rotated. I think in this case, simply calling
.refresh()after you've finished rotating it back should fix the problem (it does when I do it from the console).I'm going to operate under the assumption that initializing CodeMirror while transformed doesn't work in most cases.
(I appreciate that there is a
.refresh()workaround, but the "jump" when calling on completion of the animation is less than desirable.)I'm not yet sure if I'm volunteering to do this, but I'm thinking about it. :)
Making initialization while transformed work would require making actual editing work while transformed. This would violate a bunch of assumptions (such as that lines stretch along the y axis), and though it would be cute to be able to edit at 45 degrees, I do not feel that this feature would be worth the added complexity -- which would be quite a lot.
Still, if you think you have a sane solution to the problem, feel free to propose it.
changed title from {-
.codemirrorparent { transform: rotateY(180deg); }at initi-}ali{-z-}at{-ion results in incorrect initial cursor position-}. to {+Initializing inside a transformed CSS block inv+}ali{+d+}at{+es CodeMirror's positioning assumptions+}.I'm going to document everything I learn about this here so that it exists somewhere other than in my head.
There is no way to detect a transformation using just
getBoundingClientRect(). It has to be paired with a "canary" element to test the intrinsic attributes.The alternative to this is to traverse all the way up the DOM tree:
We might be able to avoid actually processing everything through a matrix transformation if we are exceptionally clever with the first approach. Alternatively, running all positioning math through a matrix transformation would be guaranteed correct.
jsPerf says to use the second solution.
@schanzerIf you're bored, follow along here. :)The root cause is that the response returned from
getBoundingClientRect()is not aware of transformations, it is based upon the bounding box of the element's location on the screen.The strategy for CodeMirror to reach transform independence can therefore be accomplished in one of two ways:
getBoundingClientRect()with offset-based calculations in order to understand the intrinsic size (prior to transform).getIntrinsicBoundingClientRect()that returns information as if it weren't transformed and replace all calls togetBoundingClientRect()with it.The second solution would theoretically be an almost drop-in solution while the first seems more reliable long-term but far more invasive.
@marijnhDo you have any preferences or thoughts?Edit: Also need to account for
getClientRects().My main concern is performance. Something like doing multiple calls to
getBoundingClientRectand messing with the element's style in between is definitely not going to work (it'll force multiple relayouts). Measuring of layout is already a major cost in CodeMirror. Also, there are several places where the fractional results returned bygetBoundingClientRect(but not by offset properties) are required for correct operation.Needing
getBoundingClientRect's precision probably means that the first solution would only ever get us part of the way there and that the second solution would be necessary in every scenario. Since that is the case and the second solution is relatively non-invasive I'm going to take a swing at buildinggetIntrinsicBoundingClientRectandgetIntrinsicClientRects.If we assume that the transformation is static then any performance hit would be on initialize and every other call can use the already-calculated transformation matrix. This would add one matrix multiplication operation to every call to
getBoundingClientRectwhich, fully segregated from the the DOM, should be below our threshold of caring.Without the assumption that the transformation is static, every call to
getIntrinsicBoundingClientRectwould require recalculating that matrix. This would trigger the DOM tree parent walk from my above comment (except always traversing to the root node), which will have some performance impact. These would still all be reads without interlaced DOM writes so it wouldn't trigger relayout, but it's still going to be heavier than simply saving off the transformation matrix.If it all works my proposal would be that CodeMirror adopt
getIntrinsic...with a static transformation matrix calculation by default and create an option to opt in to calculating the transformation matrix on every call.Sound reasonable?
Only having this recomputed on
refresh()would be perfectly okay -- that's how CodeMirror treats other CSS as well (changing the font size will screw up your editor until you refresh it).another use-case: integrating codemirror into a reveal.js presentation for live-coding. Reveal.js automatically scales presentation based on the window size.
Thus, I think it is a pretty useful feature. Thanks a lot for looking into this!
mentioned in issue #2660
mentioned in issue #3827
mentioned in issue #5199
For anyone that wants to display a transformed codemirror, here is a partial workaround:
reverse-transform the codemirror cursor by the inverse of the overall transform.
That is, if you have something like this that transforms some codemirrors:
Then reverse the effect of this on the codemirror cursor by scaling by 1/x:
This will put the cursor in the right place again. However, the codemirror-sizer element for some reason still has issues, so the code and cursors will display fine, but the size of the code mirror box will be off. You'll get some blank space or a scroll bar that does nothing.
Setting min-width to 0 on the codemirror-sizer fixes that until .refresh() is called or the codemirror is focused or edited.
Hello,
Just giving my two cents on that issue since I experienced the same problem. Based on the answer from
@robertstrauss, here is a code to fix the mouse cursor position and also user selected code bg/position:Hope it helps!
mentioned in issue #1793
For what it is worth, using
inputStyle: "contenteditable"works much better thaninputStyle = "textarea"when a codemirror editor is inside of a CSS tranform.Still an issue with CodeMirror 6
Update: found relevant issue in CodeMirror 6. Tl;dr "not supported" 😒
Note: this happens, e.g., with reveal.js when the window size changes #4189
and is quite annoying.
mentioned in issue #53
mentioned in issue #93
mentioned in issue #730
Codemirror 6 apparently can handle scale() but not more complex transformations.
Possible more robust workaround (based on
@acf-extended, but using more reliable addressing of containers than jQuery) for CodeMirror 5, here for use with reveal.js:Multi-line selection appears to be still unreliable, it may assume a wrong line height due to the scaling.