仿微博发博的编辑框的实现
前言:
前一阵做公司的项目,有一个需求需要实现类似微博发博的功能,包括话题、表情、图片的等功能的实现。第一反应是通过div的contenteditable属性实现,但在实践中踩到了不少的坑,而关于这方面的教程资料少之又少,磕磕绊绊的想了两天最后才实现。所以决定把自己的思路心得分享给大家,希望给同样需要这种功能的程序猿提供帮助。如果有大神指出我的问题,或者提供更好的方法我真的是十分感谢啦。
先上代码:仿微博发博
1.模拟placeholder属性
作为一个编辑框,需要给用户一个可输入的提示,也就是placeholder,但div contenteditable 的方法中并没有placeholder的属性,但通过css可以简单的模拟。
html代码:
<div class="editArea" contenteditable='plaintext-only' data-text="请输入内容..." id="editArea"></div>
css代码:
.editArea:empty:before{
content: attr(data-text);
color: #999;
font-size: 0.14rem;
line-height: 200%;
position: relative;
top: -0.05rem;
}
按理说以上代码可以实现类似与textarea的placeholder的效果,但在页面中发现当div中输入内容之后删除,会莫名多出来一个<br>
标签,导致删除内容后虽然看起来是空的,但提示的文字不会出现。所以需要用js处理一下。
我的方法是监听backspace键,当按下的时候判断div中的内容并清空。
js代码
$(".editArea").on("keyup", function(e) {
if (e.keyCode == 8) { //监听backspace事件
if ($(this).html().length == 0 || $(this).html() == "<br>") {
$(this).empty()
}
}
}
2.图片上传功能
由于做的是移动端的页面,所以需要html通过<input type="file">
直接唤起手机相册。
需要注意的是<input type="file">
并没有办法变成各种花哨的样式,需要将其透明度设置为0,定位在图标或者按钮上。
<input type="file" accept="image/*" capture="camera">
<input type="file" accept="video/*" capture="camcorder">
<input type="file" accept="audio/*" capture="microphone">
accept 直接打开系统文件夹,image-相册、video-录像、audio-录音
capture可以捕获到系统默认的设备,camera--照相机;camcorder--摄像机;microphone--录音。
3.添加表情功能
表情功能的实现思路是通过按钮唤起自定义的表情面板,当点击表情的时候在编辑框中创建一个同样路径的<img>
元素。比较麻烦的是在表情面板中有一个删除按钮,需要js实现类似backspace的功能。
代码:
$(".backspace").on('click', function(e) {
e.stopPropagation();
e.preventDefault();
// 判断是否为表情
var str = $('#editArea').html()
var patt = /<img[^>]*class="text_emoji_icon">/gi
var arr = str.match(patt) ? str.match(patt) : []
var char = arr ? arr.pop() : ''
var lastIndex = str.lastIndexOf(char)
// 判断是否为话题
var patt1 = /<span class="topic">([^<]+)<\/span> /g;
var arr1 = str.match(patt1) ? str.match(patt1) : []
var char1 = arr1 ? arr1.pop() : ''
console.log(char1)
var lastIndex1 = str.lastIndexOf(char1)
if (char) { //为表情则删除
if (char.length + lastIndex == str.length) {
var newContent = str.substr(0, lastIndex)
$('#editArea').html(newContent)
} else {
var newContent = str.substr(0, str.length - 1)
$('#editArea').html(newContent)
}
} else if (char1) { //为话题则整个删除
if (char1.length + lastIndex1 == str.length) {
var newContent = str.substr(0, lastIndex1)
$('#editArea').html(newContent)
set_focus()
}
} else { //为文本则删除一位
var newContent = str.substr(0, str.length - 1)
$('#editArea').html(newContent)
}
})
em.....这里的代码写的比较杂乱。具体的思路依靠lastIndexOf来进行检测。
在点击backspace的按钮时,首先通过正则match()
的方法检测文本中是否含有表情标签<img class='text_emoji_icon'>
或者话题标签<span class="topic"></span>
。
match()
方法本身会将匹配到的字段按先后顺序存为一个数组,所以我们取出数组最后的一个元素通过lastIndexOf()
判断是否在文本的最后。
若元素在最后一位,则 lastIndexOf()
的值+匹配文本的长度 = 整个文本的长度。至于删除就是对整个文本进行字符串的截取,就不说啦。
4.话题的添加
话题的主要需求有两个:
1.需要弹出一个推荐话题列表,当用户点击话题的时候在编辑框内添加对应的话题;
此功能的实现与表情添加功能如出一辙,不多赘述。
2.当用户输入双#号后,#与#之间的内容变成话题样式,甚至可以加上链接。
对于怎么判断双#之间的内容变成话题困扰了我好久,最后经过多次尝试,利用 keyup
监听+正则匹配实现。
具体思路:用户按键时触发检查函数,如果检测到双#号,则将其与其内容放到标签中并替换文本。
代码:
$(".editArea").on("keyup", function(e) {
var str = $(".editArea").html()
if (str.charAt(str.length - 1) == '#') { //监听用户输入#号事件
if (countInstances(str, "#") % 2 == 0) { //判断#号数量来触发话题变色效果
if (ReplaceTopic($(".editArea").html())) {
var span = ReplaceTopic($(".editArea").html())
$(this).html("")
$(this).html(span)
set_focus() //光标定位到最后
}
}
}
}
因为没必要每次触发事件都替换,所以我加了判断。
结语:
第一次写感觉写的有点混乱,文章中如果又出现错误或者需要改进的地方请大家不吝赐教。因为查了很多资料都没有查到比较官方的demo,所以自己绞尽脑汁的想出了这个办法。
文章的问题点是结合自己在做项目中的思路顺序下来,每一个部分都算是一个小知识点,这样为同样用到contenteditable的小伙伴们提供帮助。文章的末尾会放几个比较有用的函数供大家参考。
可编辑标签中光标始终定位在最后:
function set_focus() {
el = document.getElementById('editArea');
el.focus();
if ($.support.msie) {
var range = document.selection.createRange();
this.last = range;
range.moveToElementText(el);
range.select();
document.selection.empty(); //取消选中
} else {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
}
通过正则将文本内容修改
function ReplaceTopic(str) {
var r, re; // 声明变量。
var ss = str;
var i = 0
if (/\#([^\#|.]+)\#/g.test(ss)) {
console.log(/<span class="topic">([^<]+)<\/span> /g.test(ss), "asss")
if (/<span class="topic">([^<]+)<\/span> /g.test(ss)) {
ss = ss.replace(/<span class="topic">([^<]+)<\/span> /g, function(
word) {
console.log(word, "----")
return word.match(/\#([^\#|.]+)\#/g)
})
}
r = ss.replace(/\#([^\#|.]+)\#/g, function(word) {
console.log(word)
return '<span class="topic">' + word + '</span> ';
});
return (r); //返回替换后的字符串
} else {
return false
}
}