JS实现动态提示框
引言
什么项目都有个需求,应开发需求,需要写一个公式编辑器选择公式的插件,下面给大家讲一下实现过程。(擦汗,强作淡定,咳,开嗓~)
看图说话
本小菜开发功能前乐于先写个需求思维导图(纯属个人爱好):
Html
真的只有一点点~

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>公式编辑器测试</title>
<link rel="stylesheet" type="text/css" href="./css/global.css">
<script type="text/javascript" src="./js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="./js/editTips.js"></script> </head>
<body> <textarea id="test" ></textarea> </body>
</html>

css
一起给了吧~迟早要给:

/* 编辑器下拉框相关样式 */
table,tr,th,td{padding:;margin:;}
ul,li,textarea,input{text-decoration:none;list-style:none;margin:;padding:;box-sizing: border-box;}
input{
outline:none;
}
.editTips{
padding:5px 0;
border-radius: 0!important;
box-shadow: 0 2px 4px rgba(0,0,0,.2);
max-height: auto;
margin:;
z-index:;
}
.editTips li{
text-align:left;
box-sizing:border-box;
display:block;
width:100%;
line-height:1.42857143;
margin:1px 0;
padding:6px 11px;
color:#333;
cursor:pointer;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
font-weight:;
line-height:1.42857143;
border-bottom:solid 1px #e5e5e5;
background:#e5e5e5;
}
.editTips li:last-child{
border-bottom:none;
}
.active{
background:#fee188!important;
}
.editTips li.active{ background:#fee188!important; }
textarea{
text-decoration: none;
list-style: none;
margin:;
padding:;
display: block;
box-sizing: border-box;
width: 500px;
height: 300px;
margin-top: 100px;
margin-left: 100px;
}

插件模板 - this 为输入框

(function ($) {
$.fn.extend({
"editTips": function (options) {
var opts = $.extend({}, defaults, options); //使用jQuery.extend 覆盖插件默认参数
return this.each(function () { //这里的this 就是 jQuery对象 });
}
});
//默认参数
var defaults = { };
})(window.jQuery);

插入下拉框
这里 this 为输入框,在 html 中,不需要再去js插入,当然这不是问题,在这里我们将 输入框和下拉框都自定义一下,插入的 ul 中 editTips 类可以去掉,为了方便看效果暂时加上
我个人肯定是希望下拉框宽度是可以自定义的: 'width':opts.dropdownWidth, 这里我们定义了第一个参数: 下拉框宽度

var _this = this;
_this.entertext = $(this);
_this.dropdown = $('<ul class="editTips" style="display:none;"></ul>');
$("body").after(_this.dropdown);
_this.dropdown.css({
'width':opts.dropdownWidth,
'position':'absolute',
});

监听文本框
ps:既然是公式编辑器,那说明不会一直触发检索事件,只有在输入了指定字符的时候才会触发检索事件,比如:! @ # 之类,这里我们以 $ 为例,从这里我们可以想到,我们肯定
需要一个参数来动态改变检索事件触发条件,先记在心里
检索流程: 输入框输入值 - 输入特定字符触发检索,如 $ - 是否符合放松请求条件:输入间隔一秒 - 调用回调函数,我们这里为 callbacktips 匹配关键字 - 返回包含关键字部分匹配结果,以数组形式 - 遍历数组添加到下拉框 ul 中显示 - 选取当前项
其中数组相关方法运用得较多
因为其中还涉及到限制请求,方向键切换当前项,这里就不分开说了,直接给出整块代码如下:

// 监听输入框
_this.dropdown.parent().on("keyup",this,function(event){
var nowTime = window.sessionStorage.getItem('nowTime');
// 当前项索引
var n = _this.dropdown.find(".active").index();
// li 个数
var n_max = _this.dropdown.find("li").length;
// 注意 event在 firefox 中不能兼容,在方法中带上event参数,如下声明实现兼容
EVT = event || window.event;
if( EVT.keyCode == 38 ){
// 方向键控制 li 选项
if(n-1>=0){
_this.dropdown.find('li').eq(n-1).addClass("active").siblings().removeClass("active");
}
if( n == 0){
_this.dropdown.find('li').eq(n_max-1).addClass("active").siblings().removeClass("active");
}
return false;
}
// 禁止enter键换行
if( EVT.keyCode == 13 ){
return false;
}
if( EVT.keyCode == 40 ){
// 方向键控制 li 选项
if(n<n_max-1){
_this.dropdown.find('li').eq(n+1).addClass("active").siblings().removeClass("active");
}
if( n+1 == n_max ){
_this.dropdown.find('li').eq(0).addClass("active").siblings().removeClass("active");
}
return false;
}
// 限制请求,输入间隔不超过一秒时不触发检索事件发送请求
if( nowTime){
var oldTime = Date.now();
var nowTime = window.sessionStorage.getItem('nowTime');
var m = parseInt((oldTime - nowTime)/1000);
if( m >= 1){
// 文本内容
var val = _this.entertext.val();
// 以空格切割文本返回数组
var arr_test = val.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-1];
// 切割最后一个元素 单字符元素 返回数组
var temp_type = temp.split("");
// 获取数字第一个字符
var temp_cha = temp_type[0];
// 最后一个元素长度
var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf(".");
// 定义回调函数 callbacktips
var callbacktips = function(arr_json){
// 初始化 UL
_this.dropdown.find("li").remove();
for( i=0;i<arr_json.length;i++ ){
_this.dropdown.append('<li>'+arr_json[i]+'</li>');
};
_this.dropdown.show();
_this.dropdown.find("li:first-child").addClass("active");
// 自定义样式
_this.dropdown.find("li").css({
'width':'100%',
});
};
// 最后一个元素为空值
if( temp_len == 0 ){
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
// 为特定字符 符合触发条件
if( temp_cha == opts.triggerCharacter ){
if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){
// 调用回调函数
callbacktips(arr_json); });
}
}else{
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
}
}
// 初始化第一次时间
window.sessionStorage.setItem('nowTime',Date.now());
});

ps:这里出现了我们第二个参数,也是至关重要的一个参数,触发检索事件字符: opts.triggerCharacter
鼠标切换当前项
下拉菜单显示之后(这里我们先不管下拉菜单显示的位置),从体验上来说我希望既可以用鼠标选取,也可以用方向键选取,方向键切换当前项整合在上面的代码中,这里鼠标切换当前项:
// 切换当前项
_this.dropdown.on('mouseenter','li',function(){
$(this).addClass("active").siblings().removeClass("active");
});
阻止键盘按键默认事件 - enter键选取当前项
如果不做处理,我们在用方向键切换当前项的时候,你会看到光标也在上下移动,这如何能忍?怎么办?屏蔽它~,在这里我们可以整合enter键选取当前项,看代码:

// 阻止输入过程中 方向键盘的默认事件
_this.entertext.on("keydown",_this.entertext,function(event){
EVT = event || window.event;
if( EVT.keyCode == 38 ){
EVT.preventDefault();
}
if( EVT.keyCode == 40 ){
EVT.preventDefault();
}
// enter键选取对应的 li
if( EVT.keyCode == 13 ){
EVT.preventDefault();
var txt = _this.dropdown.find(".active").html();
var test_txt = _this.entertext.val();
var arr_change = test_txt.split(" ");
// 以空格切割文本返回数组
var arr_test = test_txt.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-1];
var temp_type = temp.split("");
var temp_dot = temp_type.lastIndexOf(".");
var n ;
temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt);
n = temp_type.join('');
arr_change.splice(arr_change.length-1,1,""+n);
_this.entertext.val(arr_change.join(" "));
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
});

点击当前项 重组val
要不我们直接看代码吧,你可能已经饿了~好的。看代码:

// 点击替换 重组val
$(document).on("click",'li',function(){
var txt = $(this).html();
var test_txt = _this.entertext.val();
var arr_change = test_txt.split(" ");
// 以空格切割文本返回数组
var arr_test = test_txt.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-1];y
var temp_type = temp.split("");
var temp_dot = temp_type.lastIndexOf(".");
var n ;
temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt);
n = temp_type.join('');
arr_change.splice(arr_change.length-1,1,""+n);
_this.entertext.val(arr_change.join(" "));
_this.dropdown.hide();
_this.dropdown.find('li').remove();
});

默认参数
到了这里抛开动态显示下拉框位置,我们的公式编辑器应该可以跑一跑了,这里我们给参数设置一下默认值:
//默认参数
var defaults = {
triggerCharacter : '$',
dropdownWidth:'150px'
};
这里我们将特定字符设定为 $,下拉框默认默认宽度为150px,当然其中还有一些配角参数,不屑一提,看官们有兴趣也可以自行添加一些参数
测试之后发现是可以正常使用的,那么接下来就是下拉框显示位置的问题了,这个想象好像不是秒秒钟能解决的问题,怎么办?(吐槽:什么怎么办?问百度啊~不要问我搜不到怎么办,因为我也不知道~)
这种时候不得不安慰自己:身为菜鸟还是有好处的,毕竟有无数前辈在前进的道路上闪闪发光~
搜~
掌声,在这里附上获取光标像素左边的原文链接以示感谢:http://blog.csdn.net/kingwolfofsky/article/details/6586029
剩下的就是整合啦,当然对于整合过程中遇到的一点小问题不必讶异(都坐下,基本操作):
1,测试之后发现下拉框位置是动态出现了,可位置不对,偏移心里的理想位置有八百里。没办法,再去安静的阅读一下前辈的代码,看完代码,发现上文中 下拉框的位置是绝对定位的坐标,这意味着什么
呢?(我承认是后来才发现的~不测试不知道),这意味着下拉框的显示位置不是相对于输入框,而是相对于整个body,所以我们应该把下拉框插入在body里面:
$("body").after(_this.dropdown);
2,再测:发现位置大体上是对了,但还是有偏移,再测测测~原来下拉框显示位置还受输入框 margin-left 和 margin-top 的影响,测试得出以下代码:

// 调用 kingwolfofsky, 获取光标坐标
function show(elem) {
var p = kingwolfofsky.getInputPositon(elem);
var s = _this.dropdown.get(0);
var ttop = parseInt(_this.entertext.css("marginTop"));
var tleft = parseInt(_this.entertext.css("marginLeft"))
console.log(ttop);
s.style.top = p.bottom-ttop+10+'px';
s.style.left = p.left-tleft + 'px';
}

现在我们需要一次完整的测试,调用:

$("#test").editTips({
triggerCharacter : '$',
dropdownWidth:'150px',
keyPressAction:function(temp,callbacktips){
var arr_json;
if( temp == "$" ){
arr_json = ["$a","$ab","$b","$bb"]
}
if(temp && temp.indexOf("$a")== 0){
arr_json = ["$a","$ab"];
}
else if(temp && temp.indexOf("$b")== 0){
arr_json = ["$b","$bb"];
}
callbacktips(arr_json);
}
});

当然我们这里只是模拟返回数组,如果公式库不是很大,可以在前端完成,比如说自己建一个 json文件啥的~测试结果如图:
全部 js 代码
效果很ok,那么接下来呢?看官息怒,我知道到了该出全部代码的时候到了:

/*
*****公式编辑器*****
* 调用 editTips()方法
* editTips({
* triggerCharacter: 触发匹配字符 默认为 "$"
* textareaWidth: 输入框宽度 默认 auto
* textareaHeight: 输入框高度 默认 auto
* dropdownWidth: 下拉提示框宽度 默认150px
* keyPressAction:function(temp,callbacktips){
* // 参数为temp 返回 arr_json 数组 调用回调函数 callbacktips(arr_json)
* var arr_json;
* callbacktips(arr_json);
* }
* });
*
*/ (function ($) {
$.fn.extend({
"editTips": function (options) {
var opts = $.extend({}, defaults, options); //使用jQuery.extend 覆盖插件默认参数
return this.each(function () { //这里的this 就是 jQuery对象
// 获取输入光标在页面中的坐标 返回left和top,bottom
var kingwolfofsky = {
getInputPositon: function (elem) {
if (document.selection) { //IE Support
elem.focus();
var Sel = document.selection.createRange();
return {
left: Sel.boundingLeft,
top: Sel.boundingTop,
bottom: Sel.boundingTop + Sel.boundingHeight
};
} else {
var that = this;
var cloneDiv = '{$clone_div}', cloneLeft = '{$cloneLeft}', cloneFocus = '{$cloneFocus}', cloneRight = '{$cloneRight}';
var none = '<span style="white-space:pre-wrap;"> </span>';
var div = elem[cloneDiv] || document.createElement('div'), focus = elem[cloneFocus] || document.createElement('span');
var text = elem[cloneLeft] || document.createElement('span');
var offset = that._offset(elem), index = this._getFocus(elem), focusOffset = { left: 0, top: 0 }; if (!elem[cloneDiv]) {
elem[cloneDiv] = div, elem[cloneFocus] = focus;
elem[cloneLeft] = text;
div.appendChild(text);
div.appendChild(focus);
document.body.appendChild(div);
focus.innerHTML = '|';
focus.style.cssText = 'display:inline-block;width:0px;overflow:hidden;z-index:-100;word-wrap:break-word;word-break:break-all;';
div.className = this._cloneStyle(elem);
div.style.cssText = 'visibility:hidden;display:inline-block;position:absolute;z-index:-100;word-wrap:break-word;word-break:break-all;overflow:hidden;';
};
div.style.left = this._offset(elem).left + "px";
div.style.top = this._offset(elem).top + "px";
var strTmp = elem.value.substring(0, index).replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br/>').replace(/\s/g, none);
text.innerHTML = strTmp; focus.style.display = 'inline-block';
try { focusOffset = this._offset(focus); } catch (e) { };
focus.style.display = 'none';
return {
left: focusOffset.left,
top: focusOffset.top,
bottom: focusOffset.bottom
};
}
},
// 克隆元素样式并返回类
_cloneStyle: function (elem, cache) {
if (!cache && elem['${cloneName}']) return elem['${cloneName}'];
var className, name, rstyle = /^(number|string)$/;
var rname = /^(content|outline|outlineWidth)$/; //Opera: content; IE8:outline && outlineWidth
var cssText = [], sStyle = elem.style; for (name in sStyle) {
if (!rname.test(name)) {
val = this._getStyle(elem, name);
if (val !== '' && rstyle.test(typeof val)) { // Firefox 4
name = name.replace(/([A-Z])/g, "-$1").toLowerCase();
cssText.push(name);
cssText.push(':');
cssText.push(val);
cssText.push(';');
};
};
};
cssText = cssText.join('');
elem['${cloneName}'] = className = 'clone' + (new Date).getTime();
this._addHeadStyle('.' + className + '{' + cssText + '}');
return className;
}, // 向页头插入样式
_addHeadStyle: function (content) {
var style = this._style[document];
if (!style) {
style = this._style[document] = document.createElement('style');
document.getElementsByTagName('head')[0].appendChild(style);
};
style.styleSheet && (style.styleSheet.cssText += content) || style.appendChild(document.createTextNode(content));
},
_style: {}, // 获取最终样式
_getStyle: 'getComputedStyle' in window ? function (elem, name) {
return getComputedStyle(elem, null)[name];
} : function (elem, name) {
return elem.currentStyle[name];
},
// 获取光标在文本框的位置
_getFocus: function (elem) {
var index = 0;
if (document.selection) {// IE Support
elem.focus();
var Sel = document.selection.createRange();
if (elem.nodeName === 'TEXTAREA') {//textarea
var Sel2 = Sel.duplicate();
Sel2.moveToElementText(elem);
var index = -1;
while (Sel2.inRange(Sel)) {
Sel2.moveStart('character');
index++;
};
}
else if (elem.nodeName === 'INPUT') {// input
Sel.moveStart('character', -elem.value.length);
index = Sel.text.length;
}
}
else if (elem.selectionStart || elem.selectionStart == '0') { // Firefox support
index = elem.selectionStart;
}
return (index);
}, // 获取元素在页面中位置
_offset: function (elem) {
var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement;
var clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0;
var top = box.top + (self.pageYOffset || docElem.scrollTop) - clientTop, left = box.left + (self.pageXOffset || docElem.scrollLeft) - clientLeft;
return {
left: left,
top: top,
right: left + box.width,
bottom: top + box.height
};
}
};
// 文本框监听事件
var _this = this;
_this.entertext = $(this);
_this.dropdown = $('<ul class="editTips" style="display:none;"></ul>');
// 获取到的弹出的下拉框的位置是绝对定位的坐标,所以得把弹出的层放到body里
$("body").after(_this.dropdown);
_this.dropdown.css({
'width':opts.dropdownWidth,
'position':'absolute',
});
$(this).css({
'position': 'relative', }); // 监听输入框
_this.dropdown.parent().on("keyup",this,function(event){
var nowTime = window.sessionStorage.getItem('nowTime');
// 当前项索引
var n = _this.dropdown.find(".active").index();
// li 个数
var n_max = _this.dropdown.find("li").length;
// 注意 event在 firefox 中不能兼容,在方法中带上event参数,如下声明实现兼容
EVT = event || window.event;
if( EVT.keyCode == 38 ){
// 方向键控制 li 选项
if(n-1>=0){
_this.dropdown.find('li').eq(n-1).addClass("active").siblings().removeClass("active");
}
if( n == 0){
_this.dropdown.find('li').eq(n_max-1).addClass("active").siblings().removeClass("active");
}
return false;
}
// 禁止enter键换行
if( EVT.keyCode == 13 ){
return false;
}
if( EVT.keyCode == 40 ){
// 方向键控制 li 选项
if(n<n_max-1){
_this.dropdown.find('li').eq(n+1).addClass("active").siblings().removeClass("active");
}
if( n+1 == n_max ){
_this.dropdown.find('li').eq(0).addClass("active").siblings().removeClass("active");
}
return false;
}
// 限制请求,输入间隔不超过一秒时不触发检索事件发送请求
if( nowTime){
var oldTime = Date.now();
var nowTime = window.sessionStorage.getItem('nowTime');
var m = parseInt((oldTime - nowTime)/1000);
if( m >= 1){
// 文本内容
var val = _this.entertext.val();
// 以空格切割文本返回数组
var arr_test = val.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-1];
// 切割最后一个元素 单字符元素 返回数组
var temp_type = temp.split("");
// 获取数字第一个字符
var temp_cha = temp_type[0];
// 最后一个元素长度
var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf(".");
// 定义回调函数 callbacktips
var callbacktips = function(arr_json){
// 初始化 UL
_this.dropdown.find("li").remove();
for( i=0;i<arr_json.length;i++ ){
_this.dropdown.append('<li>'+arr_json[i]+'</li>');
};
_this.dropdown.show();
_this.dropdown.find("li:first-child").addClass("active");
// 自定义样式
_this.dropdown.find("li").css({
'width':'100%',
});
};
// 最后一个元素为空值
if( temp_len == 0 ){
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
// 为特定字符 符合触发条件
if( temp_cha == opts.triggerCharacter ){
if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){
// 调用回调函数
callbacktips(arr_json); });
}
}else{
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
}
}
// 初始化第一次时间
window.sessionStorage.setItem('nowTime',Date.now());
}); // 切换当前项
_this.dropdown.on('mouseenter','li',function(){
$(this).addClass("active").siblings().removeClass("active");
});
// 阻止输入过程中 方向键盘的默认事件
_this.entertext.on("keydown",_this.entertext,function(event){
EVT = event || window.event;
if( EVT.keyCode == 38 ){
EVT.preventDefault();
}
if( EVT.keyCode == 40 ){
EVT.preventDefault();
}
// enter键选取对应的 li
if( EVT.keyCode == 13 ){
EVT.preventDefault();
var txt = _this.dropdown.find(".active").html();
var test_txt = _this.entertext.val();
var arr_change = test_txt.split(" ");
// 以空格切割文本返回数组
var arr_test = test_txt.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-1];
var temp_type = temp.split("");
var temp_dot = temp_type.lastIndexOf(".");
var n ;
temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt);
n = temp_type.join('');
arr_change.splice(arr_change.length-1,1,""+n);
_this.entertext.val(arr_change.join(" "));
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
});
// 点击替换 重组val
$(document).on("click",'li',function(){
var txt = $(this).html();
var test_txt = _this.entertext.val();
var arr_change = test_txt.split(" ");
// 以空格切割文本返回数组
var arr_test = test_txt.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-1];
var temp_type = temp.split("");
var temp_dot = temp_type.lastIndexOf(".");
var n ;
temp_type.splice(temp_dot+1,temp_type.length-1-temp_dot,txt);
n = temp_type.join('');
arr_change.splice(arr_change.length-1,1,""+n);
_this.entertext.val(arr_change.join(" "));
_this.dropdown.hide();
_this.dropdown.find('li').remove();
});
// 调用获取坐标方法 show(elem)
$(this).keyup(function(){
show(this);
});
// 调用 kingwolfofsky, 获取光标坐标
function show(elem) {
var p = kingwolfofsky.getInputPositon(elem);
var s = _this.dropdown.get(0);
var ttop = parseInt(_this.entertext.css("marginTop"));
var tleft = parseInt(_this.entertext.css("marginLeft"))
console.log(ttop);
s.style.top = p.bottom-ttop+10+'px';
s.style.left = p.left-tleft + 'px';
}
});
}
});
//默认参数
var defaults = {
triggerCharacter : '$',
dropdownWidth:'150px'
};
})(window.jQuery);

调用插件
对呀,还是上面的调用,没问题的吧~

$("#test").editTips({
triggerCharacter : '$',
dropdownWidth:'150px',
keyPressAction:function(temp,callbacktips){
var arr_json;
if( temp == "$" ){
arr_json = ["$a","$ab","$b","$bb"]
}
if(temp && temp.indexOf("$a")== 0){
arr_json = ["$a","$ab"];
}
else if(temp && temp.indexOf("$b")== 0){
arr_json = ["$b","$bb"];
}
callbacktips(arr_json);
}
});

最后,复制粘贴上面的 html ,css ,js ,调用,它就是你的了~
这次分享就到这里了,欢迎亲们品鉴,有问题可以私信或者留言哦~(偷笑:虽然我不一定看~看了不一定回~回了不一定能解决问题~)
完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>公式测试</title> <style> /* 编辑器下拉框相关样式 */
table,tr,th,td{padding:;margin:;}
ul,li,textarea,input{text-decoration:none;list-style:none;margin:;padding:;box-sizing: border-box;}
input{
outline:none;
}
.editTips{
padding:5px ;
border-radius: !important;
box-shadow: 2px 4px rgba(,,,.);
max-height: auto;
margin:;
z-index:;
}
.editTips li{
text-align:left;
box-sizing:border-box;
display:block;
width:%;
line-height:1.42857143;
margin:1px ;
padding:6px 11px;
color:#;
cursor:pointer;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
font-weight:;
line-height:1.42857143;
border-bottom:solid 1px #e5e5e5;
background:#e5e5e5;
}
.editTips li:last-child{
border-bottom:none;
}
.active{
background:#fee188!important;
}
.editTips li.active{ background:#fee188!important; }
textarea{
text-decoration: none;
list-style: none;
margin: ;
padding: ;
display: block;
box-sizing: border-box;
width: 500px;
height: 300px;
margin-top: 100px;
margin-left: 100px;
} </style> </head>
<body>
<textarea id="test" ></textarea> </body>
</html> <script type="text/javascript" src="./jquery-1.8.3.min.js"></script> <script type="text/javascript"> /*
*****公式编辑器*****
* 调用 editTips()方法
* editTips({
* triggerCharacter: 触发匹配字符 默认为 "$"
* textareaWidth: 输入框宽度 默认 auto
* textareaHeight: 输入框高度 默认 auto
* dropdownWidth: 下拉提示框宽度 默认150px
* keyPressAction:function(temp,callbacktips){
* // 参数为temp 返回 arr_json 数组 调用回调函数 callbacktips(arr_json)
* var arr_json;
* callbacktips(arr_json);
* }
* });
*
*/ (function ($) {
$.fn.extend({
"editTips": function (options) {
var opts = $.extend({}, defaults, options); //使用jQuery.extend 覆盖插件默认参数
return this.each(function () { //这里的this 就是 jQuery对象
// 获取输入光标在页面中的坐标 返回left和top,bottom
var kingwolfofsky = {
getInputPositon: function (elem) {
if (document.selection) { //IE Support
elem.focus();
var Sel = document.selection.createRange();
return {
left: Sel.boundingLeft,
top: Sel.boundingTop,
bottom: Sel.boundingTop + Sel.boundingHeight
};
} else {
var that = this;
var cloneDiv = '{$clone_div}', cloneLeft = '{$cloneLeft}', cloneFocus = '{$cloneFocus}', cloneRight = '{$cloneRight}';
var none = '<span style="white-space:pre-wrap;"> </span>';
var div = elem[cloneDiv] || document.createElement('div'), focus = elem[cloneFocus] || document.createElement('span');
var text = elem[cloneLeft] || document.createElement('span');
var offset = that._offset(elem), index = this._getFocus(elem), focusOffset = { left: , top: }; if (!elem[cloneDiv]) {
elem[cloneDiv] = div, elem[cloneFocus] = focus;
elem[cloneLeft] = text;
div.appendChild(text);
div.appendChild(focus);
document.body.appendChild(div);
focus.innerHTML = '|';
focus.style.cssText = 'display:inline-block;width:0px;overflow:hidden;z-index:-100;word-wrap:break-word;word-break:break-all;';
div.className = this._cloneStyle(elem);
div.style.cssText = 'visibility:hidden;display:inline-block;position:absolute;z-index:-100;word-wrap:break-word;word-break:break-all;overflow:hidden;';
};
div.style.left = this._offset(elem).left + "px";
div.style.top = this._offset(elem).top + "px";
var strTmp = elem.value.substring(, index).replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br/>').replace(/\s/g, none);
text.innerHTML = strTmp; focus.style.display = 'inline-block';
try { focusOffset = this._offset(focus); } catch (e) { };
focus.style.display = 'none';
return {
left: focusOffset.left,
top: focusOffset.top,
bottom: focusOffset.bottom
};
}
},
// 克隆元素样式并返回类
_cloneStyle: function (elem, cache) {
if (!cache && elem['${cloneName}']) return elem['${cloneName}'];
var className, name, rstyle = /^(number|string)$/;
var rname = /^(content|outline|outlineWidth)$/; //Opera: content; IE8:outline && outlineWidth
var cssText = [], sStyle = elem.style; for (name in sStyle) {
if (!rname.test(name)) {
val = this._getStyle(elem, name);
if (val !== '' && rstyle.test(typeof val)) { // Firefox 4
name = name.replace(/([A-Z])/g, "-$1").toLowerCase();
cssText.push(name);
cssText.push(':');
cssText.push(val);
cssText.push(';');
};
};
};
cssText = cssText.join('');
elem['${cloneName}'] = className = 'clone' + (new Date).getTime();
this._addHeadStyle('.' + className + '{' + cssText + '}');
return className;
}, // 向页头插入样式
_addHeadStyle: function (content) {
var style = this._style[document];
if (!style) {
style = this._style[document] = document.createElement('style');
document.getElementsByTagName('head')[].appendChild(style);
};
style.styleSheet && (style.styleSheet.cssText += content) || style.appendChild(document.createTextNode(content));
},
_style: {}, // 获取最终样式
_getStyle: 'getComputedStyle' in window ? function (elem, name) {
return getComputedStyle(elem, null)[name];
} : function (elem, name) {
return elem.currentStyle[name];
},
// 获取光标在文本框的位置
_getFocus: function (elem) {
var index = ;
if (document.selection) {// IE Support
elem.focus();
var Sel = document.selection.createRange();
if (elem.nodeName === 'TEXTAREA') {//textarea
var Sel2 = Sel.duplicate();
Sel2.moveToElementText(elem);
var index = -;
while (Sel2.inRange(Sel)) {
Sel2.moveStart('character');
index++;
};
}
else if (elem.nodeName === 'INPUT') {// input
Sel.moveStart('character', -elem.value.length);
index = Sel.text.length;
}
}
else if (elem.selectionStart || elem.selectionStart == '') { // Firefox support
index = elem.selectionStart;
}
return (index);
}, // 获取元素在页面中位置
_offset: function (elem) {
var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement;
var clientTop = docElem.clientTop || body.clientTop || , clientLeft = docElem.clientLeft || body.clientLeft || ;
var top = box.top + (self.pageYOffset || docElem.scrollTop) - clientTop, left = box.left + (self.pageXOffset || docElem.scrollLeft) - clientLeft;
return {
left: left,
top: top,
right: left + box.width,
bottom: top + box.height
};
}
};
// 文本框监听事件
var _this = this;
_this.entertext = $(this);
_this.dropdown = $('<ul class="editTips" style="display:none;"></ul>');
// 获取到的弹出的下拉框的位置是绝对定位的坐标,所以得把弹出的层放到body里
$("body").after(_this.dropdown);
_this.dropdown.css({
'width':opts.dropdownWidth,
'position':'absolute',
});
$(this).css({
'position': 'relative', }); // 监听输入框
_this.dropdown.parent().on("keyup",this,function(event){
var nowTime = window.sessionStorage.getItem('nowTime');
// 当前项索引
var n = _this.dropdown.find(".active").index();
// li 个数
var n_max = _this.dropdown.find("li").length;
// 注意 event在 firefox 中不能兼容,在方法中带上event参数,如下声明实现兼容
EVT = event || window.event;
if( EVT.keyCode == ){
// 方向键控制 li 选项
if(n->=){
_this.dropdown.find('li').eq(n-).addClass("active").siblings().removeClass("active");
}
if( n == ){
_this.dropdown.find('li').eq(n_max-).addClass("active").siblings().removeClass("active");
}
return false;
}
// 禁止enter键换行
if( EVT.keyCode == ){
return false;
}
if( EVT.keyCode == ){
// 方向键控制 li 选项
if(n<n_max-){
_this.dropdown.find('li').eq(n+).addClass("active").siblings().removeClass("active");
}
if( n+ == n_max ){
_this.dropdown.find('li').eq().addClass("active").siblings().removeClass("active");
}
return false;
}
// 限制请求,输入间隔不超过一秒时不触发检索事件发送请求
if( nowTime){
var oldTime = Date.now();
var nowTime = window.sessionStorage.getItem('nowTime');
var m = parseInt((oldTime - nowTime)/);
if( m >= ){
// 文本内容
var val = _this.entertext.val();
// 以空格切割文本返回数组
var arr_test = val.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-];
// 切割最后一个元素 单字符元素 返回数组
var temp_type = temp.split("");
// 获取数字第一个字符
var temp_cha = temp_type[];
// 最后一个元素长度
var temp_len = temp.length; var temp_dot = temp_type.lastIndexOf(".");
// 定义回调函数 callbacktips
var callbacktips = function(arr_json){
// 初始化 UL
_this.dropdown.find("li").remove();
for( i=;i<arr_json.length;i++ ){
_this.dropdown.append('<li>'+arr_json[i]+'</li>');
};
_this.dropdown.show();
_this.dropdown.find("li:first-child").addClass("active");
// 自定义样式
_this.dropdown.find("li").css({
'width':'100%',
});
};
// 最后一个元素为空值
if( temp_len == ){
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
// 为特定字符 符合触发条件
if( temp_cha == opts.triggerCharacter ){
if($.isFunction(opts.keyPressAction)){ opts.keyPressAction(temp, function(arr_json){
// 调用回调函数
callbacktips(arr_json); });
}
}else{
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
}
}
// 初始化第一次时间
window.sessionStorage.setItem('nowTime',Date.now());
}); // 切换当前项
_this.dropdown.on('mouseenter','li',function(){
$(this).addClass("active").siblings().removeClass("active");
});
// 阻止输入过程中 方向键盘的默认事件
_this.entertext.on("keydown",_this.entertext,function(event){
EVT = event || window.event;
if( EVT.keyCode == ){
EVT.preventDefault();
}
if( EVT.keyCode == ){
EVT.preventDefault();
}
// enter键选取对应的 li
if( EVT.keyCode == ){
EVT.preventDefault();
var txt = _this.dropdown.find(".active").html();
var test_txt = _this.entertext.val();
var arr_change = test_txt.split(" ");
// 以空格切割文本返回数组
var arr_test = test_txt.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-];
var temp_type = temp.split("");
var temp_dot = temp_type.lastIndexOf(".");
var n ;
temp_type.splice(temp_dot+,temp_type.length--temp_dot,txt);
n = temp_type.join('');
arr_change.splice(arr_change.length-,,""+n);
_this.entertext.val(arr_change.join(" "));
_this.dropdown.hide();
_this.dropdown.find('li').remove();
}
});
// 点击替换 重组val
$(document).on("click",'li',function(){
var txt = $(this).html();
var test_txt = _this.entertext.val();
var arr_change = test_txt.split(" ");
// 以空格切割文本返回数组
var arr_test = test_txt.split(" ");
// 获取数组最后一个元素
var temp = arr_test[arr_test.length-];
var temp_type = temp.split("");
var temp_dot = temp_type.lastIndexOf(".");
var n ;
temp_type.splice(temp_dot+,temp_type.length--temp_dot,txt);
n = temp_type.join('');
arr_change.splice(arr_change.length-,,""+n);
_this.entertext.val(arr_change.join(" "));
_this.dropdown.hide();
_this.dropdown.find('li').remove();
});
// 调用获取坐标方法 show(elem)
$(this).keyup(function(){
show(this);
});
// 调用 kingwolfofsky, 获取光标坐标
function show(elem) {
var p = kingwolfofsky.getInputPositon(elem);
var s = _this.dropdown.get();
var ttop = parseInt(_this.entertext.css("marginTop"));
var tleft = parseInt(_this.entertext.css("marginLeft"))
console.log(ttop);
s.style.top = p.bottom-ttop++'px';
s.style.left = p.left-tleft + 'px';
}
});
}
});
//默认参数
var defaults = {
triggerCharacter : '$',
dropdownWidth:'150px'
};
})(window.jQuery); //调用,还是上面的调用
$("#test").editTips({
triggerCharacter : '$',
dropdownWidth:'150px',
keyPressAction:function(temp,callbacktips){
var arr_json;
if( temp == "$" ){
arr_json = ["$a","$ab","$b","$bb 副科级"]
}
if(temp && temp.indexOf("$a")== ){
arr_json = ["$a","$ab"];
}
else if(temp && temp.indexOf("$b")== ){
arr_json = ["$b","$bb 这个是说明"];
}
callbacktips(arr_json);
}
}); </script>
如果想使用的时候显得高大上的感觉,其中的不足和高级功能需要你自己去补充了。
出处:http://www.cnblogs.com/wbsndbf/p/7976300.html
JS实现动态提示框的更多相关文章
- 修改js confirm alert 提示框文字的简单实例
修改js confirm alert 提示框文字的简单实例: <!DOCTYPE html> <html> <head lang="en"> & ...
- 【转】提示框第三方库之MBProgressHUD iOS toast效果 动态提示框效果
原文网址:http://www.zhimengzhe.com/IOSkaifa/37910.html MBProgressHUD是一个开源项目,实现了很多种样式的提示框,使用上简单.方便,并且可以对显 ...
- JS类百度的动态提示框思路及完成
参考的代码来自这里: http://www.jb51.net/article/28075.htm 不过说实话,这个网站太烂了,不适合看代码,另外写代码的人是个大牛,但是却没有模块化思想,所以朕不高兴直 ...
- 【JS学习笔记】第一个JS效果——鼠标提示框
分析效果实现原理--鼠标提示框 样式:div的display 事件:onmouseover,onmouseout 编写JS的流程 布局:HTML+CSS 属性:确定需要修改哪些属性 事件:确定用户做哪 ...
- js实现短暂提示框
业务场景:当鼠标移入某元素时,显示提示框进行介绍.当鼠标移除时,会自动消失.引入ToolTip.js和ToolTip.css 主方法:ToolTip.show(需要提示的元素id, 随意不重复即可, ...
- 有关js弹出提示框几种方法
1直接提示只有确定功能的提示框 只显示提示信息 alert(“提示信息”); alert ();的参数只有一个就是提示信息,无返回值 2 弹出输入框让你输入内容 prompt() ; 有两个参数:第一 ...
- 原生js实现tooltip提示框的效果
在js的世界里面,每一个小的特效都那么微不足道,却又那么的令人向往与好奇.前端工程师的任务特别高大上,因为他们的一个小小的设计就会激发别人的求知欲.比如说我,只是随机一瞟,便看到了这个tooltip的 ...
- JS实现动态提示文本框可输入剩余字数(类似发表微博数字提示)
一.实现效果: 为了更直观的体现用户在文本框输入文本时能看到自己输入了多少字,项目中需要通过判断提示文本框剩余可输入字数. html & JS: <div> <textare ...
- 弹出框四 之toastr.js (完成提示框)
1.下载 toastr.js组件 2. $(function () { toastr.success('提交数据成功'); toastr.error('Error'); toastr.warning( ...
随机推荐
- Lograge(2350✨) 在产品环境显示定制改良的日志输出。
Lograge https://github.com/roidrage/lograge 改良Rails默认的请求日志的记录. 它会精明的处理好那些noisy和无用的,未解析的,在context中运行多 ...
- 我的 VSCode 常用扩展
Beautify (option+shift+F) Bookmarks (option+option+k,l,j) Debugger for Chrome Docker EditorConfig fo ...
- 【Python】装饰器理解
以下文章转载自:点这里 关于装饰器相关的帖子记录在这里: 廖雪峰, thy专栏, stackflow Python的函数是对象 简单的例子: def shout(word="yes" ...
- 【hive】where使用注意的问题
不能再where后边使用别名,group by后边也一样不能使用别名 select id,col1 - col2 from table1 where (col1 - col2) > 1000; ...
- win10下安装VS2005运行程序出现0x000007b错误的解决方法
项目工程一运行就报错...真心坑... 方法如下: 1.安装DirectX 9.0c 形成原因是因为DirectX 9.0被损坏, 只需要安装即可. 如果有电脑管家的.在电脑管家里面搜索“Direct ...
- C++面向对象高级编程(一)基础篇
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 概要: 知识点1 构造函数与析构函数 知识点2 参数与返回值 知识点3 const 知识点4 函数重载(要与重写区分开) 知识点5 友元 先以C ...
- a的样式
.myAucCItem a { color: rgb(71,71,71);} .myAucCItem a:hover { color: rgb(71,71,71); text-decoration: ...
- About libcurl and cURL in PHP
今天在学习php时遇到要调用curl 库函数对特定url字符串进行访问操作,需要自己写一个方法进行调用,之前在linux系统中也有用到cURL 命令行工具执行对相关资源的获取,在wiki上找到了如下的 ...
- Cannot mix incompatible Qt library (version 0x40805) with this library (version 0x40801)
问题描述 今天运行我的 linux 上的 go 语言 IDE liteide 突然报错,错误如下: Cannot mix incompatible Qt library (version 0x4080 ...
- 高并发异步uwsgi+web.py+gevent
为什么用web.py? python的web框架有很多,比如webpy.flask.bottle等,但是为什么我们选了webpy呢?想了好久,未果,硬要给解释,我想可能原因有两个:第一个是兄弟项目组用 ...