原生js写文本框插入表情
-
背景
聊天软件要支持发送表情包 -
思想
传统的textarea无法支持插入图片,所以我们应该使用可编辑的div进行操作,监听div的按键事件和点击事件,记录上一次的光标位置,插入表情是获取上次的光标位置进行插入,插入后再次记录光标位置
<div contenteditable = "true" ></div>
-
实现原理–复制demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
}
.app {
width: 400px;
height: 700px;
border: 1px solid #000;
display: flex;
flex-direction: column;
}
.app .content {
flex: 1;
}
.app .input {
height: 150px;
border: 1px solid #000;
margin: 4px;
padding: 10px;
outline: none;
}
.input img {
width: 20px;
height: 20px;
}
.left {
width: 400px;
height: 400px;
position: fixed;
left: 20px;
border: 1px solid #000;
right: 100px;
}
.left img {
width: 40px;
height: 40px;
cursor: pointer;
}
</style>
<body>
<div class="app">
<div class="content"></div>
<div>
<button>表情</button>
<button>字体</button>
</div>
<div class="input" id="input" contenteditable="true">
</div>
</div>
<div class="left" id="left">
<img src="https://i-blog.csdnimg.cn/blog_migrate/30375aa6cd6be0d855464f450560b6a6.gif#pic_center" alt="">
<img src="https://i-blog.csdnimg.cn/blog_migrate/1031101ced474396827f234eab40d57f.gif#pic_center)" alt="">
<img src="https://i-blog.csdnimg.cn/blog_migrate/889e11dda257d52354afb7a92ada4b03.gif#pic_center" alt="">
<img src="https://i-blog.csdnimg.cn/blog_migrate/4f9f71c8c11813a68abee7f0f87d4bef.gif#pic_center" alt="">
</div>
</body>
</html>
<script>
var lastEditRange;
const div = document.getElementById('input')
const left = document.getElementById('left')
// 编辑框点击事件
div.onclick = (e) => {
const childNodes = Array.from(div.childNodes)
// // // 获取选定对象
const selection = getSelection()
const {
focusNode
} = selection
// 点击图片
if (!e.target.innerHTML) {
selection.removeAllRanges()
const index = childNodes.findIndex((v) => v === e.target)
selection.collapse(div, index === -1 ? childNodes.length : index)
}
lastEditRange = selection.getRangeAt(0)
}
div.onkeyup = () => {
// 获取选定对象
var selection = getSelection()
// 设置最后光标对象
lastEditRange = selection.getRangeAt(0)
}
left.onclick = (e) => {
// var edit = document.getElementById('edit')
// // 获取输入框对象
// var emojiInput = document.getElementById('emojiInput')
// div.focus()
const childNodes = Array.from(div.childNodes)
var selection = getSelection()
selection.removeAllRanges()
selection.addRange(lastEditRange)
// div.innerHTML += `<img src="${e.target.src}" />`
var range = selection.getRangeAt(0)
// 获取光标位置
var rangeStartOffset = range.startOffset;
var img = document.createElement('img')
img.src = e.target.src
// 判断选定对象范围是编辑框还是文本节点
if (selection.anchorNode.nodeName != '#text') {
if (div.childNodes.length > 0) {
// 如果文本框的子元素大于0,则表示有其他元素,则按照位置插入表情节点
for (var i = 0; i < div.childNodes.length; i++) {
if (i == selection.anchorOffset) {
div.insertBefore(img, div.childNodes[i])
}
}
} else {
// 否则直接插入一个表情元素
div.appendChild(img)
}
// 创建新的光标对象
var range = document.createRange()
// 光标对象的范围界定为新建的表情节点
range.selectNodeContents(img)
// 光标位置定位在表情节点的最大长度
range.setStart(div, Array.from(div.childNodes).findIndex(v => v === img) + 1)
// 使光标开始和光标结束重叠
range.collapse(true)
// 清除选定对象的所有光标对象
selection.removeAllRanges()
// 插入新的光标对象
selection.addRange(range)
} else {
// 如果是文本节点则先获取光标对象
// 获取光标对象的范围界定对象,一般就是textNode对象
var textNode = range.startContainer;
// 获取光标位置
const { textContent } = textNode
// 分割开始值
const startStr = textContent.substr(0, rangeStartOffset)
// 分割结束值
const endStr = textContent.substr(rangeStartOffset)
// 当前节点的索引
const index = childNodes.findIndex(v => v === textNode)
// 移除当前文本节点
const startStrNode = document.createTextNode(startStr)
const endStrNode = document.createTextNode(endStr)
div.removeChild(textNode)
if (index === childNodes.length - 1) {
// 最后一个节点处理
div.appendChild(startStrNode)
div.appendChild(img)
div.appendChild(endStrNode)
selection.collapse(div, index + 2)
} else {
// 不是最后一个
div.insertBefore(startStrNode, childNodes[index + 1])
div.insertBefore(img, childNodes[index + 1])
div.insertBefore(endStrNode, childNodes[index + 1])
selection.collapse(div, Array.from(div.childNodes).findIndex(v => v === img) + 1)
}
// 光标开始和光标结束重叠
range.collapse(true)
// // 清除选定对象的所有光标对象
// selection.removeAllRanges()
// // 插入新的光标对象
selection.addRange(range)
}
// 无论如何都要记录最后光标对象
lastEditRange = selection.getRangeAt(0)
}
</script>
快去试一试看看效果吧