jQuery插件自动扩展文本域
自动扩张文本域在诸如Facebook这样的网站上非常流行,文本域的高度随用户输入文本数量的多少而缩放有很多好处:
1. 页面不会被大面积的文本域占有;
2. 一个在线
单有多个文本域看起来更简洁,便于用户完成表单;
3. 用户可以在不使用滚动条的情况下查看文本。
第三部分教程将介绍如何用html和一个可重用德尔jQuery插件构建自动扩展文本域,在看第三篇之前,你需要了解它是如何工作的,以及如何在你的项目中使用这些代码。
查看demo
需求
和许多优秀的开发者一样,我们需要全面理解系统的需求。
1. 任何页面的任何文本域在需要时都能自动扩展;
2. 一些文本域并不需要自动扩展功能;
3. 我们必须确定文本域的高度能无限增长,或者在一个特定额范围里变化,例
如:在50px,100px之间变化;
4. 其解决
在任何页面能得到重用;
5. 必须使用“渐进增强”技巧确保用户在javascript禁用的情况下仍能使用;
6. 方案需兼容IE6,、Firefox2+、opera、safari、chrome。 计划
我们将解决方案作为一个jQuery插件来对待,jQuery主要用来处理更多的DOM嗅探以及事件代理中常见问题,你可以用其它框架来重写代码。
但是,我们如何知道何时该调整文本域的高度,以及如何使用高度值?
首先,我么给文本域分配一个“keyup”事件处理器,这样用户在按下鼠标键和改变文本时会调用函数。
接下来,我们
DOM的scrollHeight属性,它返回的是内部滚动区域的的高度。例如,用户输入文本的高度。如果我们设置文本域的高度
为当前滚动条的高度,滚动条就不会出现。不幸的是,scrollHeight并非W3C的标准,并且各个浏览器之间也不一致。
1. 在FF、safari、chrome中,scrollHeight总是小于文本域的高度,即使它没
有任何文本。因此,文本会扩展,删除文本也不会缩小。我们可以暂时将文
本域的高度设为0,然后用真正的scrollHeight的值;
2. 与mozilla中scrollHeight声明不同,FF中不包括padding,而safari 4和
chrome2 却包括padding。如果给其顶部和底部定义2px的padding,在,
webkit浏览器中scrollHeight将高出4px,这样,文本域的高度就会无限增
长。去掉padding,在FF中又不一致,最简单可行的办法将扩展文本域的
padding-top和padding-bottom定义为0;
3. 在IE和opera中,scrollHeight高度更加怪异,它会正常的返回文本域内部
的文本的高度。但是,设置文本域的高度为0将得到不正常scrollHeight值,
虽然有浏览器嗅探技术,但我不认为这是很好的方案,我们必须保证文本域
高度在IE和opera中为0。
我们也需要考虑到滚定条,默认状态下,大多数浏览器仅在需要的时候出现滚动条。但是,如果,设置overflow:auto;新起一行是滚动条就会出现,文本域高度改变时就会消失,overflow:hidden会解决滚定条闪动的问题,但是不要应用到非扩展文本域或者那些已经超出最大高度的文本域。
最后,窗口缩放也是一个问题。流动网页设计可以给文本域设置百分比宽度,那么缩放窗口就可以缩放盒子。虽然我们可以探测到窗口缩放,但在IE中方事件非常糟糕,并很快的调用事件处理器。我们可以通过代码解决该问题,但是,缩放多个文本域会导致页面跳动,从而混淆用户。因此,在文本域一旦获得焦点时,仅仅调整一次文本域的高度。
要考虑的问题很多,现在,喝杯咖啡,接下来阅读第二篇。
在第一篇我们讨论了如何构建自动扩展文本框,并总结了各种需求。在我们真正关心实际的Javascript代码之前,我们需要明白如何在网页中使用我们的代码。
自动扩展文本域如何初始化,既然我们的方案是jQuery插件,页面中每一个需要自动扩展的文本域调用以下的
:
view sourceprint?
1.$("textarea").TextAreaExpander();
这样声明有点鲁莽,既然每一个文本域都自动扩展,也没有响应的高度限制,这样一个页面就有很多声明。
view sourceprint?
1.$("#textarea1").TextAreaExpander();
2.$("#textarea2").TextAreaExpander(50, 200); // box will size between 50 and 200 pixels 3.$("#textarea3").TextAreaExpander(90, 300); // box will size between 90 and 300 pixels
虽然这很容易办到,服务器会针对应用状态生成不同的文本域。维护这样的代码很费劲,也容易产生开发错误。
我们需要事情更加简单化,并使其他开发者也能使用我们的组件。如果一个文本域能自动扩展,我们就给其定义一个“expand”的class属性,它也可拥有其它class属性,不必担心它已经拥有的class,可以添加任意数量的class,只需用空格分开。 view sourceprint?
1.
2.
要限制文本域高度在一定范围内,我们可以给expand添加minium和maxium值,如
view sourceprint?
1.
2.
当页面加载后,js将在DOM树中搜索含有“expand”属性的节点,并其自动为其应用自动扩展效果,这有几个好处:
1. 已经存在的文本域默认状态下不会自动扩展;
2. 与在js中分开声明不同,事件触发特定于具体的文本域标签中,他容易理解,
便于阅读,方便维护;
3. 在需要的时候我们仍可使用jQuery TextAreaExpander() 方法,例如,在页
面加载后一个文本域添加到了DOM树中;
4. 渐进增强实在是太好了。
确定了html代码以及自动扩展触发的时机之后,我们可以将代码放到页面的底部。
view sourceprint?
1.
2.
加载最新的jQuery库和我们自己创建的TextAreaExpander 插件;
第一篇我们讨论了如何构建自动扩展文本域,并分析了需求。第二篇介绍了如何初始化,现在,是时候开始最耗体力工作的时候了。
插件名为:TextAreaExpander ,你可以参阅read about the intricacies of
jQuery plugin development in this tutorial了解jQuery插件的构建方法,核心代码如下:
view sourceprint?
01.(function($) {
02.
03. // jQuery plugin definition
04. $.fn.TextAreaExpander = function(minHeight, maxHeight) {
05.
06. // ... our code ...
07.
08. return this;
09. };
10.
11.})(jQuery);
$.fn声明定义了一个新的插件,,TextAreaExpander,接受minHeight和maxHeight两个参数,我们也可以用json对象来表示,但是我们不可能有更多的参
数,因此我们尽量简单化。
注:“this”指的是jQuery对象,返回该对象用以 确保jQuery效果绑定到同一
个DOM元素。
初始化文本域
将下面的初始化代码放到TextAreaExpander中: view sourceprint?
01.// initialize
02.this.each(function() {
03.
04. // is a textarea?
05. if (this.nodeName.toLowerCase() != "textarea") return; 06.
07. // set height restrictions
08. var p = this.className.match(/expand(\d+)\-*(\d+)*/i); 09. this.expandMin = minHeight || (p ? parseInt('0'+p[1], 10) : 0); 10. this.expandMax = maxHeight || (p ? parseInt('0'+p[2], 10) : 99999); 11.
12. // initial resize
13. ResizeTextarea(this);
14.
15. // add events
16. if (!this.Initialized) {
17. this.Initialized = true;
18. $(this).css("padding-top", 0).css("padding-bottom", 0);
19. $(this).bind("keyup", ResizeTextarea).bind("focus", ResizeTextarea); 20. }
21.});
22.
23.return this;
24.};
函数遍历jQuery对象选择的所有节点,并执行匿名函数,函数内部的“this”是一个文本节点,初始化是这样的:
1. 第一行确保只有文本域才拥有自动扩展功能;
2. 接下来的三行获得高度的最小和最大值,TextAreaExpander接受的两个参
数是默认值,如果默认值不存在,将用正则表达式分析class属性“expand”,
如果仍不存在,那么就是0~~999(注:文本域总是有一个有一个最小值,
那么其高度将不会为0),这些值被当作文本域的属性储存起来。
3. 接下来的一行调用ResizeTextarea 函数,并传递文本节点。这样,在文本
域初始化时,将会设置一个最小高度。
4. 最后,我们重设垂直的padding值,并定义“keyup”和“focus”事件。在文本获
得焦点和用户改变文本时,ResizeTextarea函数将会被调用;“if”语句确保任
何文本域只调用一次ResizeTextarea 方法,条件语句可以应用到整个初始
化函数。但是,但这样就可以随意改变其最小最大值。
缩放文本域
现在我们定义我们的ResizeTextarea函数
在第一篇,我们分析了浏览器的差异,IE和opera中的文本域不应该定义height:0。因此,我们需定义一个变量,它在IE、opera中的返回值为false。 view sourceprint?
1.var hCheck = !($.browser.msie || $.browser.opera);
现在,创建ResizeTextarea函数
view sourceprint?
01.// resize a textarea
02.function ResizeTextarea(e) {
03.
04. // event or element?
05. e = e.target || e;
06.
07. // find content length and box width
08. var vlen = e.value.length, ewidth = e.offsetWidth;
09. if (vlen != e.valLength || ewidth != e.boxWidth) {
10.
11. if (hCheck && (vlen < e.valLength || ewidth != e.boxWidth)) e.style.height = "0px";
12. var h = Math.max(e.expandMin, Math.min(e.scrollHeight, e.expandMax)); 13.
14. e.style.overflow = (e.scrollHeight > h ? "auto" : "hidden"); 15. e.style.height = h + "px";
16.
17. e.valLength = vlen;
18. e.boxWidth = ewidth;
19. }
20.
21. return true;
22.};
这个函数接受了一个参数“e”,它要么是文本域节点(初始化时),要么是一个事件对象(按下鼠标键或获得焦点时):
1. 第一行在事件释放时将“e”指向文本域节点;
2. 文本域输入的字数存储到vLen中,盒子的宽度存储到eWidth中,如果这些
值没有改变,我们就没必要担心box的缩放(用户只是移动鼠标)。vLen
和eWidth会作为文本域节点对象的valLength 和boxWidth 保存下来,这
些值在文本域缩放时将被设置。因此,缩放总是出现在ResizeTextarea函
数的第一次调用;
3. 接下来的几行重置文本域大高度为0。这仅仅出现在非IE/opera浏览器中,
在
被删除和盒子宽度改变时将会发生;
4. 文本域的scrollHeight分派给变量“h”,Math.min和Math.max用来确保其值
在文本域规定的范围之内;
5. 在我们改变文本域高度之前,修改其overflow属性,滚动条仅在内容高度超
过文本域高度时出现;
6. 现在我们就可以修改文本域的高度,更新valLength和boxWidth的值;
7. 最后,函数返回true确保其文本域事件处理器不会被取消。
到此,TextAreaExpander 插件已经完成,为了确保其效果应用到class属性为“expand”的文本域上。在文件末尾,我们需要增加一个事件在页面加载后初始化响应的文本域。
view sourceprint?
1.// initialize all expanding textareas
2.jQuery(document).ready(function() {
3. jQuery("textarea[class*=expand]").TextAreaExpander();
4.});