介绍
富文本编辑器是现代网络应用程序中常见的一种功能,它使用户能够以类似于字处理软件的方式编辑文本内容。虽然市面上有很多成熟的富文本编辑器库和插件可供选择,但是利用HTML5的contenteditable属性,我们也可以轻松地创建一个简易的富文本编辑器,而无需依赖第三方库或插件。
这是mdn对于contenteditable属性的介绍 —— mdn web doc
当然,本文不是介绍如何开发一款富文本编辑器的,而是介绍 Salary主题 的表情图片插入是如何实现的。
实现表情图片插入
Salary主题 的表情图片插入,是我当初开发主题时的一个特殊的想法。市面上大部分的博客主题都是使用emoji,为了能有更丰富的表情,参考了 掘金 的评论组件,进而实现了 Salary主题 的表情插入。
起步
首先,我们需要一个具有contenteditable属性的div元素作为编辑器的容器,以及一个用于显示表情图片的面板。
<div contenteditable="true" spellcheck="false" />
编码
Salary 是基于 vueJS 开发的,所以我们在这里把表情面板提取成一个组件,新建一个Emoji.vue,这里直接贴出源码:
<script lang='ts' setup>
interface Emoji<T = string> {
name?: T
url: string
}
const emits = defineEmits(['emojiClick'])
// 定义表情释义
const emojiNames: string[] = [
'微笑', '呲牙', '色', '发呆', '可怜', '流泪', '害羞', '闭嘴', '睡', '吃瓜群众', '尴尬', '发怒', '调皮', '撇嘴', '思考', '不失礼貌的微笑', '奸笑', '抓狂',
'吐', '偷笑', '愉快', '白眼', '傲慢', '困', '灵光一现', '流汗', '憨笑', '捂脸', '奋斗', '咒骂', '疑问', '嘘', '晕', '衰', '敲打',
'再见', '擦汗', '抠鼻', '泣不成声', '坏笑', '左哼哼', '右哼哼', '打哈欠', '鄙视', '委屈', '快哭了', '摸头', '阴险', '亲亲', '机智', '得意', '大金牙', '拥抱',
'大笑', '送心', '震惊', '拽酷', '尬笑', '大哭', '笑哭', '做鬼脸', '红脸', '鼓掌', '恐惧', '斜眼', '嘿哈', '惊讶', '绝望的凝视', '囧', '皱眉', '耶', '石化',
'我想静静', '吐血', '互粉', '互相关注', '加好友', '强', '钱', '飞吻', '打脸', '惊恐', '悠闲', '泪奔', '舔屏', '紫薇别走', '听歌', '难过', '生病', '绿帽子', '如花',
'惊喜', '吐彩虹', '吐舌头', '无辜呆', '看', '白眼的狗', '黑脸', '猪头', '熊吉', '不看', '玫瑰', '凋谢', '嘴唇', '爱心', '心碎', '赞', '弱', '握手', 'ok', '谢谢',
'比心', '碰拳', '击掌', '左', '右', '力量', '胜利', '抱拳', '勾引', '拳头', '庆祝', '礼物', '红包', '18禁', '去污粉', '666', '给力', 'v5', '菜刀', '炸弹', '便便',
'月亮', '太阳', '发财', '黄瓜', '西瓜', '啤酒', '咖啡', '蛋糕']
// 导入所有表情图片Url(.png格式)并存储在emojiList对象中:
const emojiList = import.meta.globEager('@/assets/emojis/*.png')
// 对所有表情进行排序、命名,并将其返回作为计算长度的值:
const emojis = computed(() => {
const emojiArray: Emoji[] = []
// 遍历数组并推入 emojiArray 中
Object.entries(emojiList).forEach(([_name, value]) => {
emojiArray.push({
url: (value as any).default,
})
})
// 根据图片 Url 的长度排序,并为每个表情添加正确的名称
emojiArray.sort((a, b) => a.url.length - b.url.length).map((item, index) => item.name = emojiNames[index])
return emojiArray
})
const show_emoji_popover = shallowRef(false)
// 派发事件
function handleEmojiClick(emoji) {
emits('emojiClick', emoji)
}
</script>
<template>
<n-popover v-model:popup-visible="show_emoji_popover" class="somnia-tooltip" trigger="click" raw
placement="bottom-start">
<template #trigger>
<button class="lite" max-w-10em truncate text-base h-2rem border-none rounded-lg font-500 transition-base px-0.65em
cursor-pointer>
<i class="fa-regular fa-face-grin-squint-tears" mr-1 />表情
</button>
</template>
<div class="title">
表情
</div>
<n-grid cols="6 s:3 m:6 l:9 xl:9 2xl:9" class="content emoji-wrapper" h-270px overflow-y-scroll p-0.5em
overflow-scroll responsive="screen" :x-gap="5" :y-gap="5">
<template v-for="emoji in emojis" :key="emoji.name">
<n-gi @click="handleEmojiClick(emoji)">
<img class="emoji-item" :src="emoji.url" w-36px h-36px cursor-pointer rounded flex p-1 transition items-center
justify-center>
</n-gi>
</template>
</n-grid>
</n-popover>
</template>
<style scoped lang="scss">
.emoji-item {
border: 1px solid transparent;
&:hover {
filter: drop-shadow(0 0 10px var(--primary-color));
border-color: var(--primary-color);
transition: 0s;
}
}
button {
font-size: 14px;
color: var(--primary-color);
border: 1px solid var(--primary-opacity-30);
background: var(--primary-opacity-10);
transition: .35s;
&:hover {
border-color: var(--primary-color);
}
}
</style>
在使用时,我们需要给div增加一些事件,同时需要引入Emoji.vue组件
<div ref="replyInput"
contenteditable="true"
disabled="disabled"
spellcheck="false"
:placeholder="placeholder"
class="reply-input replyInput"
@keydown="keyDown"
@paste="handlePaste" />
<Emoji @emojiClick="emojiClick" />
实现插入
重点来了,在点击表情的时候,如何实现插入html标签呢?
最初我是想通过 window.getSelection() 来实现插入图片到指定的光标位置的,但是使用过后发现总会有问题。
查阅资料之后,最终选择使用 document.execCommand 来实现。
MDN文档
当一个 HTML 文档切换到设计模式时,document暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素。
function emojiClick(emoji) {
const img = `<img src="${emoji.url}" alt="${emoji.name}" class="emoji" draggable="false">`
document.execCommand('insertHTML', false, img)
}
监听粘贴事件,粘贴的时候去除html格式,只粘贴纯文本。
function handlePaste(e) {
e.preventDefault()
// 粘贴纯文本
const text = e.clipboardData.getData('text/plain')
document.execCommand('insertHTML', false, text)
}
使用 document.execCommand 可以完美的在指定的光标处插入html片段进行渲染。
总结
关于 contenteditable 属性的使用还远远不止这些,这里只是向大家公布了 Salary主题 中表情插入的实现方法。
你也可以利用这个属性来创建一个富文本编辑器,亦或是 nvpress 的文章编辑。
希望本文能够帮助到你,如果有任何疑问或建议,欢迎在评论区留言交流!