通过codemirror实现一个可插入自定义标签的textarea

写在开头

本章来分享一个小案例,具体效果如上图所示。⬆

该案例主要通过 CodeMirror 来实现,而 CodeMirror 是一个通过 JS 实现的Web代码编辑器组件,它带有大量的语言模式、CSS主题和很多高级的插件功能,还允许进一步扩展,总之它非常强大。😋

是一个很不错的产品,值得推荐入手。👻

本次小编使用的是 CodeMirror5 版本,最新版本已经到 6 了,而且开发者们也在积极维护。

这两个版本差别还是挺大的,不过呢,对本篇文案的案例影响却不大,我们只是使用到了其中一个很小的功能,所以你想使用哪个版本都是可以的。

贴两个版本的文档地址,可以自己瞅瞅:😋

codemirror5文档

codemirror6文档

正文

接下来就开始进入主题了。👏

安装:

javascript 复制代码
npm install codemirror@5.65.14

演示部分小编选择使用 Vue,其他技术栈都是可以的。

页面结构:

html 复制代码
<template>
  <div>
    <div class="textarea-editor">
      <textarea ref="textarea" class="textarea" />
    </div>
    <input v-model="content" placeholder="请输入标签内容" />&nbsp;
    <button @click="addTag">添加</button>
  </div>
</template>

JS 逻辑:

javascript 复制代码
<script>
// 编辑器基本样式
import "codemirror/lib/codemirror.css";
// 提示语样式
import "codemirror/addon/display/placeholder";
import CodeMirror from "codemirror";

export default {
  data() {
    return {
      content: "",
      $cm: null, // 添加 $ 开头能避免实例对象进行依赖收集
    };
  },
  mounted() {
   this.init();
  },
  methods: {
    /** @name 初始化 **/
    init() {
      this.$cm = CodeMirror.fromTextArea(this.$refs.textarea, {
        placeholder: "请输入......",
        lineWrapping: true, // 自动换行
      });
    },
    /** @name 标记 **/
    markVariable(mark) {
      // 创建自定义的标签元素
      const spanDom = document.createElement("span");
      const label = mark.variable;
      spanDom.title = label;
      spanDom.innerText = label;
      spanDom.classList.add("textarea-tag");
      spanDom.dataset.variable = mark.variable;
      // 标记特定的文本:https://codemirror.net/5/doc/manual.html#api_history
      this.$cm.markText(mark.start, mark.end, {
        replacedWith: spanDom, // 将特定位置的文本替换成给定的节点元素,必须是行元素,不能是块元素
        atomic: true, // 原子化,会把节点元素当成一个整体,光标不会进入其中
      });
    },
    /** @name 插入 **/
    insertVariable(content) {
      if (!content) return;
      // 获取自定义标签未插入之前的光标对象,记录了光标的位置和状态
      const cursor = this.$cm.getCursor();
      // 将自定义标签插入textarea中:https://codemirror.net/5/doc/manual.html#api_selection
      // 在前后加上 ##与 $$ 只是方便我们后期getValue()的时候好进行内容匹配
      this.$cm.replaceSelection(`##${content}$$`);
      // 将插入的自定义标签进行标记(将 ##${content}$$ 变成 <span>{content}</span>)
      this.markVariable({
        start: cursor,
        end: this.$cm.getCursor(),  // 获取自定义标签插入之后的光标对象
        variable: content,
      });
      this.$cm.setCursor(this.$cm.getCursor());
      this.$cm.focus();
    },
    /** @name 添加自定义标签 **/
    addTag() {
      this.insertVariable(this.content);
    },
    /** @name 获取值 **/
    getValue() {
      const value = this.$cm.getValue();
      console.log(value);
    },
  },
};
</script>

Em......以上代码小编都写上了详细的注释,代码也缩减到不能再缩啦,应该不是很难,相信都能看懂啦。😉

可能需要注意的是 CodeMirror 的光标对象:

CodeMirror 给我们提供了很多操作输入内容的方法,很多时候都需要我们提供这个光标对象。

最后就是样式啦:

javascript 复制代码
<style scoped>
.textarea-editor {
    width: 300px;
    height: 140px;
    border: 1px solid rgb(234, 234, 234);
    font-size: 14px;
    overflow: hidden;
    position: relative;
    cursor: text;
    margin-bottom: 10px;
}
.textarea-editor:hover {
    border-color: #6c8dff;
}
.textarea {
    width: 100%;
    height: 100%;
    border: none;
    background-color: transparent;
}
</style>

<style>
/* 全局样式,覆盖codemirror一些基本样式 */
.textarea-editor .CodeMirror {
    width: 100%;
    height: 100%;
}
.textarea-editor pre.CodeMirror-line,
.textarea-editor pre.CodeMirror-line-like {
    line-height: 24px;
    outline: none;
}
.textarea-tag {
    width: fit-content;
    height: 24px;
    line-height: 24px;
    text-align: center;
    min-width: 60px;
    max-width: 260px;
    font-size: 12px;
    border-radius: 11px;
    color: #fff;
    background-color: #fab30b;
    box-sizing: border-box;
    padding: 2px 6px;
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
}
</style>

以上就是这个案例的全部源码内容啦。👏

结尾

可能有小伙伴会在想做一个可插入自定义标签的 textarea ,需要那么麻烦专门去引入 CodeMirror 吗?

当然不需要,如果仅仅是这个功能的话❗

但是,由于小编业务中对这个 textarea 还有很多其他需求,比如字体颜色的处理、智能提示、复制粘贴、兼容性问题等等,所以选择 CodeMirror 可以帮忙省下很多麻烦事。


至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。

老样子,点赞+评论=你会了,收藏=你精通了。

相关推荐
AiMan9 分钟前
豆包爱学还是不可以导出左侧所有对话,只能单容导出,方法不对吗
前端
无懈可击24 分钟前
Vue 可视化AI表单设计器 FcDesigner v3.6 版本发布,支持导出 SQL功能!
vue.js·开源·ai编程
AiMan30 分钟前
Grok生成的内容能否下载吗?
前端
徐小夕1 小时前
JitArchives,一款基于JitWord协同AI文档打造的企业级电子档案系统
前端·vue.js·github
陈随易1 小时前
Node.js 2026上半年都有哪些性能提升?
前端·后端·程序员
墨林陌2 小时前
AI 热点日报(2026-11-09):苹果代理式Siri曝光,Anthropic推进IPO,编码智能体实测翻车
前端
vipxieliang2 小时前
JavaScript 迭代器与生成器:控制异步与惰性求值
前端·javascript
Hilaku2 小时前
Event Loop 这篇你看完就不用再看别的了!
前端·javascript·程序员
恋猫de小郭2 小时前
Flutter 下 AOT 一个你怎么都想不出来的 Bug ,JIT 正常的计算,在 AOT 居然算错了
android·前端·flutter