This is very tricky to get right, in particular because dragging in an editor should, I believe, select text. Overloading it for scrolling seems counterintuitive. Therefore, I propose the following.
This resembles blackmiacool's suggestion in the comments a lot. I only chose not to disable 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 < 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 && 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 && 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 < 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 && 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 && consume !== false) this.pos += match[0].length;
return match;
}
}
};
</textarea>
</div>
The idea is that we add an overlay div
to every CodeMirror editor. This blocks scrolling from happening in that editor. It is a cheap and portable solution. Cancelling scroll events is much harder, hence this approach. It also allows us to do the following.
When users click an editor (thus, the overlay), we make the overlay ignore all pointer events. This enables scrolling, text selection, cursor movement, et cetera. For UX, it is good to indicate when an editor is focused. This can be done by highlighting the overlay! A red border is applied in the demo, you can of course do anything you like.
Then, when users click anywhere else, the active editor (if one is active) is deactivated and again ignores scroll events (and other pointer events). Because the border goes away, users will understand what happened and quickly learn that they first need to click an editor before being able to edit code in it, or select text for copy & pasting.
When you want to disable scrolling on the body while an editor is focused, check out this answer for example (there are many related questions and answers).