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 相关文章推荐
jquery 插件开发 extjs中的extend用法小结
Jan 04 Javascript
jquery Mobile入门—外部链接切换示例代码
Jan 08 Javascript
jQuery修改li下的样式以及li下的img的src的值的方法
Nov 02 Javascript
SuperSlide标签切换、焦点图多种组合插件
Mar 14 Javascript
手机端图片缩放旋转全屏查看PhotoSwipe.js插件实现
Aug 25 Javascript
微信小程序 使用腾讯地图SDK详解及实现步骤
Feb 28 Javascript
jquery DataTable实现前后台动态分页
Jun 17 jQuery
vue使用$emit时,父组件无法监听到子组件的事件实例
Feb 26 Javascript
javascript将非数值转换为数值
Sep 13 Javascript
vue源码nextTick使用及原理解析
Aug 13 Javascript
JS实现烟花爆炸效果
Mar 10 Javascript
js实现盒子拖拽动画效果
Aug 09 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 模板高级篇总结
2006/12/21 PHP
Php Mssql操作简单封装支持存储过程
2009/12/11 PHP
PHPMailer邮件发送的实现代码
2013/05/04 PHP
扩展Jquery插件处理mouseover时内部有子元素时发生样式闪烁
2011/12/08 Javascript
JS小功能(setInterval实现图片效果显示时间)实例代码
2013/11/28 Javascript
使用JQuery快速实现Tab的AJAX动态载入(实例讲解)
2013/12/11 Javascript
JavaScript制作的可折叠弹出式菜单示例
2014/04/04 Javascript
TypeScript 中接口详解
2015/06/19 Javascript
详解javascript高级定时器
2015/12/31 Javascript
更高效的使用JQuery 这里总结了8个小技巧
2016/04/13 Javascript
基于jQuery实现仿微博发布框字数提示
2016/07/27 Javascript
JavaScript之排序函数_动力节点Java学院整理
2017/06/30 Javascript
详解NODEJS的http实现
2018/01/04 NodeJs
基于vue中keep-alive缓存问题的解决方法
2018/09/21 Javascript
Vue实现购物车的全选、单选、显示商品价格代码实例
2019/05/06 Javascript
JS实现简易留言板增删功能
2020/02/08 Javascript
JavaScript构造函数原理及实现流程解析
2020/11/19 Javascript
[00:35]DOTA2上海特级锦标赛 MVP.Phx战队宣传片
2016/03/04 DOTA
[48:41]VP vs VG Supermajor小组赛 B组胜者组决赛 BO3 第二场 6.2
2018/06/03 DOTA
Python中函数及默认参数的定义与调用操作实例分析
2017/07/25 Python
Python PyQt4实现QQ抽屉效果
2018/04/20 Python
3分钟学会一个Python小技巧
2018/11/23 Python
由Python编写的MySQL管理工具代码实例
2019/04/09 Python
Python利用for循环打印星号三角形的案例
2020/04/12 Python
python解释器安装教程的方法步骤
2020/07/02 Python
python爬虫爬取网页数据并解析数据
2020/09/18 Python
HTML5超炫酷粒子效果的进度条的实现示例
2019/08/23 HTML / CSS
英国二手物品交易网站:Preloved
2017/10/06 全球购物
体验完美剃须:The Art of Shaving
2018/08/06 全球购物
linux面试题参考答案(9)
2016/01/29 面试题
一套软件测试笔试题
2014/07/25 面试题
出租房屋协议书
2014/09/14 职场文书
2016年父亲节寄语
2015/12/04 职场文书
2019毕业典礼主持词!
2019/07/05 职场文书
Python&Matlab实现灰狼优化算法的示例代码
2022/03/21 Python
MySql按时,天,周,月进行数据统计
2022/08/14 MySQL