新浪微博发布输入框@功能

新浪微博发布输入框,用getBoundingClientRect()来获取@的位置,再根据@的位置来确定联系人列表查看Demo

之前的版本使用nobr标签来计算@的位置,具体方法是用换行符把字符串分割成数组,计算每个数组的宽高,再加上所有换行符,可得高度 top;宽度是用分割后的数组最后一个元素的字符串的宽度对textarea宽度取模得出 left。加上一些基本的字符串操作和恰当的CSS定位就可以实现。不过后来发现有个问题,比如说在中文字母数字都输入的时候,一连串的数字或字母会自动换行,如下图所示的情况:
一连串的字母数字会自动换行导致定位出现问题

这样就造成了@定位不准确。

昨天无意的浏览发现了getBoundingClientRect()这个方法,它提供了元素的位置信息,有了这个方法方便多了,想想之前的版本是把问题想复杂了。如下图所示解决了上图所示的问题:
用getBoundingClientRect()后一连串的字母数字会自动换行也不会出现定位问题

关于getBoundingClientRect()这个方法简单介绍一下:

语法:

1
oRect = object.getBoundingClientRect()

MSDN给的返回值解释为:Returns a TextRectangle object. Each rectangle has four integer properties (top, left, right, and bottom) that represent a coordinate of the rectangle, in pixels.

简单理解就是该方法获得页面中某个元素的上,左,右和下分别相对浏览器视窗左上角(注意,不是文档区域的左上角)。下图是很好的解释:
getBoundingClientRect()图解
object.getBoundingClientRect().top元素上边距离浏览器视窗上边的距离
object.getBoundingClientRect().right元素右边距离浏览器视窗左边的距离
object.getBoundingClientRect().bottom元素下边距离浏览器视窗上边的距离
object.getBoundingClientRect().left元素左边距离浏览器视窗左边的距离
注意:IE、Firefox3+、Opera9.5、Chrome、Safari支持,在IE中,默认坐标从(2,2)开始计算,导致最终距离比其他浏览器多出两个像素,因此需要处理兼容。

对于这个@的定位,上图中蓝色的块应该被@替代,用@的left和top来给弹出的联系人列表定位。

问题是怎么处理@这个字符,一般的方法是在输入框textarea下面放置一个和其规格一样的不可见的div,也就是位置、宽高、margin和padding都一致,这样就能保证上下两层字符串是完全重叠的。

什么时候出现联系人列表呢?试了试新浪微博的输入框,肯定是要有@字符的,再判断当前光标与光标之前最近的@字符之间是否有空格和回车,若没有则根据光标和@之间的字符串来筛选联系人,最终生成联系人列表供用户选择。否则反之。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//获取textarea的值
var objString = textarea.value;
//记录光标当前位置
var cursorPosition = posCursor(textarea);
//光标之前的字符串
var beforeCursorString = objString.substr(0,cursorPosition);
//记录@在光表前出现的最近的位置
var atLocation = beforeCursorString.lastIndexOf('@');
//记录光标和光标前最近的@之间的字符串,记为标识符,判断其是否含有空格
var indexString = objString.substr(atLocation,cursorPosition-atLocation);
//记录从开始到光标前最近的@之间的字符串,用来定位
var positionString = objString.substr(0,atLocation);

if (beforeCursorString.indexOf('@')!=-1&&indexString.indexOf(' ')==-1&&indexString.indexOf('\n')==-1) {
//满足以上三个条件(光标之前的字符串含有@,光标和其之前最近的@之间没有空格和回车)
//则出现@联系人列表,接下来执行相关操作
}

首先我们得找到@,上段代码中的atLocation就是@出现的位置,接下来就根据这个@来定位。

根据atLocation找到从起始位置到@之前的字符串positionString,然后把这段字符串中的换行处理掉,并在结尾加上<span id="at">@</span>,并插入到textarea下面的不可见的divinnerHTML中:

1
hiddenObj.innerHTML = positionString.replace(/\n/g,"<br/>") + '<span id="at">@</span>';

现在就可以参考<span id="at">@</span>的位置来给联系人列表定位了。下面是主要函数:

1
2
3
4
5
6
7
8
9
10
function getXY(obj) {
var rect = obj.getBoundingClientRect(),
scrollTop = document.body.scrollTop || document.documentElement.scrollTop,
scrollLeft = document.body.scrollLeft || document.documentElement.scrollLeft,
isIE = window.ActiveXObject ? 2 : 0;
var position ={};
position.left = rect.left - isIE + scrollLeft;
position.top = rect.top - isIE + scrollTop;
return position;
}

这个函数传入一个元素(本例中包裹@字符的span),根据getBoundingClientRect()获取坐标之后,再加上页面滚动的高度和IE下的2px的误差,返回一个包含字符@位置的对象position

在最后的定位时,加上一些调节的参数修正一下联系人列表的位置,因为联系人列表应该在@字符的下面而不是覆盖。像下面这样修正一下:

1
2
atList.style.left = getXY(at).left + 2 + 'px';
atList.style.top = getXY(at).top + 18 + 'px';

点击联系人时把该人名插入到textarea中。我们以@为标识符把textarea中的字符串分成两部分,再把@和人名和空格组装成第三部分插入到之前两部分的中间,代码如下:

1
2
3
4
5
//将textarea分成三块,@之前的area1、@+联系人+' '的area2、光标之后的area3
var area1 = objString.substr(0,atLocation);
var area2 = '@' + listClick[i].innerHTML + ' ';
var area3 = objString.substr(cursorPosition,getLength(objString) - cursorPosition);
textarea.value = area1+area2+area3;

这步完成之后就要把光标定位到上一步组装的字符串后,也就是上段代码中的area2之后。

至此,这个功能就算搞定了。值得欣慰的是IE5.5+、Firefox 3.5+、Chrome 4+、Safari 4.0+、Opara 10.10+均支持getBoundingClientRect()方法,我写的这段代码考虑了兼容性,所以这个小小的插件通吃绝大多数浏览器。

Fork me on GitHub