TextRange之插入表情

SNS类或是微博类的产品一般都有一个功能:插入表情,如下所示:

image

 

重点:兼容IE与其它主流的浏览器,根据上一次选择的选区范围进行操作。

自己写了一个TextRange(参考了网上的一些例子和代码)

/**
 * @author Meteoric_cry
 */

/**
 * 文本选区操作类
 */
var TextRange = function() {
	var inner;
	
	return inner = {
		/**
		 * 获取选区
		 * 
		 * @param {HTMLElement} oElement input或是textarea对象
		 * @return [start, end] 返回input或textarea选区的开始与结束的索引值
		 */
		getRange : function(oElement) {
			var start = 0, end = 0;
			
			if(!document.selection) {//not IE
				start = oElement.selectionStart;
				end = oElement.selectionEnd;
			} else if(document.selection) {
				var range = document.selection.createRange(),
					range_all = document.body.createTextRange(),
					i = 0;
				
				
				range_all.moveToElementText(oElement);
				/**
				 * 两个range:一个是已经选择的text(range),一个是整个textarea(range_all)
				 * 
				 * compareEndPoints比较两个端点,range_all的起点比range更向左(allIndex - index < 0),则range_all需要向右移动
				 */
				for(; range_all.compareEndPoints("StartToStart", range) < 0; start++) {
					range_all.moveStart('character', 1);
				}
				
				for(; i<start; i++) {
					if(oElement.value.charAt(i) == "\n") {
						start++;
					}
				}
				
				range_all = document.body.createTextRange();
				range_all.moveToElementText(oElement);
				
				for(; range_all.compareEndPoints('StartToEnd', range) < 0; end++) {
					range_all.moveStart('character', 1);
				}
				
				for(i=0; i <= end; i++) {
					if(oElement.value.charAt(i) == "\n") {
						end++;
					}
				}
			}
			
			return [start, end];
		},	
		/**
		 * 获取选区的起始位置
		 * 
		 * @param {HTMLElement} oElement input或是textarea对象
		 * @return DOM对象选区的起始位置
		 */
		selectionStart : function(oElement) {
			return inner.getRange(oElement)[0];
		},
		/**
		 * 获取选取前的内容
		 * 
		 * @param {HTMLElement} oElement input或是textarea对象
		 * @return 返回DOM对象选区开始前的文本内容
		 */
		selectionBefore : function(oElement) {
			 return oElement.value.slice(0, inner.getRange(oElement)[0]);
		},
		/**
		 * 设置DOM的选区
		 * 
		 * @param {HTMLElement} oElement input或是textarea对象
		 * @param {Number} start 被设置的选区的起始位置 
		 * @param {Number} end	被设置的选区的结束位置
		 */
		selectText : function(oElement, start, end) {
			oElement.focus();
			
	        if (!document.selection) {
	            oElement.setSelectionRange(start, end);
	            return ;
	        }
	        var range = oElement.createTextRange();
	        range.collapse(1);
	        range.moveStart("character", start);
	        range.moveEnd("character", end - start);
	        range.select();
		},
		/**
		 * 插入文本内容
		 * 
		 * @param {HTMLElement} oElement
		 * @param {String} sInsertText
		 * @param {Number} nStart
		 * @param {Number} nLen
		 */
		insertText : function(oElement, sInsertText, nStart, nLen) {
			oElement.focus();
	        nLen = nLen || 0;
			
	        if (!document.selection) {
	            var text = oElement.value,
	                start = nStart - nLen,
	                end = start + sInsertText.length;
				
	            oElement.value = text.slice(0, start) + sInsertText + text.slice(nStart, text.length);
	            it.selectText(oElement, end, end);
	            return ;
	        }
	        var c = document.selection.createRange();
	        c.moveStart("character", -nLen);
	        c.text = sInsertText;
		},
		/**
		 * 获取光标的位置
		 * 
		 * @param {HTMLElement} oElement
		 * @return cursorPos
		 */
		getCursorPos : function(oElement) {
			var cursorPos = 0;
			
	        if (window.ActiveXObject) {
	            oElement.focus();
	            var range = document.selection.createRange(),
					stored_range = range.duplicate();
	           	
	            stored_range.moveToElementText(oElement);
	            stored_range.setEndPoint("EndToEnd", range);
	            oElement.selectionStart = stored_range.text.length - range.text.length;
	            oElement.selectionEnd = oElement.selectionStart + range.text.length;
	            cursorPos = oElement.selectionStart;
	        } else {
	            if (oElement.selectionStart || oElement.selectionStart == "0") {
	                cursorPos = oElement.selectionStart;
	            }
	        }
			
	        return cursorPos;
		},
		/**
		 * 获取选区内的文本内容
		 * 
		 * @param {HTMLElement} oElement
		 * @return selectedText 选区的文本内容
		 */
		getSelectedText : function(oElement) {
			var selectedText = "";
			
	        var getSelection = function (e) {
	            if (e.selectionStart != undefined && e.selectionEnd != undefined) {
	                return e.value.slice(e.selectionStart, e.selectionEnd);
	            } else {
	                return "";
	            }
	        };
			
	        if (window.getSelection) {
	            selectedText = getSelection(oElement)
	        } else {
	            selectedText = document.selection.createRange().text;
	        }
	        return selectedText;
		},
		/**
		 * 设置光标的位置,默认不选区
		 * 
		 * @param {HTMLElement} oElement
		 * @param {Number} pos
		 * @param {Number} coverlen
		 */
		setCursor : function(oElement, pos, coverlen) {
			pos = pos ? pos : oElement.value.length;
			coverlen = coverlen ? coverlen : 0;
	        oElement.focus();
			
	        if (oElement.createTextRange) {
	            var range = oElement.createTextRange();
	            range.move("character", pos);
	            range.moveEnd("character", coverlen);
	            range.select();
	        } else {
	            oElement.setSelectionRange(pos, pos + coverlen);
	        }
		},
		/**
		 * 插入内容后光标的位置保持不变
		 * 
		 * @param {HTMLElement} oElement  
		 * @param {String} str
		 * @param {Object} pars
		 */
		unCoverInsertText : function(oElement, str, pars) {
			pars = pars ? pars : {};
	        pars.start =  pars.start ? pars.start * 1 : 0;
	        pars.end = pars.end ? pars.end * 1 : oElement.value.length;
			
	        var text = oElement.value,
	            fstr = text.slice(0, pars.start),
	            lstr = text.slice(pars.end, text == "" ? 0 : text.length);
			
			oElement.value = fstr + str + lstr;
			
	        inner.setCursor(oElement, pars.start + (str ? str.length : 0));
		}
	}
}();

 

先写个例子测试一下TextRange里面的方法:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>选区的测试</title>
<script type="text/javascript" src="textRange.js"></script>
</head>
<body>

<table cellpadding="0" cellspacing="0" style="border:1px solid #406c99;">
	<tbody>
		<tr>
			<td>start:<input type="text" id="start" /></td>
			<td>end:<input type="text" id="end" /></td>
		</tr>
		<tr>
			<td colspan="2">
				<textarea id="txt" style="width:400px; height:100px;"  onkeyup="savePos(this);"  onmouseup="savePos(this);" onfocus="savePos(this);"></textarea>
			</td>
		</tr>
		<tr>
			<td><input type="text" id="insTxt" /></td>
			<td><input type="button" onclick="addText();" value="addText" /></td>
		</tr>
	</tbody>
</table>


<div>
	<button onclick="setSelection();">选中选区</button>
	<button onclick="setCursorPos();">设置光标位置</button>
</div>

<div style="border:1px solid #999; width:800px; margin-top:30px;">
	选区之前的内容:<input type="text" id="beforeTxt"	style="width:600px;"/>
</div>

<script type="text/javascript">
	function $(id) {
		return typeof id === "string" ? document.getElementById(id) : id;
	}
	
	//添加内容
	function addText() {
		var elem = $("txt");
		var range = elem.getAttribute('range') ? elem.getAttribute('range').split("|") : [0, 0];
		var str_1 = elem.value.slice(0, range[0]);
		var str_2 = elem.value.slice(range[1]);
		
		elem.value = str_1 + $("insTxt").value + str_2;
	}
	
	//保存选区
	function savePos(elem) {
		var range = TextRange.getRange(elem);
		
		$("start").value = range[0];
		$("end").value = range[1];
		
		var rangeBeforeTxt = TextRange.selectionBefore(elem);
		$("beforeTxt").value = rangeBeforeTxt.replace(/\n/g, '--');
		
		elem.setAttribute("range", range.join("|"));
	}
	
	//设置选区
	function setSelection() {
		var start = $("start").value,
			end = $("end").value;
			
		TextRange.selectText($("txt"), start, end);
		
		TextRange.unCoverInsertText($("txt"), '#===#', {
            start: start,
            end: end
        })
	}
	
	function setCursorPos() {
		TextRange.setCursor($("txt"), 3, 5);//索引值、长度
	}
</script>

</body>
</html>

 

博客园插入代码好像有问题,复制到本地运行测试一下就行了 :)

 

下面就是插入表情的示例代码了:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>UBB表情的插入</title>
<script type="text/javascript" src="textRange.js"></script>
<style type="text/css">
	* {margin:0; padding:0;}
	body {font:12px/1.3 Arial, Helvetica, sans-serif; background-color:#FFFFFF;}
	ul, li {list-style:none}
	img {border:none}
	
	.container {margin:30px;}
	#txtDemo {width:475px; height:110px; word-break:break-all; word-wrap:break-word;}
	#facePanel {border-width:1px 2px 2px 1px; border-color: #CCCCCC; border-style:solid; -webkit-border-radius: 6px; -moz-border-radius:6px; padding:6px; width:330px; margin-top:5px; display:none;}
	#facePanel li{float:left; width:28px; height:28px; padding:0 5px 5px 0;}
	#facePanel li a {display:block; text-decoration:none; border:1px dashed #DDD; width:26px; height:26px; line-height:26px; overflow:hidden; text-align:center;}
	#facePanel li a:hover {border-color:#999;}
	
	#facePanel li a span {display:inline-block;}
	#facePanel li a img {vertical-align:middle;}
	
	#facePanel ul {zoom:1;}
	#facePanel ul:after{ content:"\0020"; display:block; height:0; line-height:0; clear:both; visibility:hidden;}
</style>
</head>
<body>
	
<div class="container">
	<div>
		<textarea id="txtDemo"></textarea>
	</div>
	<button onclick="showFace(event);">插入表情</button>
	<div id="facePanel">sadf</div>
</div>

<script type="text/javascript">
	function $(id) {
		return typeof id === 'string' ? document.getElementById(id) : id;
	}
	
	function addEvent(el, type, handler) {
		if(el.attachEvent) {
			el.attachEvent("on" + type, handler);
		} else if(el.addEventListener) {
			el.addEventListener(type, handler, false);
		}
	}
	
	function removeEvent(el, type, handler) {
		if(el.detachEvent) {
			el.detachEvent('on' + type, handler);
		} else if(el.removeEventListener) {
			el.removeEventListener(type, handler, false);
		}
	}
	
	var faceInited = false;
	
	//显示表情
	function showFace(ev) {
		if(!faceInited) {
			initFace();
		}
		
		var facePanel= $("facePanel");
		
		facePanel.style.display = "block";
		addEvent(document.body, 'click', hideFace);
		addEvent(facePanel, 'click', cancelEventBubble);
		
		cancelEventBubble(ev);
	}
	
	//隐藏表情
	function hideFace() {
		$("facePanel").style.display = "none";
		removeEvent(document.body, 'click', hideFace);
		removeEvent($("facePanel"), 'click', cancelEventBubble);
	}
	
	//插入表情
	function insertFace(elem) {
		var txtElem = $("txtDemo"),
			range = txtElem.getAttribute("range") ? txtElem.getAttribute("range").split("|") : [0, 0];
		
		var str_1 = txtElem.value.slice(0, range[0]);
		var str_2 = txtElem.value.slice(range[1]);
		
		txtElem.value = str_1 + elem.getAttribute("value") + str_2;
		
		if(!document.selection) {
			txtElem.selectionStart = txtElem.value.length;
			txtElem.selectionEnd = txtElem.value.length;
		} else {
			var range = txtElem.createTextRange();
	        range.collapse(1);
	        range.moveStart("character", txtElem.value.length);
	        range.moveEnd("character", txtElem.value.length);
	        range.select();
		}
		
		txtElem.focus();
		
		hideFace();
	}
	
	//取消事件冒泡
	function cancelEventBubble(ev) {
		ev = ev || window.event;
		
		if(ev.stopPropagation) {
			ev.stopPropagation();
		} else if(!ev.cancelBubble) {
			ev.cancelBubble = true;
		}
	}
	
	//记录textarea的选区的start&end
	function savePos() {
		var elem = $("txtDemo"),
			range = getRange(elem);//获取选区
		
		elem.setAttribute("range", range.join("|"));
		
		document.title = "start:" + range[0] + "--" + "end:" + range[1];
		
		cancelEventBubble(arguments[0] || window.event);
	}
	
	!(function() {
		var txtElem = $("txtDemo");
		
		addEvent(txtElem, 'focus', savePos);
		addEvent(txtElem, 'mouseup', savePos);
		addEvent(txtElem, 'keyup', savePos);
		addEvent(txtElem, 'mousemove', savePos);//Chrome 在选中文本域内的文字时,不能触发mouseup事件,导致range依旧为最近一次的range
	})();
	
	//初始化表情
	function initFace() {
		var faces = [{"icon":"\u8db3\u7403","value":"[\u8db3\u7403]","src":"basic\/football.gif"},{"icon":"\u54e8\u5b50","value":"[\u54e8\u5b50]","src":"basic\/shao.gif"},{"icon":"\u7ea2\u724c","value":"[\u7ea2\u724c]","src":"basic\/redcard.gif"},{"icon":"\u9ec4\u724c","value":"[\u9ec4\u724c]","src":"basic\/yellowcard.gif"},{"icon":"\u54c8\u54c8","value":"[\u54c8\u54c8]","src":"basic\/laugh.gif"},{"icon":"\u5475\u5475","value":"[\u5475\u5475]","src":"basic\/smile.gif"},{"icon":"\u6cea","value":"[\u6cea]","src":"basic\/cry.gif"},{"icon":"\u6c57","value":"[\u6c57]","src":"basic\/sweat.gif"},{"icon":"\u7231\u4f60","value":"[\u7231\u4f60]","src":"basic\/love.gif"},{"icon":"\u563b\u563b","value":"[\u563b\u563b]","src":"basic\/tooth.gif"},{"icon":"\u54fc","value":"[\u54fc]","src":"basic\/hate.gif"},{"icon":"\u5fc3","value":"[\u5fc3]","src":"basic\/heart.gif"},{"icon":"\u6655","value":"[\u6655]","src":"basic\/dizzy.gif"},{"icon":"\u6012","value":"[\u6012]","src":"basic\/angry.gif"},{"icon":"\u86cb\u7cd5","value":"[\u86cb\u7cd5]","src":"basic\/cake.gif"},{"icon":"\u82b1","value":"[\u82b1]","src":"basic\/flower.gif"},{"icon":"\u6293\u72c2","value":"[\u6293\u72c2]","src":"basic\/crazy.gif"},{"icon":"\u56f0","value":"[\u56f0]","src":"basic\/sleepy.gif"},{"icon":"\u5e72\u676f","value":"[\u5e72\u676f]","src":"basic\/cheer.gif"},{"icon":"\u592a\u9633","value":"[\u592a\u9633]","src":"basic\/sun.gif"},{"icon":"\u4e0b\u96e8","value":"[\u4e0b\u96e8]","src":"basic\/rain.gif"},{"icon":"\u4f24\u5fc3","value":"[\u4f24\u5fc3]","src":"basic\/sad.gif"},{"icon":"\u6708\u4eae","value":"[\u6708\u4eae]","src":"basic\/moon.gif"},{"icon":"\u732a\u5934","value":"[\u732a\u5934]","src":"basic\/pig.gif"},{"icon":"\u8721\u70db","value":"[\u8721\u70db]","src":"basic\/candle.gif"}];
		var imgURI = "http://timg.sjs.sinajs.cn/miniblog2style/images/common/face/";
		var tempArr = [];
		tempArr.push('<ul>');
		
		for(var i=0, len = faces.length; i<len; i++) {
			tempArr.push([
				'<li><a href="javascript:;" hideFocus="true" onclick="insertFace(this);return false;" value="' + faces[i].value + '" title="' + faces[i].icon + '"><img src="' + imgURI + faces[i].src + '" alt="' + faces[i].icon + '" /><span>&nbsp;</span></a></li>',
			].join(""));
		}
		
		tempArr.push('</ul>');
		
		$("facePanel").innerHTML = tempArr.join("");
	}
	
	//获取选区
	function getRange(elem) {
		var start = 0, end = 0;
			
		if(!document.selection) {
			start = elem.selectionStart;
			end = elem.selectionEnd;
		} else if(document.selection) {
			var range = document.selection.createRange(),
				range_all = document.body.createTextRange(),
				i = 0;
				
			range_all.moveToElementText(elem);
			
			for(; range_all.compareEndPoints("StartToStart", range) < 0; start++) {
				range_all.moveStart('character', 1);
			}
			
			for(; i<start; i++) {
				if(elem.value.charAt(i) == "\n") {
					start++;
				}
			}
			
			range_all = document.body.createTextRange();
			range_all.moveToElementText(elem);
			
			for(; range_all.compareEndPoints('StartToEnd', range) < 0; end++) {
				range_all.moveStart('character', 1);
			}
			
			for(i=0; i <= end; i++) {
				if(elem.value.charAt(i) == "\n") {
					end++;
				}
			}
		}
		
		return [start, end];
	}
</script>

</body>
</html>
posted @ 2010-10-14 14:01  meteoric_cry  阅读(954)  评论(2编辑  收藏  举报