js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标


Posted in Javascript onJanuary 04, 2018

引言

前段时间发了一个编辑器的插件,忙完后自己再次进行了详细的测试,然后心里冒出一句:“这谁写的这么奇葩的插件?完全没什么luan用啊!”

自己做了让自己不满意的事,咋整?男人不怕累,花了时间重写(为世界上所有像我一样勤劳的男人点赞)~

思维导图

在小生看来,在开发每一个新功能的时候都应该做到心中有一张思维导图:功能实现逻辑和实现功能大致的方法。当然我们不可能在还没动手

前就考虑得面面俱到,但在正式开发之前心里对整个流程有个清晰的印象肯定会让我们在动手时愈加流畅(喝口娃哈哈美滋滋,看图~):

js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标

流程效果图

js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标

触发检索事件字符可自定义,默认为 $,输入 $ 触发检索显示,此时检索值为空,所以显示所有选项,继续输入 a ,检索值为 a,显示匹配选项,当再输入 . 时, 检索值获取条件发生改变(具体我们等下看代码),

图四中为整个流程在控制台中的记录。

js代码 -- 监听输入框

 全局变量

 考虑到里面小方法比较多,为了简化代码,这里我选择模块化一下,需要用到以下全局变量。这里特别提一下:持续事件和点事件的区别,持续顾名思义,持续事件就是一直触发的事件,这里 $ 触发检索事件后,检索值 selectVal

 是变化的,但是我们又不需要它一直处于触发状态,怎么办呢?对,开关,我们可以给这个事件设置一个开关,条件满足时打开开关,事件持续触发,结束后关闭开关,结束检索事件,这里设置的开关是:searchStart;而点事件

 这里就是输入 . 时触发的事件,它只需要在输入 . 时获取相关的值就行了,不需要连续触发,这里我们设置参数 enterCharacter : 当前输入的字符

var _this = $(this);
var e = event || window.event; // 键值兼容
var searchStart = false; // 设置检索事件开关
var checkCharacter = false; // 输入字符检索开关  
var oldCurrentPos = ''; // 检索值开始的位置
var currentPos = ''; // 检索值结束的位置
var selectVal = ''; // 检索值
var pos = ''; // 设置光标位置
var enterCharacter = ''; // 当前输入的字符
var dotVal; // 输入 . 时从0到当前光标位置文本
var dotDollerPos; // 获取往后查找离 . 最近的 $ 的下标,引文输入 . 时的检索值即dotSelectVal不包含 $ 本身,所以需要加1 
var dotSelectVal; // 输入 . 时的检索值

  插入输入框 

  首先插入下拉框,当然留到后面插入也可以(你开心你说什么都是对的),但是这里有个点需要注意一下:为什么选择插入在body下?因为我们获取到的下拉框的位置是绝对定位坐标。

// 插入下拉框
 _this.dropdown = $('<ul class="editTips" style="display:none;"></ul>');
// 获取到的弹出的下拉框的位置是绝对定位的坐标,所以得把弹出的层放到$("body").after(_this.dropdown);   
 _this.dropdown.css({
 'width':opts.dropdownWidth,
 'position':'absolute',
 });
 _this.css({
 'position': 'relative',
 });

 注意:这里我们提一下,要获取检索值,即 selectVal,我们需要知道事件触发时光标所在的位置,即 oldCurrentPos,以及光标当前位置 currentPos,有了这两个 下标,我们才能动态获取 selectVal

 获取光标当前位置

  关于获取输入框光标以及获取值等方法,不了解的朋友可以去看一下 range 方法,当然无数前辈已经做过无数归纳总结讲解(向前辈们敬礼~):

// 获取当前光标位置 currentPos
  var getStart =function() {  
   var all_range = '';
   if (navigator.userAgent.indexOf("MSIE") > -1) { //IE
   if( _this.get(0).tagName == "TEXTAREA" ){ 
    // 根据body创建textRange
    all_range = document.body.createTextRange();
    // 让textRange范围包含元素里所有内容
    all_range.moveToElementText(_this.get(0));
   } else {
    // 根据当前输入元素类型创建textRange
    all_range = _this.get(0).createTextRange();
   }
   // 输入元素获取焦点
   _this.focus();
   // 获取当前的textRange,如果当前的textRange是一个具体位置而不是范围,textRange的范围从currentPos到end.此时currentPos等于end
   var cur_range = document.selection.createRange();
   // 将当前的textRange的end向前移"选中的文本.length"个单位.保证currentPos=end
   cur_range.moveEnd('character',-cur_range.text.length)
   // 将当前textRange的currentPos移动到之前创建的textRange的currentPos处, 此时当前textRange范围变为整个内容的currentPos处到当前范围end处
   cur_range.setEndPoint("StartToStart",all_range);

   // 此时当前textRange的Start到End的长度,就是光标的位置
   currentPos = cur_range.text.length;
   } else {
   // 文本框获取焦点
   _this.focus();
   // 获取当前元素光标位置
   currentPos = _this.get(0).selectionStart;
   //console.log("光标当前位置:"+currentPos);
   }
   // 返回光标位置
   return currentPos;
  };

               

获取检索值开始位置

检索开始位置,即事件触发时光标所在位置,直白来说,就是把事件触发时光标所在位置 currentPos 赋值给 oldCurrentPos 储存起来,然后与新的 currentPos 组

成的区域 (oldCurrentPos,currentPos)就是我们检索值所在区域 

               

// 获取检索值开始位置 oldCurrentPos
  var getOldCurrentPos = function(){
   getStart(); // 开始输入的时候的光标位置 currentPos
   oldCurrentPos = currentPos; // 储存输入开始位置
   console.log(oldCurrentPos);
  }

设置光标位置

选择当前项重组输入框 value 值后光标是默认显示在最后的,这当然不符合我们的开发需求,我们想要的效果是事件结束时光标能在我们编辑结束的位置(关于value值重组我们在下面的方法中再看)               

// 设置光标位置
  var setCarePosition = function(start,end) {
   if(navigator.userAgent.indexOf("MSIE") > -1){
   var all_range = '';
   if( _this.get(0).tagName == "TEXTAREA" ){ 
    // 根据body创建textRange
    all_range = document.body.createTextRange();
    // 让textRange范围包含元素里所有内容
    all_range.moveToElementText(_this.get(0));
   } else {
    // 根据当前输入元素类型创建textRange
    all_range = _this.get(0).createTextRange();
   }
   _this.focus();
   // 将textRange的start设置为想要的start
   all_range.moveStart('character',start);
   // 将textRange的end设置为想要的end. 此时我们需要的textRange长度=end-start; 所以用总长度-(end-start)就是新end所在位置
   all_range.moveEnd('character',-(all_range.text.length-(end-start)));
   // 选中从start到end间的文本,若start=end,则光标定位到start处
   all_range.select();
   }else{
   // 文本框获取焦点
   _this.focus();
   // 选中从start到end间的文本,若start=end,则光标定位到start处
   _this.get(0).setSelectionRange(start,end);
   }
  };

结束检索事件

在结束检索事件中我们需要初始化下拉框以及关闭开关,这里需要将该方法声明在获取检索值方法前面,因为获取值后整个事件流程结束,我们需要初始化变量为下一次事件触发做好准备             

// 结束检索事件
  var endSearch = function(){
   _this.dropdown.find("li").remove(); // 移除下拉框中的选项
   _this.dropdown.hide(); // 隐藏下拉框
   searchStart = false; // 初始化检索开关 searchStart
   enterCharacter=''; // 初始化当前字符
  }

 

获取检索的值

看下方代码,我们能够获取值的前提是 searchStart 开关 打开状态,这里我们为了保持插件的灵活性,将触发字符设置为变量,这里默认为 $ 和 . ,enterCharacter为当前输入的字符,

        因为当我们输入 . 时,selectVal 的获取规则会改变,所以这里我们需要将 selectVal 获取方式区分开来,注意:这里我们要考虑到存在一个操作 -- 回删,输入 $,下拉框出来了,但是我

        们又觉得此处 $ 出现得还不是时候(反正就是要删),删除 $,那么检索事件也就结束,初始化相关变量。当输入的是 . 时,如果要替换值,那么我们需要的获取从 . 在的位置往后找

到离 . 最近的 $ 符号,得到其在文本中的位置,这样我们才能重组 value            

// 获取检索的值 selctVal
  var getSelectVal = function(){
   var val = _this.val();
   if( searchStart == true && enterCharacter != opts.levelCharacter ){ // 当输入的是字符 triggerCharacter 的时候 默认为 $
   selectVal = val.substring(oldCurrentPos,currentPos); // 检索值直接为获取的文本区域
   }
   if( searchStart == true && enterCharacter == opts.levelCharacter ){ // 当输入的是字符 levelCharacter 的时候 默认为 .
   dotVal = val.slice(0,currentPos);
   dotDollerPos = dotVal.lastIndexOf(opts.triggerCharacter)+1;
   dotSelectVal = dotVal.substring(dotDollerPos,currentPos);
   selectVal = dotSelectVal;
   console.log("到当前下标的字符串为:"+dotVal);
   console.log("到当前下标最近的$下标是:"+dotDollerPos);
   console.log("输入 . 时检索值为:"+dotSelectVal);
   }  
   console.log("获取的值区域为:"+oldCurrentPos+"-"+currentPos);
   if( oldCurrentPos > currentPos ){ // 回删时清除选项li 隐藏下拉框
   endSearch()
   }  
  }

改变输入框 value 值,定位光标位置

因为我们这里存在两种选择方式,鼠标点击和按 enter 键,两者的区别只在于执行事件的方式,将同样的代码写两遍未免有点不美,这里我们将它摘出来

注意:此处需要区分触发检索事件的符号是 $ 还是 . ,因为符号不同,我们获取的值是不同的,光标定位也是不同            

// 选中li当前项 改变输入框value值 定位光标
  var changeValue = function(){
   var val = _this.val(); 
   var liTxt = _this.dropdown.find(".active").text();
   var liTxtLength = liTxt.length;
   var valLength = val.length;
   // 此处需要区分触发检索事件的符号是
   if( enterCharacter == opts.levelCharacter ){ // 如果是 .
   var beforeSelectVal = val.substring(0,dotDollerPos);  
   }
   else{ // 如果是 &
   var beforeSelectVal = val.substring(0,oldCurrentPos);
   }
   var beforeSelectValLength = beforeSelectVal.length;
   var afterSelectVal = val.substring(currentPos,valLength);
   var pos = liTxtLength + beforeSelectValLength;
   val = beforeSelectVal+liTxt+afterSelectVal;
   _this.val(val);
   setCarePosition(pos,pos); // 将光标定位在插入值后面
   endSearch();
   console.log("文本长度:"+beforeSelectVal.length);
   console.log("li文本为:"+liTxt);
   console.log("前部为:"+beforeSelectVal);
   console.log("后部分为:"+afterSelectVal);
   return false; // 此处必须加上return false 不然会调用callbacktips 初始化 dropdown
  }

定义回调函数

    获取检索值之后就需要发送请求了,我们拿到返回的数组 arr_json 后,将其遍历生成 li 添加到下拉框中              

// 定义回调函数 callbacktips
  var callbacktips = function(arr_json){
   // 初始化 UL 
   _this.dropdown.find("li").remove();
   if( arr_json ){
   for( i=0;i<arr_json.length;i++ ){
    var n = arr_json[i].indexOf(selectVal);
    if( n != -1 ){
    _this.dropdown.append('<li>'+arr_json[i]+'</li>'); 
    }else{
    return;
    }     
   };
   }   
   _this.dropdown.show();
   _this.dropdown.find("li:first-child").addClass("active");
   // 自定义样式
   _this.dropdown.find("li").css({ 
   'width':'100%',
   });
  };

获得焦点时获取光标位置

这里我们直接调用上面的方法就行了

 

// 获得焦点的时候获取光标位置
  _this.click(function(){
   getOldCurrentPos()
  });

阻止键盘默认事件

这里我们需要判断下拉框的状态:显示还是隐藏

 //下拉框显示时 阻止键盘方向键默认事件
  _this.keydown(function(e){
   var dropdownIsshow = _this.dropdown.css("display");
   if( dropdownIsshow == "block" ){
   if( e.keyCode == 38 || e.keyCode == 40 || e.keyCode == 13 ){
    e.preventDefault();
   }
   }
  })

keyup 事件

通过keyuo事件:”我们能实时监听输入框;也能通过按键切换当前项以及改变光标位置;也能限制输入字符范围,比如这里:当输入某些字符时,将会被认为输入了不合法字符而终止检索事件;

我们的事件开关也是通过该事件能改变其状态的以及 enter 键选取当前项

// 监听输入框value值变化
  _this.keyup(function(e){
   var val = _this.val(); 
   // 当前项索引
   var n = _this.dropdown.find(".active").index();
   // li 个数
   var n_max = _this.dropdown.find("li").length;  
   getStart(); // 获得最新光标位置
   // 方向键控制 li 选项
   if( e.keyCode == 38 ){   
   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; // 此处必须加上return false 不然会重复初始化
   }  
   if( e.keyCode == 40 ){
   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; // 此处必须加上return false 不然会重复初始化
   } 
   if( e.keyCode != 37 && e.keyCode != 38 && e.keyCode != 39 && e.keyCode != 40 ){
   var reg = new RegExp("[`~!@#^&*()=|{}':;',\\[\\]<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]");
   enterCharacter = val.substring(currentPos-1,currentPos); // 当前输入的字符
   //console.log(enterCharacter);
   if( reg.test(enterCharacter) == false && enterCharacter != " "){ // 输入的字符合法 可以执行检索事件
    //console.log("输入字符合法");
    checkCharacter = true;
   }else{
    checkCharacter = false;
    endSearch()
    console.log("输入了不合法字符");
    //console.log(selectVal);   
   }   
   }   
   console.log("当前输入的字符是:"+enterCharacter);
   if( enterCharacter == opts.triggerCharacter || enterCharacter == opts.levelCharacter){
   console.log("输入了$或者.");
   // 输入了 $,打开开关,允许检索事件执行
   searchStart = true;
   getOldCurrentPos(); // 输入 $ 的时候重置 oldCurrentPos
   }
   getSelectVal(); // 外度调用获取检索值方法 保证实时更新 selectVal 及 searchStart
   if( searchStart == true && checkCharacter == true && e.keyCode != 13 ){
   console.log("获取的值:"+selectVal);
   if( $.isFunction(opts.keyPressAction) ){   
    opts.keyPressAction(selectVal, function(arr_json){
    // 调用回调函数
    callbacktips(arr_json);   
    });
   }
   }
   if( e.keyCode == 13 ){ // 按enter键选取当前li文本值 重组输入框 value值
   var dropdownIsshow = _this.dropdown.css("display");
   if( dropdownIsshow == "block" ){ // 为了在下拉框隐藏时按 enter键 能换行,需要加上这个判断
    changeValue();
    console.log("这是点击enter后searchStart:"+searchStart);
   }
   }
   console.log("这是整个事件执行完成以后:"+searchStart);
  });

鼠标滑入切换当前项

 

 // 切换当前项
  _this.dropdown.on('mouseenter','li',function(){
   $(this).addClass("active").siblings().removeClass("active");
  });

点击选取当前项 失去焦点事件

这里采用了 event.target 方法来获得事件源,如果是 下拉框中的 li ,则执行 changeValue() 方法,否则结束检索事件 endSearch()

 

// 点击当前项获取文本值 重组输入框 value值 失去焦点时隐藏下拉框 清空下拉框
  $(document).click(function(e){
   var e = event || window.event;
   var el = e.target.localName; // 获取事件源 标签名
   el == "li" ? changeValue() : endSearch();
   //console.log(el);
  })

js代码 -- 动态获取光标位置

这个方法是借鉴一位前辈的,这里附上原文地址(前辈大善):http://blog.csdn.net/kingwolfofsky/article/details/6586029

/*********以下为获取下拉框像素坐标方法*********/ 
  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 
   }; 
   } 
  };

调用获取坐标方法

  // 调用获取坐标方法 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.css("marginTop"));
   var tleft = parseInt(_this.css("marginLeft"));
   s.style.top = p.bottom-ttop+10+'px'; 
   s.style.left = p.left-tleft + 'px';   
  }

js代码 -- 设置默认参数

var defaults = { 
 triggerCharacter : '$', // 默认触发事件 字符
 levelCharacter: '.', // 默认多层检索触发字符
 dropdownWidth:'150px' // 下拉框默认宽度
 };

js代码 -- 插件调用

此处只为展示效果 在 keyPressAction 中能自定义匹配规则进行拓展

 

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

由于代码比较多,这里就不展示所有代码了,最终效果图:

js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标

在此附上demo下载链接:

不管你信不信,我已经设置了下载口令,亲们必须在心里说出我的一个优点才能点击下载~

总结

以上所述是小编给大家介绍的js 公式编辑器 - 自定义匹配规则 - 带提示下拉框 - 动态获取光标像素坐标,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript高级程序设计
Dec 29 Javascript
js操作textarea方法集合封装(兼容IE,firefox)
Feb 22 Javascript
jQuery插件datepicker 日期连续选择
Jun 12 Javascript
快速学习jQuery插件 jquery.validate.js表单验证插件使用方法
Dec 01 Javascript
深入php面向对象、模式与实践
Feb 16 Javascript
深入浅析vue组件间事件传递
Dec 29 Javascript
Angular搜索场景中使用rxjs的操作符处理思路
May 30 Javascript
浅谈webpack devtool里的7种SourceMap模式
Jan 14 Javascript
使用React手写一个对话框或模态框的方法示例
Apr 25 Javascript
微信公众号获取用户地理位置并列出附近的门店的示例代码
Jul 25 Javascript
微信小程序实现弹幕墙(祝福墙)
Nov 18 Javascript
前端如何实现动画过渡效果
Feb 05 Javascript
浅谈vuex 闲置状态重置方案
Jan 04 #Javascript
详解Angular5 服务端渲染实战
Jan 04 #Javascript
JavaScript中的高级函数
Jan 04 #Javascript
Three.js 再探 - 写一个微信跳一跳极简版游戏
Jan 04 #Javascript
JS实现带导航城市列表以及输入搜索功能
Jan 04 #Javascript
微信小程序实现的贪吃蛇游戏【附源码下载】
Jan 03 #Javascript
详解Angular2学习笔记之Html属性绑定
Jan 03 #Javascript
You might like
PHP 字符串分割和比较
2009/10/06 PHP
PHP中的integer类型使用分析
2010/07/27 PHP
一个PHP针对数字的加密解密类
2014/03/20 PHP
codeigniter中view通过循环显示数组数据的方法
2015/03/20 PHP
Zend Framework框架中实现Ajax的方法示例
2017/06/27 PHP
表单项的name命名为submit、reset引起的问题
2007/12/22 Javascript
JQuery开发的数独游戏代码
2010/10/29 Javascript
从零开始学习jQuery (十) jQueryUI常用功能实战
2011/02/23 Javascript
jquery数组之存放checkbox全选值示例代码
2013/12/20 Javascript
js onmousewheel事件多次触发问题解决方法
2014/10/17 Javascript
Jquery中$.post和$.ajax的用法小结
2015/04/28 Javascript
JQuery中DOM事件绑定用法详解
2015/06/13 Javascript
js实现点击向下展开的下拉菜单效果代码
2015/09/01 Javascript
JavaScript中this的9种应用场景及三种复合应用场景
2015/09/12 Javascript
Jquery和angularjs获取check框选中的值的方法汇总
2016/01/17 Javascript
javascript数组去重方法分析
2016/12/15 Javascript
JavaScript ES6中export、import与export default的用法和区别
2017/03/14 Javascript
highcharts.js数据绑定方式代码实例
2019/11/13 Javascript
element-ui点击查看大图的方法示例
2020/12/14 Javascript
[03:11]2014DOTA2国际邀请赛-VG掉入败者组 独家专访357
2014/07/19 DOTA
Python内存映射文件读写方式
2020/04/24 Python
孕妇装中的著名品牌:Isabella Oliver(伊莎贝拉·奥利弗)
2016/10/31 全球购物
亚马逊印度站:Amazon.in
2017/10/15 全球购物
匡威荷兰官方网站:Converse荷兰
2018/10/24 全球购物
英国百年闻名的优质健康产品连锁店:Holland & Barrett
2019/12/19 全球购物
The North Face官方旗舰店:美国著名户外品牌
2020/09/28 全球购物
任课老师推荐信范文
2013/11/24 职场文书
2014年迎新年活动方案
2014/02/19 职场文书
工艺工程师岗位职责
2014/03/04 职场文书
群众路线教育实践活动心得体会
2014/03/07 职场文书
调查研究项目计划书
2014/04/29 职场文书
党委班子纠正“四风”问题整改措施
2014/10/28 职场文书
《雪地里的小画家》教学反思
2016/02/16 职场文书
如何使用分区处理MySQL的亿级数据优化
2021/06/18 MySQL
排查MySQL生产环境索引没有效果
2022/04/11 MySQL
MySql数据库触发器使用教程
2022/06/01 MySQL