如何用Vue实现简易的富文本编辑器,并支持Markdown语法

前端开发经常会用到富文本编辑器,比如CKEditor,动不动一个库几十M的代码量,其中涉及许多你可能用不到的功能特性和相关设置,CKEditor最新版本的代码仓库就有接近2000个JS文件,300,000行代码。

可是如果你只需要一个简易版的编辑器,真的值得引入这么一个庞大的库吗?

今天我们从实现一个简易版的编辑器带大家了解一下其背后涉及到的原理。

开始

这个编辑器将要使用到markdown:一个简洁语法并且自带样式的语言,而且远比纯HTML的输入输出要安全得多。

首先,我们需要一些依赖包。 @ts-stack/markdown 和 turndown,@ts-stack/markdown是用来将markdown语法转化为HTML代码显示用的,而turndown是将HTML代码转化为markdown语言。

接下来,创建一个基础的Vue组件,命名为WysiwygEditor.vue,在组件中添加一个div元素,并且将它的contenteditable属性设置为true,然后添加一些Tailwind样式去美化一下。

<!-- WysiwygEditor.vue -->
<template><div><div@input="onInput"v-html="innerValue"contenteditable="true"class="wysiwyg-output outline-none border-2 p-4 rounded-lg border-gray-300 focus:border-green-300"/></div>
</template><script>
export default {name: 'WysiwygEditor',props: ['value'],data() {return {innerValue: this.value}},methods: {onInput(event) {this.$emit('input', event.target.innerHTML)}}
}
</script>

然后使用该组件:

<!-- Some other component -->
<template><!-- ... --><wysiwyg-editor v-model="someText" /><!-- ... -->
</template>
<!-- ... -->

看起来像这样

现在这个div元素的样式看起来像textarea 标签的效果了。

让文本变为富文本

在编辑器的上面会有一些带有bold,italic,underlined,headings,lists等文本的编辑按钮。并且上面会有对应功能的图标。可以通过安装fontawesome icon来实现。然后对按钮进行一些样式设置。

.button {@apply border-2;@apply border-gray-300;@apply rounded-lg;@apply px-3 py-1;@apply mb-3 mr-3;
}
.button:hover {@apply border-green-300;
}

先将这些按钮添加鼠标点击后的监听方法,后面我们会去实现每一个方法里的具体执行。

<!-- WysiwygEditor.vue -->
<template><!-- ... --><div class="flex flex-wrap"><button @click="applyBold" class="button"><font-awesome-icon :icon="['fas', 'bold']" /></button><button @click="applyItalic" class="button"><font-awesome-icon :icon="['fas', 'italic']" /></button><button @click="applyHeading" class="button"><font-awesome-icon :icon="['fas', 'heading']" /></button><button @click="applyUl" class="button"><font-awesome-icon :icon="['fas', 'list-ul']" /></button><button @click="applyOl" class="button"><font-awesome-icon :icon="['fas', 'list-ol']" /></button><button @click="undo" class="button"><font-awesome-icon :icon="['fas', 'undo']" /></button><button @click="redo" class="button"><font-awesome-icon :icon="['fas', 'redo']" /></button></div><!-- ... -->
</template>
<!-- ... -->

编辑器现在看起来是这样了

现在看起来是不是越来越接近了。还缺少按钮动作的执行方法。这里要用到document.execCommand,虽然MDN已经宣称将废弃该特性,但是大部分浏览器仍然支持。我们暂且还是使用它。

让我们通过它来实现applyBold方法

methods: {// ...applyBold() {document.execCommand('bold')},// ...
}

非常简洁明了,同样,我们来实现其它方法

// ...applyItalic() {document.execCommand('italic')},applyHeading() {document.execCommand('formatBlock', false, '<h1>')},applyUl() {document.execCommand('insertUnorderedList')},applyOl() {document.execCommand('insertOrderedList')},undo() {document.execCommand('undo')},redo() {document.execCommand('redo')}// ...

这里唯一需要说明的是applyHeading,因为我明确需要在此处指定所需的元素。使用这些命令后,可以预先对输出的元素标签进行一些样式设置

.wysiwyg-output h1 {@apply text-2xl;@apply font-bold;@apply pb-4;
}
.wysiwyg-output p {@apply pb-4;
}
.wysiwyg-output p {@apply pb-4;
}
.wysiwyg-output ul {@apply ml-6;@apply list-disc;
}
.wysiwyg-output ol {@apply ml-6;@apply list-decimal;
}

有了一定样式后,在输入框中输入一些内容

为了使得更美观一点,把空行用空的段落标签代替,以回车结束的内容归为一个段落

 // ...data() {return {innerValue: this.value || '<p><br></p>'}},mounted() {document.execCommand('defaultParagraphSeparator', false, 'p')},// ...

添加markdown支持

如果我想直接在编辑器里写markdown语法,暂时还不支持

# Hello, world!**Lorem ipsum dolor** _sit amet_* Some
* Unordered
* List1. Some
1. Ordered
1. List

结果看起来是这样

完全没有任何样式。别忘了,前面我们安装了@ts-stack/markdown库,现在可以使用了

import { Marked } from '@ts-stack/markdown'export default {name: 'WysiwygEditor',props: ['value'],data() {return {innerValue: Marked.parse(this.value) || '<p><br></p>'}},// ...

我们把输入的内容markdown语法转化为HTML代码之后,看起就正常了

同时还需要在组件传出本文编辑器数据的时候,进行转化,这里要用到前面安装的turndown

import TurndownService from 'turndown'export default {// ...methods: {onInput(event) {const turndown = new TurndownService({emDelimiter: '_',linkStyle: 'inlined',headingStyle: 'atx'})this.$emit('input', turndown.turndown(event.target.innerHTML))},
// ...

让我们把编辑器中输入的markdown语法文本在页面中通过模板输出后的效果

<!-- Some other component -->
<template><!-- ... --><wysiwyg-editor v-model="someText" /><pre class="p-4 bg-gray-300 mt-12">{{ someText }}</pre><!-- ... -->
</template>

同步输入输出,内容是一致的,没有任何问题

看起来一切正常,达到了我们想要的效果。下面是全部的代码

<template><div><div class="flex flex-wrap"><button @click="applyBold" class="button"><font-awesome-icon :icon="['fas', 'bold']" /></button><button @click="applyItalic" class="button"><font-awesome-icon :icon="['fas', 'italic']" /></button><button @click="applyHeading" class="button"><font-awesome-icon :icon="['fas', 'heading']" /></button><button @click="applyUl" class="button"><font-awesome-icon :icon="['fas', 'list-ul']" /></button><button @click="applyOl" class="button"><font-awesome-icon :icon="['fas', 'list-ol']" /></button><button @click="undo" class="button"><font-awesome-icon :icon="['fas', 'undo']" /></button><button @click="redo" class="button"><font-awesome-icon :icon="['fas', 'redo']" /></button></div><div@input="onInput"v-html="innerValue"contenteditable="true"class="wysiwyg-output outline-none border-2 p-4 rounded-lg border-gray-300 focus:border-green-300"/></div>
</template><script>
import { Marked } from '@ts-stack/markdown'
import TurndownService from 'turndown'export default {name: 'WysiwygEditor',props: ['value'],data() {return {innerValue: Marked.parse(this.value) || '<p><br></p>'}},mounted() {document.execCommand('defaultParagraphSeparator', false, 'p')},methods: {onInput(event) {const turndown = new TurndownService({emDelimiter: '_',linkStyle: 'inlined',headingStyle: 'atx'})this.$emit('input', turndown.turndown(event.target.innerHTML))},applyBold() {document.execCommand('bold')},applyItalic() {document.execCommand('italic')},applyHeading() {document.execCommand('formatBlock', false, '<h1>')},applyUl() {document.execCommand('insertUnorderedList')},applyOl() {document.execCommand('insertOrderedList')},undo() {document.execCommand('undo')},redo() {document.execCommand('redo')}}
}
</script>

结论

只需要87行代码便实现了一个简易的富文本编辑器。虽然功能还是太简单,但是最起码我们知道了实现一个富文本编辑器后面的原理。后面需要增加功能就不是什么难事了。

分享硬核的编程知识,关注“太空编程”公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/354583.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

互联网基建成果,快速实现一个clubhouse要多久

最近国外的一款基于语音的社交软件clubhouse掀起了一股热潮&#xff0c;尤其是在Eleon Musk的带动下&#xff0c;整个互联网圈内人都在第一时间抢先体验。不管它为什么会火&#xff0c;肯定不是技术上有什么特别的优势。随着整个互联网技术生态的不断发展&#xff0c;互联网基础…

阿里帝国到底有多庞大

马云&#xff0c;男&#xff0c;1964年9月10日生于浙江省杭州市&#xff0c;祖籍浙江省嵊州市谷来镇&#xff0c; 阿里巴巴集团主要创始人&#xff0c;现担任阿里巴巴集团董事局主席、日本软银董事、TNC中国理事会主席兼全球董事会成员、华谊兄弟董事、生命科学突破奖基金会董事…

如何搭建一个内部组件共享平台

如今前端越来越趋于组件化的开发方式&#xff0c;最大的益处就是UI页面和逻辑的共用。在开发者的眼里&#xff0c;如果你打开一个网站&#xff0c;组件化的开发方式会让你看起来像这个样子&#xff1a;逻辑功能上我们会封装成一个库&#xff0c;然后NPM发布到公共仓库上&#x…

不要讨厌HATEOAS

或我如何学会不再担心和爱HATEOAS REST已成为实现Web服务的事实上的解决方案&#xff0c;至少已成为一种流行的解决方案。 这是可以理解的&#xff0c;因为REST在使用HTTP规范时提供了一定程度的自我文档。 它经久耐用&#xff0c;可扩展&#xff0c;并提供了其他一些理想的特…

前端如何进行日志驱动开发

日志在开发过程中的作用自不必说&#xff0c;一旦程序出现问题&#xff0c;我们首先想到的是通过日志监控去追查。 好的日志可以通过应用程序执行的历史记录模拟出用户在使用程序的时候操作的完整过程。 想知道发生了什么 为了便于我们分析程序哪里出现问题&#xff0c;我们…

消息钩子学习工程

前奏近来一直在自学Windows Hook相关的知识&#xff0c;已经尝试多种注入方式。尤其对消息钩子方式很感兴趣&#xff0c;因为看到Spy能够截获系统中绝大多数应用的消息流&#xff0c;就很想知道它的工作原理&#xff0c;打算制作属于自己的Spy。消息钩子简介&#xff1a;消息钩…

[Angular] 笔记 8:list/detail 页面以及@Input

1. list 页面 list/detail 是重要的 UI 设计模式。 vscode terminal 运行如下命令生成 detail 组件&#xff1a; PS D:\Angular\my-app> ng generate component pokemon-base/pokemon-detail --modulepokemon-base/pokemon-base.module.ts CREATE src/app/pokemon-base/p…

javaone_JavaOne 2012 – 2400小时! 一些建议

javaone您可能已经看到JavaOne 2012 Content Catalog在线。 计划委员会经过数周的紧张工作&#xff0c;对每个提案进行了分类&#xff0c;审查&#xff0c;评分和讨论&#xff0c;我们终于设法为您设置了&#xff08;希望如此&#xff09;有趣的组合。 整整105天或2400小时&…

推荐几个最近Star过的Github仓库

平时逛Github的时候&#xff0c;总是顺手对一些自己认为好的仓库给个 Star&#xff0c;一是对作者的鼓励&#xff0c;二来推荐给关注自己的人&#xff08;首页动态可见&#xff09;。 下面列举了一些我平时 Star 过的仓库&#xff0c;顺便也推荐给我的读者。 Front-End Checkli…

使用Gatling + Gradle + Jenkins Pipeline为您的JAX-RS(和JavaEE)应用程序进行连续压力测试...

在这篇文章中&#xff0c;我将解释如何使用Gatling项目为您的JAX-RS Java EE端点编写压力测试&#xff0c;以及如何将它们与Gradle和Jenkins Pipeline集成&#xff0c;因此&#xff0c;除了进行简单的压力测试外&#xff0c;您还可以使用以下方法&#xff1a; 连续的压力测试&a…

使用 VuePress 搭建一个自己的知识文档

最近准备对前端知识做一个梳理&#xff0c;将自己的平时遇到的问题和解决方案形成一个知识文档。本文记录了搭建 VuePress 的主要过程&#xff0c;同时也提供了部分自定义的配置&#xff0c;示例地址&#xff1a;http://doc.i-fanr.com 环境搭建 VuePress 有着比较完善的中文文…

金三银四跳槽面试季,我整理前端知识做了个网站

每年的金三银四&#xff0c;都将迎来求职面试的一个高峰期&#xff0c;为什么会有那么多的求职需求&#xff1f;多是因为以下几个来源&#xff1a;已拿 offer 等年终奖的&#xff1a;年前已经找到机会&#xff0c;领了年终奖辞职要到新公司报到的临时起意要辞及裸辞的&#xff…

hadoop的Map阶段的四大步骤

深入理解map的几个阶段是怎样执行的。转载于:https://www.cnblogs.com/xubiao/p/7846080.html

小程序 Typescript 最佳实践

小程序结合TypeScript开发&#xff0c;如果用第三方框架&#xff0c;首选Taro已完美支持。但是如果你选择原生开发&#xff0c;那么下面的这份实践可能会帮到你。小程序 Typescript 最佳实践使用 gulp 构建&#xff08;支持 typescript 和 less/sass/scss&#xff09;使用 type…

这款电脑升降桌美到我了

一直在寻觅一款集颜值与功能于一体的电脑升降桌&#xff0c;这款乐歌 E5 电动桌终于成功地满足了我的需求。有黑白两款颜色可选&#xff0c;但其中白色钢化玻璃版常适合用来作为白色系桌面的基础——四周圆角设计&#xff0c;再加上碳素钢的桌体框架&#xff0c;整体非常有质感…

分享一个引起极度舒适的工作桌面

干净整洁的桌面或许不能带给你工作效率的提升&#xff0c;但一定会给你带来愉悦的心情。长期码字一定需要一个升降桌&#xff0c;可自由地调节高度&#xff0c;以保证舒适的坐姿和灵活的视角。另外坐久了&#xff0c;累了还能站立工作一会儿。有了外显之后&#xff0c;如果不需…

canvas绘制多边形

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>canvas绘制多边形</title> </head> <body> <canvas id"canvas" style"border: 1px solid darkcyan;" width…

ehcache rmi_EhCache复制:RMI与JGroups

ehcache rmi最近&#xff0c;我正在研究一种需要复制缓存的产品。 缓存提供程序已经确定-EhCache&#xff0c;剩下的就是有关传输的问题。 哪一个是最佳选择&#xff1f; 这里的最佳选择是指性能更好的选择。 仅在两个可用传输之间进行了性能评估-JGroups和RMI&#xff0c;对其…

Element Table 可以实现哪些常见的有用的功能

最近项目中频繁使用 table 功能&#xff0c;因为 UI 框架使用的又是 Element UI&#xff0c;于是总结下在 Element 下 el-table 组件使用技巧。1.行背景色table 组件提供了 row-style 属性&#xff0c;说明&#xff1a;行的 style 的回调方法&#xff0c;也可以使用一个固定的 …

如果在这样的环境中写代码,会不会很高效

桌面环境分享系列又来了。我会把平时看到的好的桌面布置分享给大家&#xff0c;帮助大家在桌面整理和打造方面提供一些新的想法和创意。如何评价一个开发桌面的好坏&#xff0c;首先一定要清爽整洁&#xff0c;该有的家伙事儿一定要有。不是要看上去要有多高大上&#xff0c;重…