Code Mirror - Implement drag-and-drop scrolling within editors only (regular body scrolling)

I am trying to implement the following scroll mechanism:

  • There are several Mirror code editors in the document.
  • Body scrolling should be performed regularly (default behavior), even if the cursor is over the editor
  • Scrolling inside the editor should only be done by dragging and dropping the scroll (while holding down the mouse button and scrolling)
  • When scrolling the body and moving the cursor inside the editor, scrolling of the editor should not start, because the mouse button is not held.

I struggled with this. At the moment, the best option is to set the .CodeMirror-scroll class to unset !important when you click the mouse button and return to the default value ( scroll !important ) when the mouse button is not available for scrolling. However, this seems to break and unstable scroll behavior is triggered (for example, when the mouse button is clicked, the editors do not save the last scroll value and always reset to zero).

I even tried using the CodeMirror API function cm.scrollTo cm.scrollTo(x, y) to force the scroll value on mouseup , but that also does not work.

Here's a JSFiddle that shows the scroll propagating from the body to the child editor as the cursor moves. GIF is also shown.

Usually using scroll scrolling is not a problem, as I have done this in the past, both on my own and using this lib. However, I cannot manipulate the scroll event in the .CodeMirror-scroll class as it seems to be overloaded by lib, possibly due to the CSS class (snippet from CSS source):

 .CodeMirror-scroll { overflow: scroll !important; /* Things will break if this is overridden */ /* 30px is the magic margin used to hide the element real scrollbars */ /* See overflow: hidden in .CodeMirror */ margin-bottom: -30px; margin-right: -30px; padding-bottom: 30px; height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; } 

As the author comments, it seems that changing the overflow will break the material, so I think I need to find a solution that is not related to changing the CSS / style. I will be very grateful for any help.

+5
source share
1 answer

This is very difficult to do correctly, in particular because the drag and drop in the editor should, I believe, select the text. Overloading it to scroll seems inconsistent. Therefore, I propose the following.
This is reminiscent of the blackmiacool suggestion in the comments. I decided not to turn off scrolling on the body.

First a demo, both here and on JSFiddle , for your convenience.

 (function() { function isChildOf(el, parent) { do { el = el.parentNode; } while (el !== null && el !== parent && el !== document.body); return (el === parent); } var readOnlyCodeMirror = CodeMirror.fromTextArea(document.getElementById('codesnippet_readonly'), { mode: "javascript", theme: "default", lineNumbers: true, readOnly: true }); var editableCodeMirror = CodeMirror.fromTextArea(document.getElementById('codesnippet_editable'), { mode: "javascript", theme: "default", lineNumbers: true }); var activeEditor = null, newActiveEditor = null; for (let cm of document.querySelectorAll('.CodeMirror')) { let overlay = document.createElement('div'); overlay.classList.add('cm__overlay'); cm.insertBefore(overlay, cm.firstChild); overlay.addEventListener('click', function(event) { overlay.classList.add('cm__overlay--hidden'); if (activeEditor === null) { activeEditor = cm; } else { newActiveEditor = cm; } }); } document.body.addEventListener('click', function(event) { if (activeEditor !== null && !isChildOf(event.target, activeEditor)) { activeEditor.firstChild.classList.remove('cm__overlay--hidden'); activeEditor = null; if (newActiveEditor !== null) { activeEditor = newActiveEditor; newActiveEditor = null; } } }); }()); 
 .cm__overlay { position: absolute; z-index: 10; top: 0; left: 0; width: 100%; height: 100%; box-sizing: border-box; } .cm__overlay--hidden { pointer-events: none; border: 1px solid red; } 
 <h1>Using CodeMirror (readonly and editable code)</h1> <p><a href="http://codemirror.net/mode/javascript/index.html">http://codemirror.net/mode/javascript/index.html</a></p> <link rel="stylesheet" href="http://codemirror.net/lib/codemirror.css"> <script src="http://codemirror.net/lib/codemirror.js"></script> <script src="http://codemirror.net/addon/edit/matchbrackets.js"></script> <script src="http://codemirror.net/mode/javascript/javascript.js"></script> <h2>Readonly</h2> <div> <textarea rows="4" cols="50" id="codesnippet_readonly" name="codesnippet_readonly"> // Demo code (the actual new parser character stream implementation) function StringStream(string) { this.pos = 0; this.string = string; } StringStream.prototype = { done: function() {return this.pos >= this.string.length;}, peek: function() {return this.string.charAt(this.pos);}, next: function() { if (this.pos &lt; this.string.length) return this.string.charAt(this.pos++); }, eat: function(match) { var ch = this.string.charAt(this.pos); if (typeof match == "string") var ok = ch == match; else var ok = ch &amp;&amp; match.test ? match.test(ch) : match(ch); if (ok) {this.pos++; return ch;} }, eatWhile: function(match) { var start = this.pos; while (this.eat(match)); if (this.pos > start) return this.string.slice(start, this.pos); }, backUp: function(n) {this.pos -= n;}, column: function() {return this.pos;}, eatSpace: function() { var start = this.pos; while (/\s/.test(this.string.charAt(this.pos))) this.pos++; return this.pos - start; }, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { if (consume !== false) this.pos += str.length; return true; } } else { var match = this.string.slice(this.pos).match(pattern); if (match &amp;&amp; consume !== false) this.pos += match[0].length; return match; } } }; </textarea> </div> <div> <h2>Editable</h2> <textarea rows="4" cols="50" name="codesnippet_editable" id="codesnippet_editable"> // Demo code (the actual new parser character stream implementation) function StringStream(string) { this.pos = 0; this.string = string; } StringStream.prototype = { done: function() {return this.pos >= this.string.length;}, peek: function() {return this.string.charAt(this.pos);}, next: function() { if (this.pos &lt; this.string.length) return this.string.charAt(this.pos++); }, eat: function(match) { var ch = this.string.charAt(this.pos); if (typeof match == "string") var ok = ch == match; else var ok = ch &amp;&amp; match.test ? match.test(ch) : match(ch); if (ok) {this.pos++; return ch;} }, eatWhile: function(match) { var start = this.pos; while (this.eat(match)); if (this.pos > start) return this.string.slice(start, this.pos); }, backUp: function(n) {this.pos -= n;}, column: function() {return this.pos;}, eatSpace: function() { var start = this.pos; while (/\s/.test(this.string.charAt(this.pos))) this.pos++; return this.pos - start; }, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { if (consume !== false) this.pos += str.length; return true; } } else { var match = this.string.slice(this.pos).match(pattern); if (match &amp;&amp; consume !== false) this.pos += match[0].length; return match; } } }; </textarea> </div> 

The idea is that we add a div overlay to every CodeMirror editor. This blocks scrolling from what is happening in this editor. It is a cheap and portable solution. Undoing scroll events is much more complicated, therefore this approach. It also allows us to do the following.

When users click on the editor (thus overlay), we do the inscription ignore all events of the pointer . This allows you to scroll, select text, move the cursor, etc. For UX, it is useful to indicate when the editor is focused. This can be done by highlighting the overlay! The red frame is used in the demo, you can, of course, do whatever you like.

Then, when users click elsewhere, the active editor (if active) deactivates and ignores scroll events (and other pointer events) again. As the border leaves, users will understand what happened and quickly find out that you first need to click the editor before you can edit the code in it, or select the text to copy and paste.

If you want to disable scrolling on the body while the editor is focused, look at this answer (there are many related questions and answers).

+1
source

Source: https://habr.com/ru/post/1265919/


All Articles