CodeMirror 是最流行的代码编辑器之一,包括写作软件 WonderPen 在内的很多工具都使用它开发。

与印象笔记等笔记软件不同,WonderPen 的定位是一款写作软件。写作软件与笔记软件之间有很多区别,最大的不同就是前者更专注于提升写作时的体验,而后者则更专注于数据的存储与连接。写作软件中有一个常见的特性,「打字机模式」。

打字机模式

在 Word 等大多数编辑软件中,如果你输入了很多内容,超过一屏之后,光标通常会位于屏幕最下方,这时除非你滚动一下文档,否则光标会一直处于屏幕最下的位置,你的视线也只能一直盯在这个位置,这对需要长时间输入的用户来说并不友好。所谓打字机模式,其实就是像传统的打字机一样,在用户输入时将光标在屏幕上的 y 坐标固定(比如固定在屏幕中间),这样便能保持用户视线高度固定。在技术上来说,就是固定文档的滚动位置。

在 CodeMirror 中,实现这样的功能很简单,下面是一个例子。

首先,我们生成一个 CodeMirror 编辑器实例

const cm = CodeMirror(document.body, {
  lineNumbers: true
});

cm.setSize("100%", "100%");

我们并不需要让滚动条一直固定,因为有时候用户需要手动滚动文档,查看上方或下方的内容,因此我们只需要监听它的 change 事件,并在每次事件触发时更新滚动位置。

cm.on("changes", (cm, changes) => updateByTypewriterMode(cm, changes));

这儿的 updateByTypewriterMode() 函数是我们的一个自定义函数,CodeMirror 会传入两个参数进去,分别是当前 CodeMirror 编辑器的实例以及本次 change 事件对应的修改内容。

updateByTypewriterMode() 函数的内容如下:

function updateByTypewriterMode(cm, changes) {
  if (cm.getSelection().length !== 0) {
    return;
  }

  const scroll_origins = ["+input", "+delete", "*compose", "paste"];

  for (let i = 0, len = changes.length; i < len; i++) {
    let each = changes[i];
    let origin = each.origin;
    if (scroll_origins.includes(origin)) {
      cm.execCommand("wpScrollSelectionToCenter");
      return;
    }
  }
}

在发生指定类型的修改时,执行 CodeMirror 的命令 wpScrollSelectionToCenter。这个命令是一个自定义命令,定义如下:

CodeMirror.commands.wpScrollSelectionToCenter = function(cm) {
  if (cm.getOption("disableInput")) {
    return CodeMirror.Pass;
  }

  // 获取当前光标所在位置
  let top = cm.cursorCoords(true, "local").top;
  // 获取当前编辑器高度
  let editor_height = cm.getWrapperElement().offsetHeight;
  // 获取当前行高
  let lh = cm.defaultTextHeight();
  let scroll_y = Math.round(top - editor_height / 2 + lh / 2);
  cm.scrollTo(null, scroll_y);
};

同时,我们可以再用 CSS 给编辑器下方添加一定高度的空白,保证需要的时候滚动条有足够的空间。

.CodeMirror-lines {
  padding-bottom: 50%;
}

这样,打字机模式就基本完成了,用户输入内容时,如果文档长度已经超过容器高度的一半,则光标所在行会自动滚动到屏幕中间。

示例

你可以点击这儿,查看在线的完整示例。此例中,你可以在 CodeMirror 编辑器中随意输入内容,可以看到光标始终会保持在容器的上 1/2 位置。