抛开框架,原生 JS 实现微信 MarkDowm 编辑器📝

2,464 阅读11分钟

现在许多人都应该有写公众号的习惯,有的写公众号为了分享,有的是为了记录生活。把文字写出来,再通过一些编辑器进行排版之后,完成一篇文章往往要花费大量的时间,往往排版是需要最久的,因为所有的编辑器都只是尽可能的满足使用者的需求,并没有百分百符合自己心意的编辑器,今天就讲讲如何实现一个自己的专属编辑器。

需要实现的功能

  • markdown 语法支持
  • 代码块高亮
  • 自定义排版规则
  • 把自定义的排版文章复制到微信公众号的编辑器内保留排版

效果展示

在这里展示的只是最基本的功能,细节没有处理,我会一步一步告诉你们如何去实现,想要有多好看的效果就得靠你们自己的想法来进行美化了。

这里有个我自己定制的完整版预览地址:Wechat Markdown Editor

Github 项目地址:github.com/didadi599/w…

觉得还可以的小伙伴记得留下你们的 star 哦~😋

HTML 布局

做我们前端开发的第一步当然是布局啦,虽然实际效果中并没有布局多少,就随意排了一下,大家别介意,主要是怎么实现上面功能的思路。

在项目文件夹下新建 index.html 文件,布局代码如下,因为真的实在是太简洁了,就不多做解释了。

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="./css/index.css">
  <title>Editor</title>
</head>
<body>
  <div class="wrap">
    <div>
      <button id="copy">复制</button>
      <textarea id="textarea" class="textarea"></textarea>
    </div>

    <div id="paper" class="paper"></div>
  </div>
  
  <script src="./js/index.js"></script>
</body>
</html>

看到这样写页面的感觉是不是非常怀念,自从会了框架之后,大家多久没写过这样的页面了😭。代码中引入了 css 文件夹中的 index.css 文件和 js 文件夹中的 index.js 文件,为接下来准备。(这人真 TM 啰嗦🙃)

css 文件也不多,就只有这么几个。

.wrap {
  display: flex;
}

.textarea {
  resize: none;
  height: 300px;
  width: 200px;
  margin-right: 20px;
  flex: 0 200px;
}

.paper {
  word-break: break-all;
  width: 500px;
  height: 600px;
  background: #eeeeee;
}

JS 编写

布局搞定了,接下来就开始进行我们逻辑代码的编写。

js/index.js 文件:

// textarea 元素
var textarea = document.getElementById('textarea');

// paper 元素
var paper = document.getElementById('paper');

// 监听 textarea 元素的按键弹起事件
textarea.onkeyup = function (e) {
  var value = e.target.value;
  paper.innerHTML = value;
}

暂时先写下这十行入门级代码,上面的代码我们分别得到了 textarea 元素和 paper 元素,然后监听 textarea 元素的按键弹起事件。

当按键弹起,我们获取 textarea 元素中的文本内容,也就是它的 value 值,然后把这个值渲染到页面上的 paper 元素内部。

这样我们就可以一边输入,一边看到旁边的 paper 元素也一起渲染出了我们输入的内容到页面上。

其实,这已经炒鸡像一个简单的编辑器了!!!

请求 markdown-it 的帮忙

接下来我们需要借助 Github 上开源的 markdow-it 项目,这个项目可以轻松的把 markdown 语法转换成 html 元素。毕竟,从零实现一个 markdow 解析器那可不是一般的麻烦,码字可能可以码到我猝死。有现成的东西咱门直接拿来用就好,毕竟不是每个人都能手写一个解析器的。

这是 markdown-it 的项目地址:github.com/markdown-it…

下载的话用这个地址(Ctrl + s 直接保存到本地就可以):raw.githubusercontent.com/markdown-it…

把下载下来的代码放到我们项目下的 js 文件夹内,在 index.html 中引入我们的 markdow-it.min.js 文件。

index.html 文件:

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="./css/index.css">
  <title>Editor</title>
</head>
<body>
  <div class="wrap">
    <div>
      <button id="copy">复制</button>
      <textarea id="textarea" class="textarea"></textarea>
    </div>

    <div id="paper" class="paper"></div>
  </div>
  
  <script src="./js/markdown-it.min.js"></script>
  <script src="./js/index.js"></script>
</body>
</html>

Tips:注意引入的顺序!!!

继续书写我们的 index.js 文件。

// textarea 元素
var textarea = document.getElementById('textarea');

// paper 元素
var paper = document.getElementById('paper');

// 初始化 markdown-it
var md = window.markdownit();

// 监听 textarea 元素的按键弹起事件
textarea.onkeyup = function (e) {
  var value = e.target.value;
  value = md.render(value);
  paper.innerHTML = value;
}

增加了两行代码,分别是初始化我们刚刚引入的 markdown-it 编译器,调用它的 render() 方法,讲我们文本框内的 value 转换成 html 元素,然后渲染到页面上。到这里,我们已经可以渲染出这样的效果了。

仔细的你们肯定发现,代码块并没有高亮😱,这究竟是咋回事捏。

这是因为,我们的 markdown-it 并没有给我们内置代码块高亮的功能,它仅仅只是一个 markdown 语法解析器。如果需要高亮的话,得需要借助一下另一个 js 库的帮忙,还需要对 markdow-it 进行一些配置。

请求 highlight.js 的帮忙

我们需要用到一个叫做 highlight.js 的代码块高亮库,也是由一些大佬开发给我们这些菜鸡使用的东西。

highlight.js 项目地址:github.com/highlightjs…

下载地址:cdnjs.cloudflare.com/ajax/libs/h…

样式文件在下面这个地址里面挑一个就好,不会下载的直接复制下来粘贴到一个 js 文件里就好了😂

github.com/highlightjs…

下好我们的 js 文件和 css 文件之后,肯定先得把它们都引入到我们的项目里,再次强调引入的顺序。

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="./css/index.css">
  <link rel="stylesheet" href="./css/a11y-dark.css">
  <title>Editor</title>
</head>
<body>
  <div class="wrap">
    <div>
      <button id="copy">复制</button>
      <textarea id="textarea" class="textarea"></textarea>
    </div>

    <div id="paper" class="paper"></div>
  </div>

  <script src="./js/highlight.min.js"></script>
  <script src="./js/markdown-it.js"></script>
  <script src="./js/index.js"></script>
</body>
</html>

代码高亮的样式最好放到 index.css 的下面,这样可以防止自己写的样式名在某种机缘巧合之下重名然后覆盖掉代码高亮的样式。

代码高亮的 js 文件必须要比 markdown-it 先引入,这是我之前踩的一个坑,当时真的很难受😥。

继续整我们的 js 代码。

// textarea 节点
var textarea = document.getElementById('textarea');

// paper 节点
var paper = document.getElementById('paper');

// 复制按钮
var copyBtn = document.getElementById('copy');

// 初始化 markdownit
var md = window.markdownit({
  // markdown-it 高亮配置
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return '<pre class="hljs"><code>' +
          hljs.highlight(lang, str, true).value +
          '</code></pre>';
      } catch (__) { }
    }

    return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
  }
});

textarea.onkeyup = function (e) {
  var value = e.target.value;
  value = md.render(value);
  paper.innerHTML = value;
}

这次的代码不多,就只是在初始化 markdown-it 的时候,给它传入一个对象参数。这几行代码大家也不用纠结啥意思,就直接复制粘贴就好了,这是人家定的规矩,照着写就好,如果真想知道是啥原理,就去看 markdown-it 的源码吧🤔。

这样,我们的代码就可以高亮了,我选择的代码样式是 a11y-dark 。效果就变成了这样。

这样,我们的代码高亮就搞定了,但是,为啥我们的其他元素也这么朴素😂,这就是我们要实现的自定义排版了。方法也非常的简单,只要写一个新的主题样式给他们就好了。

自定义排版样式

css 文件夹下新建一个 default.css 的主题样式。然后把它引入到 html 中。

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="./css/index.css">
  <link rel="stylesheet" href="./css/default.css">
  <link rel="stylesheet" href="./css/a11y-dark.css">
  <title>Editor</title>
</head>
<body>
  <div class="wrap">
    <div>
      <button id="copy">复制</button>
      <textarea id="textarea" class="textarea"></textarea>
    </div>

    <div id="paper" class="paper"></div>
  </div>

  <script src="./js/highlight.min.js"></script>
  <script src="./js/markdown-it.js"></script>
  <script src="./js/clipboard.min.js"></script>
  <script src="./js/index.js"></script>
</body>
</html>

这次我们就不需要借助其他工具的帮忙了,毕竟定制嘛,肯定得我们按自己喜好来进行定制了。定制也只是进行一些 css 样式的添加,我就不进行过多的解释了。

.paper h1 {
  background: #f9ed69;
  margin-bottom: 15px;
  margin-top: 0;
}
.paper h2 {
  background: #f08a5d;
  margin-bottom: 15px;
  margin-top: 0;
}
.paper h3 {
  background: #b83b5e;
  margin-bottom: 15px;
  margin-top: 0;
}
.paper h4 {
  background: #6a2c70;
  margin-bottom: 15px;
  margin-top: 0;
}

我只给 h1 ~ h4 添加了自定义样式进行演示,为什么前面要加个 .paper 呢?

这样可以防止这个样式影响到全局的 h1 ~ h4 的样式,只有当它们在有 class 为 .paper 的容器里的 h1 ~ h4 才会有效果,.paper 只是一个名字,可自行修改,把 class 添加到我们用来渲染的那个标签就好。

编写好自己喜欢的样式之后,现在效果就变成这亚子了。

我喜欢的样子,即使再丑,也🐟女无瓜。

把定制好的样式内容复制到微信公众号

不知不觉,我们的需求就剩下最后一个了,就是把我们定制好的内容复制粘贴到我们的公众号里,还需要带样式的那种。这个也是跟前面一样简单的操作,我们借助一下别人的工具就好啦😜。

我们需要使用到一个叫 clipboard.js 的工具。这个工具主要就是用来做快捷复制的,而且还能复制上我们的标签样式。

clipboard.js 项目地址:github.com/zenorocha/c…

clipboard.js 下载地址:raw.githubusercontent.com/zenorocha/c…

还是老样子,引入我们的下载好的 js 文件,顺序这个可以随意一点,只要在 index.js 文件之前引入就好。

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="./css/index.css">
  <link rel="stylesheet" href="./css/default.css">
  <link rel="stylesheet" href="./css/a11y-dark.css">
  <title>Editor</title>
</head>
<body>
  <div class="wrap">
    <div>
      <button id="copy" data-clipboard-action="copy" data-clipboard-target="#paper">复制</button>
      <textarea id="textarea" class="textarea"></textarea>
    </div>

    <div id="paper" class="paper"></div>
  </div>

  <script src="./js/highlight.min.js"></script>
  <script src="./js/markdown-it.js"></script>
  <script src="./js/clipboard.min.js"></script>
  <script src="./js/index.js"></script>
</body>
</html>

我们需要给我们的复制按钮加上两个属性,data-clipboard-action="copy"data-clipboard-target="#paper",第一个属性主要是告诉 clipboard 点击这个按钮的时候执行的是复制操作,第二个属性是告诉 clipboard 它应该复制 idpaper 容器内的内容。

js 代码也是出奇的简单。

// textarea 节点
var textarea = document.getElementById('textarea');

// paper 节点
var paper = document.getElementById('paper');

// 复制按钮
var copyBtn = document.getElementById('copy');

// 初始化 markdownit
var md = window.markdownit({
  highlight: function (str, lang) {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return '<pre class="hljs"><code>' +
          hljs.highlight(lang, str, true).value +
          '</code></pre>';
      } catch (__) { }
    }

    return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
  }
});

// textarea 的键盘弹起事件
textarea.onkeyup = function (e) {
  var value = e.target.value;
  value = md.render(value);
  paper.innerHTML = value;
}

// copy 按钮的点击事件
copyBtn.onclick = function (e) {
  var clipboard = new ClipboardJS('#copy');

  // 复制成功的回调函数
  clipboard.on('success', function () {
    alert('复制成功');
    e.clearSelection();
  });

  // 复制失败的回调函数
  clipboard.on('error', function () {
    alert('复制失败');
  });
}

获取我们的 copy 按钮元素,然后 var 一个 clipboard 的实例,并向它传入按钮的 id ,下面两个 on 方法是用来监听是否复制成功的回调函数。

这样我们的复制功能就实现了,我们点击一下复制,出现一个提示窗口,点击确定,完成复制。

接下来去我们的公众号内进行一波粘贴试验。

虽然看起来宽度并没有铺满,但是预览和发布的时候,是完全 OK 的,莫慌。

效果跟我们的样式一样的话,恭喜你们,你们成功的学会了如何写一个专属🐟自己的微信公众号 Markdown 编辑器,简直奈斯,撒花~撒花~,热烈庆祝!!!

源码我把它也放到我那个完整的项目里吧,可能会有些细微差别,也仅仅只是名字,不影响不影响😂

源码:github.com/didadi599/w…

上面地址的 editor-demo 文件夹里的就是源码啦。

公众号:辣鸡OvO👈