为了给ikuku.cn社区加上同样的功能,花了一点时间实现了QQ空间的评论框,有想学习编辑器的实现原理的朋友,可以认真阅读代码。 需要包含jQuery。

这个编辑框可以在任意位置输入@时,弹出@人名的补全内容,并且只有@正确的人名,才会给服务器端生成数据。删除时,也是一次删掉人名,不会发生@joson 退格一下变成 @joso 这种。 可以给用户不会出错的愉快体验。

var commentEditor = function(id){
this.editorContainer = $('#'+id);
this.editorPlaceholder = $('<div class="comment_editor_placeholder"></div>');
this.editor = $('<div class="comment_editor_main"></div>');
this.currentEl = null;
this.currentOffset = null;
this.offsetLeft = 0;
this.editorStatus = 0;
this.op = {
placeholder : '在这里输入评论',
padding : 5
};
this.editorPlaceholder.css({
width : this.editorContainer.width() - this.op.padding*2,
height : this.editorContainer.height() - this.op.padding*2,
position:'relative'
});
this.editor.css({
overflow : 'auto',
width : this.editorContainer.width() - this.op.padding*2,
height : this.editorContainer.height() - this.op.padding*2,
position:'relative',
marginTop : -this.editorContainer.height()
});
this.editor.attr('contenteditable',true);
this.editorContainer.append(this.editorPlaceholder);
this.editorContainer.append(this.editor);
this.editorPlaceholder.text(this.op.placeholder);
this.editorContainer.addClass('comment_editor');
if( this.getData() != '' ){
this.editorPlaceholder.text('');
}
this.list = new commentEditor.downList(this);
this._init();
this.listenAt();
};
commentEditor.prototype = {
_init : function(){
var self = this;
this.listenChange();
$(document).bind('click',function(){
self.list.hide();
if( self.editorStatus == 1 ){
self.editorStatus = 0;
self.editor.trigger('editor.close');
}
});
this.editor.bind('click',function(e){
e.stopPropagation();
});
this.editor.bind('editor.change',function(){
self.listenCursor();
});
this.editor.bind('mouseup',function(){
self.listenCursor();
});
this.editor.bind('focus',function(){
self.editorPlaceholder.text('');
if( self.editorStatus == 0 ){
self.editorStatus = 1;
self.editor.trigger('editor.open');
}
});
this.editor.bind('blur',function(){
if( self.getData() == '' ){
self.editorPlaceholder.text(self.op.placeholder);
}
});
},
listenAt : function(){
var self = this,timeout=null,currentname=null;
this.editor.bind('editor.at',function(e,data){
self.offsetLeft = String(data).length + 1 ;
clearTimeout(timeout);
if( currentname == data ){
self.list.show();
return ;
}
currentname = data;
timeout = setTimeout(function(){
  $.post('/test_comment_exe.php',{action:'at',name:data},function(data){
  if( data != '' ){
self.list.show();
self.list.setData(data,function(data){
var op = $('<div><a href="javascript:">\
<div style="float:left;width:30px;height:30px;"><img src="/profile_image/'+data.ID+'/30" width="30" height="30" /></div>\
<div style="float:left; margin-left:10px;">'+data.user_nicename+'</div></a>\
</div>');
op.bind('click',function(){
self.insertHtml('<button contenteditable="false" class="comment_editor_element">@'+data.user_nicename+'</button>',true);
self.list.hide();
});
return op;
});
  }else{
self.list.hide();
  }
  });
},100);
});
this.editor.bind('editor.at_close',function(){
clearTimeout(timeout);
self.list.hide();
});
},
listenChange : function(){
var self = this,oldText=this.editor.html();
this.editor.bind('keyup',function(e){
var newText = self.editor.html();
if( newText != oldText ){
oldText = newText;
self.editor.trigger('editor.change');
}
});
},
getSelectionObject : function(){
if (window.getSelection){  // mozilla FF
return window.getSelection();
}else if (document.getSelection){
return document.getSelection();
}else if (document.selection){  //IE
return document.selection.createRange().text;
}
},
getRangeObject : function(selectionObject) {
return selectionObject.getRangeAt(0);
},
listenCursor : function(){
var userSelection = this.getSelectionObject();
if( !userSelection.anchorNode.data || userSelection.type == 'None' || userSelection.type == 'Range' ){
this.editor.trigger('editor.at_close');
return ;
}
this.currentOffset = userSelection.anchorOffset;
this.currentEl = userSelection.anchorNode;
var prevText = userSelection.anchorNode.data.substring( 0, userSelection.anchorOffset );
var lastDot = prevText.lastIndexOf('@');
if( lastDot != -1 ){
prevText = prevText.substring(lastDot);
if( !prevText.match(/\s/) ){
this.editor.trigger('editor.at',[ prevText.substring(1)]);
return ;
}else if( prevText.match(/^[^\s]+\s$/) ){
this.editor.trigger('editor.at_apply',[prevText.substring(1,prevText.length-1)]);
}
}
this.editor.trigger('editor.at_close');
},
insertHtml : function(html,lastspace){
var offsetLeft = this.offsetLeft;
this.offsetLeft = 0;
if( this.currentEl != null && this.currentOffset != null ){
this.editor.focus();
var userSelection = this.getSelectionObject();
var range = this.getRangeObject(userSelection);
var el = $(html);
var el_dom = el.get(0);
if( !el_dom ){
el_dom = document.createTextNode(html);
}
range.setStart(this.currentEl,this.currentOffset - offsetLeft );
range.setEnd(this.currentEl,this.currentOffset );
range.deleteContents();
range.insertNode( el_dom );
range.setStartAfter( el_dom, 0 );
range.setEndAfter( el_dom , 0 );
if(lastspace){
var space = document.createTextNode('\u00A0');
range.insertNode( space );
range.setStartAfter( space, 0 );
range.setEndAfter( space , 0 );
}
if( navigator.userAgent.indexOf('Chrome') != -1 ){
if( el_dom.nextSibling == el_dom.parentNode.lastChild && el_dom.nextSibling.data==''){
range.insertNode($('<br/>').get(0));
}
}
if( userSelection.setPosition )
userSelection.setPosition(range.startContainer,range.startOffset);
else{
var nrange = this.getRangeObject(userSelection);
nrange.setStart(range.startContainer,range.startOffset);
nrange.setEnd(range.startContainer,range.startOffset);
userSelection.removeAllRanges();
userSelection.addRange(nrange);
}
this.currentOffset = range.startOffset;
this.currentEl = range.startContainer;
}else{
//console.log( $(html) );
this.editor.html( this.editor.html() + html );
this.editor.focus();
var userSelection = this.getSelectionObject();
var range = this.getRangeObject(userSelection);
range.setStartAfter( this.editor.get(0).lastChild, 0 );
range.setEndAfter( this.editor.get(0).lastChild, 0 );
if( userSelection.setPosition ){
userSelection.setPosition(range.startContainer,range.startOffset);
}else{
var nrange = this.getRangeObject(userSelection);
nrange.setStart(range.startContainer,range.startOffset);
nrange.setEnd(range.startContainer,range.startOffset);
userSelection.removeAllRanges();
userSelection.addRange(nrange);
}
this.currentOffset = range.startOffset;
this.currentEl = range.startContainer;
}
},
getData : function(){
return this.editor.html();
},
bind : function(eventName,callback){
this.editor.bind(eventName,callback);
}
};
commentEditor.downList = function(container){
this.container = container;
this.list = $('<div class="comment_editor_list"></div>');
this.list.css({
position:'absolute',
width: this.container.editor.width()+this.container.op.padding*2 - 2,
top:this.container.editor.offset().top + this.container.editor.height()+this.container.op.padding*2,
left:this.container.editor.offset().left
});
this.status = 'show';
this.list.hide();
$(document.body).append(this.list);
this.list.bind('click',function(e){
e.stopPropagation();
});
};
commentEditor.downList.prototype = {
show : function(){
this.status = 'show';
this.list.show();
},
hide : function(){
this.list.hide();
},
setData : function(data,callback){
var self = this;
this.list.find('*').remove();
var data = $.parseJSON(data);
if(data){
$.each(data,function(i){
if(callback)
self.list.append(callback(data[i]));
});
}
}
};

仿QQ空间的评论编辑器实现相关推荐

  1. Android仿QQ空间二级评论列表

    之前项目中产品需求带二级的评论列表,首先想到是QQ空间评论列表. 先上效果图 下面我们来分析一下布局结构,首先一级列表是listview,然后二级列表也可以有多条,为了省事我只添加了一条,第一反应是l ...

  2. Java 仿QQ空间评论 后台代码实现 一张表

    因为项目业务需要,特此写出此仿QQ空间的评论功能,项目如下图所示,records下的是一级评论 里面的commentList是二级评论,前端根据此数据格式渲染即可 表结构如下,其中father_id与 ...

  3. java仿qq空间音乐播放_完美实现仿QQ空间评论回复特效

    评论回复是个很常见的东西,但是各大网站实现的方式却不尽相同.大体上有两种方式 1. 像优酷这种最常见,在输入框中@要回复的人,这种方式下,用www.cppcns.com户可以修改@. 新浪微博则是在这 ...

  4. php 朋友圈留言,php实例-PHP仿qq空间或朋友圈发布动态、评论动态、回复评论、删除动态或评论的功能(上)...

    我们大部分人都发过动态,想必都知道发动态.回复评论.删除动态的整个过程,那么这个功能是如何实现的呢?下面小编给大家带来了实例代码,对PHP仿qq空间或朋友圈发布动态.评论动态.回复评论.删除动态或评论 ...

  5. php mysql仿微信朋友圈评论表设计_PHP仿qq空间或朋友圈发布动态、评论动态、回复评论、删除动态或评论的功能(上)...

    我们大部分人都发过动态,想必都知道发动态.回复评论.删除动态的整个过程,那么作为初学者,要模仿这些功能有点复杂的,最起码表的关系得弄清楚~~ 先把思路理一下: (1)用户登录,用session读取当前 ...

  6. 仿QQ空间、微信朋友圈点击评论弹出输入框

    从上周开始一直在寻找仿QQ空间.朋友圈类似的弹出输入框,并且需要解决键盘不遮挡内容的解决方案 在网上寻寻觅觅始终不得解,后来无意中看见一篇百度百科的文章总算给了我解决的思路 这个就是给了我启发的文章 ...

  7. java 仿qq空间_仿QQ空间和微信朋友圈,高解耦高复用高灵活

    先看看效果: 用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦.高复用.高灵活. 动态列表界面MomentListFragment支持 下拉刷新与上拉加载 ...

  8. Android仿QQ空间底部菜单

    之前曾经在网上看到Android仿QQ空间底部菜单的Demo,发现这个Demo有很多Bug,布局用了很多神秘数字.于是研究了一下QQ空间底部菜单的实现,自己写了一个,供大家参考.效果如下图所示:  点 ...

  9. 【Android UI设计与开发】第09期:底部菜单栏(四)Fragment+PopupWindow仿QQ空间最新版底部菜单栏

    转载请注明出处:http://blog.csdn.net/yangyu20121224/article/details/9023451          在今天的这篇文章当中,我依然会以实战加理论结合 ...

最新文章

  1. idea使用lombok不生效的解决办法
  2. IE6环境下遭遇winow.location.href=''的跳转bug
  3. c++调用python的代码、函数、类
  4. linxu安装OSX
  5. 石油化工静设备计算机辅助设计桌面系统,PV Desktop 石油化工静设备计算机辅助设计桌面系统简介...
  6. Android之在笔记本电脑adb devices识别不了oppo A9手机(设备管理器ADB Interface里面有个黄色感叹号)
  7. 苹果MacBook Air 2018款少量用户会出现主板问题可免费更换
  8. JavaScript之数组去重
  9. LeetCode:62. 不同路径(python、c++)
  10. 现在电脑的主流配置_主流级玩家 应该如何配置高性价比电脑
  11. 阶段1 语言基础+高级_1-3-Java语言高级_08-JDK8新特性_第2节 Stream流式思想概述_1_使用传统的方式,遍历集合,对集合进行过滤...
  12. VS2010 asp.net development server 无法展示svg图片
  13. 51单片机定时器实现PWM波
  14. 智慧路灯解决方案-最新全套文件
  15. Java基础之==与equal()的区别
  16. 雷达原理之 多普勒效应原理及应用(一)
  17. 《一直在路上》系列——大美贵州,贵州避暑之旅
  18. 【MarkDown】基础语法
  19. Vue怎么操作父元素、兄弟元素、子元素
  20. Java 实验8 《抽象类与接口》

热门文章

  1. VUE如何导入开源项目及报错处理gyp ERR! stack Error: Command failed: D:\Python\python.EXE -c import sys; print “%s.
  2. QQ2006Beta1新功能3D秀更多截图曝光(转)
  3. 周志华西瓜南瓜书学习(一)
  4. 对《移动互联网白皮书(2013年)》的几个解读
  5. springboot教学系统毕业设计-附源码191733
  6. Osm地图osmdroid下载离线地图,第三方瓦片地图
  7. 脸部动作编码系统(FACS)应用
  8. python3 Win32 exception occurred releasing IUnknown at 0x26dc1458 ----错误已经解决
  9. 操作系统课设--多用户多级目录的文件系统
  10. 几个有用的数学概念-数论