详解微博发言框的@功能

May 17, 2016 am 09:09 AM

经常使用微博的人会发现,当我们在输入框输入@然后敲一个人的名字,会弹出一个tip提示层,如图所示:

weibo.jpg



出于对这个功能的好奇,并抱着学习的态度,翻阅了一些资料后对这个Javascript进行了探讨和研究。
对这个功能进行分析如下:

1、确定光标的位置

2、textarea文本框里对字符串@的判断

3、tip的弹出事件

4、键盘的操作事件

5、ajax调用

6、文字的插入

......

当然还有其他的功能。

看着是不是感觉很复杂?没关系,我们一步一步的分析。

首先我们要确定textarea的光标位置。在W3C中,获取光标位置比较简单,可以用selectionStart和selectionEnd,IE浏览器不支持这2个属性 ,但是IE又一个document.selection对象,可以模拟实现相同的功能。代码如下:


  1. //先定义一个基本类,来设置一些全局变量
  2. function demonAt(opts) {
  3.     this.elem=opts.elem; //文本框
  4.     this.at= {};    //临时保存文本框内容截取属性
  5.     this.opt= {};
  6.     this.searched=""; //用于判断用户输入字符是否和前面一样,如果一样跳过ajax
  7.     this.url=opts.url;
  8.     this.index=1;
  9. }
  10. //微博的@功能
  11. demonAt.prototype= {
  12.     getCursor: function(elem) {
  13.         var _this=this;
  14.         var rangeData = {
  15.             start: 0,
  16.             end: 0,
  17.             text: ""
  18.         };
  19.         if(typeof(this.elem.selectionStart)=="number") {//W3C
  20. rangeData.start=this.elem.selectionStart;//光标起始位置
  21.             rangeData.end=this.elem.selectionEnd;//光标末尾位置
  22. rangeData.text=this.elem.value.substring(0,this.elem.selectionStart);//获取文本框value
  23.         } else if (document.selection) {//IE
  24.             var sRange=document.selection.createRange();
  25.             var oRange=document.body.createTextRange();
  26.             oRange.moveToElementText(this.elem);
  27.             rangeData.text=sRange.text;
  28.             rangeData.bookmark = sRange.getBookmark();
  29. for(i=0;oRange.compareEndPoints("StartToStart",sRange)
  30.                 if (this.elem.value.charAt(i) == '\r') {
  31.                     i ++;//IE的特殊处理,遇到enter键需要加1
  32.                 }
  33.             }
  34.             rangeData.start=i;
  35.             rangeData.end = rangeData.text.length + rangeData.start;
  36.             rangeData.text=this.elem.value.substring(0,i);
  37.         }
  38.         //alert(rangeData.text)
  39.         return rangeData;
  40.     },
  41.     setCursor: function(elem,start,end) {//设置光标
  42.         if(this.elem.setSelectionRange) {//W3C
  43.             this.elem.setSelectionRange(start,end);
  44.         } else if(this.elem.createRange) {//IE
  45.             var range=this.elem.createRange();
  46.             if(this.elem.value.length==rangeData.start) {
  47.                 range.collapse(false);
  48.                 range.select();
  49.             } else {
  50.                 range.moveToBookmark(rangeData.bookmark);
  51.                 range.select();
  52.             }
  53.         }
  54.     },
  55.     add: function(elem,txtData,nStart, nLen) {//插入文本参数操作的元素,数据,起始坐标位置,用户输入字符长度
  56.         //this.setCursor(this.elem,this.rangeData);
  57.         this.elem.focus();
  58.         var _range;
  59.         if(this.elem.setSelectionRange) {//W3C
  60.             _tValue=this.elem.value;//获取文本框内容
  61.             var _start = nStart - nLen,//设置光标起点光标的位置-离@的文本长度
  62.             _end = _start + txtData.length,//设置光标末尾,start+数据文字长度
  63.             _value=_tValue.substring(0,_start)+txtData+" "+_tValue.substring(nStart, this.elem.value.length);
  64.             this.elem.value=_value;
  65.             this.setCursor(this.elem,_end+1,_end+1);
  66.         } else if(this.elem.createTextRange) {
  67.             _range=document.selection.createRange();
  68.             _range.moveStart("character", -nLen);//移动光标
  69.             _range.text = txtData+" ";
  70.         }
  71.     }
  72. }
复制代码


自定义一个rangeData对象,保存光标的位置和textarea框内从光标位置到开始处的字符串;返回出来。这个对象在下面其他函数中会用到。根据光标位置的确定,可以书写文字插入函数add();有了上面的函数,我们可以对textarea框内@的字符判断,然后实现tip层定位和弹出,如果判断这个,我们可以用正则:

  1. var _reg=/@[^@\s]{1,20}$/g;
复制代码


那么定位呢,若在textarea内判断是不现实的,因为我们无法获取正确的left和top值,所以这里需要模拟一个div层,将div插入到body 中,定位到与textarea相同的位置,然后获取到textarea内的文字,进行字符串的拆分,加上标签元素,这样可以获取到正确的位置。说的有点绕了,看下面代码能更直观的表达。

  1. var _string=""+"@前面的文字"+""+"@"+""+"@后面的文字"+"";
复制代码


看到这句,很多人应该理解做法,将这段append到上诉定位的div中,这样,我们可以通过标签获取到offset值了。于是我们写下面的代码:


  1. demonAt.prototype= {
  2.     init: function() {//首先我们要初始化
  3.         var _body=$("body");
  4.         var _div=$("
    "),
  5.         _tip=$("
    ");
  6.         _body.append(_div);
  7.         _body.append(_tip);
  8.         var _left=$(this.elem).offset().left+"px",
  9.         _top=$(this.elem).offset().top+"px",
  10.         _width=$(this.elem).outerWidth()+"px",
  11.         _height=$(this.elem).outerHeight()+"px",
  12.         _lineHeight=$(this.elem).css("line-height"),
  13.         _style="position:absolute;overflow:hidden;z-index:-9999;line-height:"+_lineHeight+";width:"+_width+";height:"+_height+";left:"+_left+";top:"+_top;
  14.         _div.attr("style",_style);
  15.         this.inset();
  16.     },
  17.     getAt: function() {
  18.         var _rangeData=this.getCursor();
  19.         var k=_value=_rangeData.text.replace(/\r/g,"");//去掉换行符
  20.         var _reg=/@[^@\s]{1,20}$/g;//正则,获取value值后末尾含有@的并且在20字符内
  21.         var _string="";
  22.         if(_value.indexOf("@")>=
  23.         0&&_value.match(_reg)) {
  24.             var _postion=_rangeData.start;
  25.             var _oValue=_value.match(_reg)[0];//找到value中最后匹配的数据
  26.             var vReg=new RegExp("^"+_oValue+".*$","m");//跟数据匹配的正则   暂时保留
  27.             _value=_value.slice(0, _postion); //重写_value 字符串截取  从0截取到光标位置
  28.             if(/^@[a-zA-Z0-9\u4e00-\u9fa5_]+$/.test(_oValue)&& !/\s/.test(_oValue)) {
  29.                 this.at['m'] = _oValue = _oValue.slice(1);//用户输入的字符  如@颓废小魔,即"颓废小魔"
  30.                 this.at['l'] = _value.slice(0, -_oValue.length - 1); //@前面的文字
  31.                 this.at['r'] = k.slice(_postion - _oValue.length, k.length);//@后面的文字
  32.                 this.at['pos']=_postion;//光标位置
  33.                 this.at['len']=_oValue.length;//光标位置至@的长度,如 @颓废小魔,即"颓废小魔"的长度
  34.                 this.showTip(this.url)
  35.             } else {//alert(1)
  36.                 this.hiddenTip()
  37.             }
  38.         } else {
  39.             this.hiddenTip()
  40.         }
  41.     },
  42.     buidTip: function() {//创建tip,设置tip的位置
  43.         var _this=this;
  44.         $("#tWarp").empty();
  45.         var _string=""+this.format(this.at['l'])+""+"@"+""+this.format(this.at['r'])+"";
  46.         $("#tWarp").html(_string);
  47.         var _left=$("#tWarp cite").offset().left+"px",
  48.         _top=$("#tWarp cite").offset().top+parseInt($("#tWarp").css("line-height"))+"px";
  49.         if(parseInt(_top)>parseInt($("#tWarp").offset().top+$("#tWarp").height())) {
  50.             _top=$("#tWarp").offset().top+$("#tWarp").height()+"px";
  51.         }
  52.         $("#tipAt").css({
  53.             "left":_left,
  54.             "top":_top,
  55.             "display":"block"
  56.         });
  57.         $("#tipAt li").eq(1).addClass("on").siblings().removeClass("on");
  58.         _this.hover();
  59.         //取消keyup绑定,绑定keydown,键盘操作选择,避免与文本框的事件冲突
  60.         $(_this.elem).unbind('keyup').bind('keydown', function(e) {
  61.             return _this.keyMove(e);
  62.         });
  63.     },
  64.     hiddenTip: function() {
  65.         var _this=this;
  66.         $("#tipAt").css("display","none");
  67.         $("#tipAt li").unbind("click,mouseover");
  68.     }
  69. }
复制代码


然后我们添加键盘的操作,这里注意的是,我们在textarea输入文字的时候已经绑定keyup事件,为了避免事件多次绑定,tip的选择我们用keydown事件处理。

  1. demonAt.prototype= {
  2.     keyMove: function(e) {//键盘操作
  3.         var _this=this;
  4.         var _key=e.keyCode;
  5.         var _len=$("#tipAt li").length;
  6.         switch(_key) {
  7.             case 40:
  8.                 //下
  9.                 _this.index++;
  10.                 if(_this.index>_len-1) {
  11.                 _this.index=1;
  12.                 }
  13.                 _this.keyMoveTo(_this.index);
  14.                 //return false一定要加上,不然JS会继续进行调用keyHandler,从而绑定了keyup事件影响到键盘的keydown事件
  15.                 return false;
  16.                 break;
  17.             case 38:
  18.                 //上
  19.                 _this.index--;
  20.                 if(_this.index
  21.                 _this.index=_len-1;
  22.                 }
  23.                 _this.keyMoveTo(_this.index);
  24.                 return false;
  25.                 break;
  26.             case 13:
  27.                 //enter键
  28.                 var txtData=$(".on").text();
  29.                 _this.add(_this.elem,txtData,_this.at['pos'],_this.at['len'])
  30.                 _this.keyHandler()
  31.                 return false;
  32.                 break;
  33.             default:
  34.         };
  35.         _this.keyHandler();
  36.     },
  37.     keyHandler: function() {
  38.         var _this=this;
  39.         _this.index=1;
  40.         //enter键盘操作后重新绑定keyup
  41.         $(_this.elem).unbind("keydown").bind("keyup", function() {
  42.             _this.getAt();
  43.         })
  44.     },
  45.     keyMoveTo: function(index) {
  46.         $("#tipAt li").removeClass("on").eq(index).addClass("on");
  47.     }
  48. }
复制代码


然后添加tip的点击事件和hover事件。

  1. demonAt.prototype= {
  2.     inset: function() {//给li绑定事件,
  3.         var _this=this;
  4.         $("#tipAt").delegate("li","click", function() {//事件委托
  5.             if($(this).index()==0) {
  6.                 _this.elem.focus();
  7.                 return false;
  8.             } else {
  9.                 var txtData=$(this).text();
  10.                 _this.add(_this.elem,txtData,_this.at['pos'],_this.at['len'])
  11.                 _this.hiddenTip()
  12.             }
  13.         })
  14.     },
  15.     hover: function() {
  16.         //hover事件
  17.         var _this=this;
  18.         $("#tipAt li:not(:first)").hover( function() {
  19.             _this.index=$(this).index();
  20.             $(this).addClass("hover").siblings().removeClass("on hover")
  21.         }, function() {
  22.             $(this).removeClass("hover");
  23.         })
  24.     }
  25. }
复制代码

到这里,微博的@功能就已经全部讲解清楚了,希望各位可以清楚明白的了解。



转自我爱猫猫技术博客

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

HTML5 Webサイトにオーディオを追加する方法は? HTML5 Webサイトにオーディオを追加する方法は? Mar 10, 2025 pm 03:01 PM

この記事では、< audio>を使用してHTML5にオーディオを埋め込む方法について説明します。フォーマット選択のベストプラクティス(MP3、OGG Vorbis)、ファイルの最適化、および再生のためのJavaScriptコントロールを含む要素。 複数のオーディオfを使用して強調しています

HTML5ページの可視性APIを使用して、ページが表示されたときに検出するにはどうすればよいですか? HTML5ページの可視性APIを使用して、ページが表示されたときに検出するにはどうすればよいですか? Mar 13, 2025 pm 07:51 PM

この記事では、HTML5ページの可視性APIを使用して、ページの可視性を検出し、ユーザーエクスペリエンスを向上させ、リソースの使用量を最適化します。重要な側面には、メディアの一時停止、CPU負荷の削減、視認性の変化に基づいて分析の管理が含まれます。

ViewPortメタタグを使用して、モバイルデバイスのページスケーリングを制御するにはどうすればよいですか? ViewPortメタタグを使用して、モバイルデバイスのページスケーリングを制御するにはどうすればよいですか? Mar 13, 2025 pm 08:00 PM

この記事では、ビューポートメタタグを使用してモバイルデバイスのページスケーリングを制御し、最適な応答性とパフォーマンスのために幅や初期スケールなどの設定に焦点を当てています。

ユーザー入力にHTML5フォームを使用する方法は? ユーザー入力にHTML5フォームを使用する方法は? Mar 10, 2025 pm 02:59 PM

この記事では、HTML5フォームを作成および検証する方法について説明します。 < form>の詳細要素、入力タイプ(テキスト、電子メール、番号など)、および属性(必須、パターン、MIN、最大)。 HTML5の利点は、古い方法よりも形成されます

Geolocation APIでユーザーの場所のプライバシーと許可を処理するにはどうすればよいですか? Geolocation APIでユーザーの場所のプライバシーと許可を処理するにはどうすればよいですか? Mar 18, 2025 pm 02:16 PM

この記事では、Geolocation APIを使用してユーザーの場所のプライバシーと許可を管理し、アクセス許可を要求するためのベストプラクティスを強調し、データセキュリティを確保し、プライバシー法を遵守することについて説明します。

HTML5とJavaScriptでインタラクティブなゲームを作成する方法は? HTML5とJavaScriptでインタラクティブなゲームを作成する方法は? Mar 10, 2025 pm 06:34 PM

この記事では、JavaScriptを使用してインタラクティブなHTML5ゲームの作成を詳述しています。 ゲームデザイン、HTML構造、CSSスタイリング、JavaScriptロジック(イベント処理とアニメーションを含む)、およびオーディオ統合をカバーしています。 必須JavaScriptライブラリ(Phaser、PI

インタラクティブなユーザーインターフェイスにHTML5ドラッグアンドドロップAPIを使用するにはどうすればよいですか? インタラクティブなユーザーインターフェイスにHTML5ドラッグアンドドロップAPIを使用するにはどうすればよいですか? Mar 18, 2025 pm 02:17 PM

この記事では、HTML5ドラッグアンドドロップAPIを使用してインタラクティブなユーザーインターフェイスを作成し、要素をドラッグ可能にし、キーイベントを処理し、カスタムフィードバックを使用してユーザーエクスペリエンスを強化する手順を作成する方法について説明します。また、aへの一般的な落とし穴についても説明します

クライアントとサーバー間の双方向通信にHTML5 WebSockets APIを使用するにはどうすればよいですか? クライアントとサーバー間の双方向通信にHTML5 WebSockets APIを使用するにはどうすればよいですか? Mar 12, 2025 pm 03:20 PM

この記事では、リアルタイムの双方向のクライアントサーバー通信のために、HTML5 WebSockets APIについて説明します。 クライアントサイド(JavaScript)とサーバー側(Python/Flask)の実装を詳述し、スケーラビリティ、状態管理、などの課題に対処します。

See all articles