富文本插件因为要满足很多功能,所以比较冗余,大部分功能是没有必要的,样式也难以改成达到设计原型的样式。基于input/textarea标签开发简易版表情插件效果如图所示
示例效果
原理
需要解决的问题:
- 如何获取输入标签光标的位置
- 如何输入内容在光标位置的后面
- 如果是textarea输入内容过多会出现默认滚动条,如果设置overflow-y: hidden会导致无法滚动
方法:
- 通过节点的selectionStart属性获取光标的位置
- 通过节点的setRangeText方法输入内容在光标位置的后面
- 预先获取浏览器滚动条样式的宽度,利用外层盒子的overflow:hidden隐藏掉textarea的滚动条
示例代码
<!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>简单表情组件开发测试</title>
</head>
<style>
.test{
width: 400px;
height: 500px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
background-color: #d6d3d5;
box-shadow: 0 0 10px 1px #d6d3d5;
}
.chat{
width: 100%;
height: 250px;
}
.text-area-function{
width: 100%;
height: 50px;
background-color: #fff;
position: relative;
}
.emoji-component{
position: absolute;
width: 320px;
height: 160px;
line-height: 0;
top: -160px;
left: 0;
background-color: #fff;
display: none;
}
.emoji-item-outter{
display: inline-block;
width: 40px;
height: 40px;
}
.emoji-item{
width: 36;
height: 36px;
line-height: 36px;
text-align: center;
border: 1px solid #ffffff;
font-size: 20px;
cursor: pointer;
}
.emoji-item:hover{
background-color: #f2f2f2;
border: 1px solid #f2f2f2;
border-radius: 4px;
}
.emoji{
width: 40px;
height: 40px;
display: inline-block;
margin-top: 5px;
margin-left: 10px;
line-height: 40px;
text-align: center;
border: 1px solid #dfdddd;
border-radius: 4px;
font-size: 25px;
cursor: pointer;
}
.emoji:hover{
background-color: #f2f2f2;
}
.text-area-input{
width: 100%;
height: 200px;
overflow: hidden;
}
#send-text-content{
overflow-y: scroll;
width: 400px;
height: 200px;
resize: none;
border: none;
outline: none;
font-size: 16px;
color: var(--text-color);
/* overflow-y: hidden; */
}
textarea::-webkit-input-placeholder {
color: #dfdddd !important;
}
textarea:-moz-placeholder {
color: #dfdddd !important;
}
textarea::-moz-placeholder {
color: #dfdddd !important;
}
textarea:-ms-input-placeholder {
color: #dfdddd !important;
}
</style>
<body>
<div class="test">
<!-- 聊天框 -->
<div class="chat"></div>
<!-- 输入框功能,当前示列仅实现表情功能
其中cancelEmojiComponent()用于隐藏表情组件
-->
<div class="text-area-function" onclick="cancelEmojiComponent()" >
<!-- 显示表情组件按钮 -->
<div class="emoji">
😃
</div>
<!-- 表情组件 由于数目很多,原生用js添加,vue可以通过v-for即可 -->
<div class="emoji-component"></div>
</div>
<!-- 输入框 -->
<div class="text-area-input">
<textarea
id="send-text-content"
placeholder="请输入内容..."
></textarea>
</div>
</div>
</body>
</html>
<script>
// 由于显示表情组件按钮的外层还有一个click事件,所以要取消冒泡事件
// html通过e.stopPropagation();vue通过修饰符stop即可
document.getElementsByClassName('emoji')[0].onclick = (e)=>{
e.preventDefault();
e.stopPropagation()
// 显隐控制
let emojiComponent = document.getElementsByClassName('emoji-component')[0];
emojiComponent.style.display = 'block';
}
// 获取浏览器滚动条宽度
function getBarWidth() {
var noScroll, scroll, oDiv = document.createElement("DIV");
oDiv.style.cssText = "position:absolute; top:-1000px; width:100px; height:100px; overflow:hidden;";
noScroll = document.body.appendChild(oDiv).clientWidth;
oDiv.style.overflowY = "scroll";
scroll = oDiv.clientWidth;
document.body.removeChild(oDiv);
return noScroll - scroll;
}
// 取消表情组件显示
function cancelEmojiComponent(e) {
let emojiComponent = document.getElementsByClassName('emoji-component')[0];
emojiComponent.style.display = 'none';
}
// 由于textarea要内容够多才有显示条
// 在css中通过overflow-y: scroll;迫使他出现滚动条
// 通过计算textarea的父节点的宽度和浏览器滚动条宽度得到textarea的宽度
function initTextArea(){
let ele = document.getElementById('send-text-content');
let parent = ele.parentNode;
let clientWidth = parent.getBoundingClientRect().width;
let barWidth = getBarWidth();
let width = clientWidth + barWidth +'px';
ele.style.width = width;
}
// 初始化组件,通过js往组件标签加内容
// 采用了文档流碎片一次性将需要操作DOM的操作给完成避免过多的重绘回流
function initEmojiComponent() {
let g = document.createDocumentFragment();
emojiList = [
'😃 ','😄','😉','😆','😅','🤣','😂','🙂',
'🙃','😘','😚','😜','🤑','🤪','🤫','😒',
'😬','😈','😪','😷','😟','😨','😰','😡',
'💀','🙏','💪','👐','👍','✊','✌️','👌'
]
for (let i = 0; i < emojiList.length; i++) {
let outerdiv = document.createElement('div');
let innerdiv = document.createElement('div');
outerdiv.className = 'emoji-item-outter';
innerdiv.className = 'emoji-item';
innerdiv.innerHTML = emojiList[i];
innerdiv.onclick = (e) =>{
cancelEmojiComponent();
console.log(e)
let ele = document.getElementById('send-text-content');
let cursor = ele.selectionStart;
// // 获取文本输入框元素节点
ele.setRangeText(e.srcElement.innerHTML);
// 表情占据2个字符否则乱码
ele.selectionStart = cursor + 2;
console.log(ele.selectionStart);
ele.focus();
ele = null;
}
outerdiv.append(innerdiv);
g.append(outerdiv);
}
document.getElementsByClassName('emoji-component')[0].append(g);
}
initTextArea();
initEmojiComponent()
</script>