整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          純CSS文字聚光燈效果~#web前端

          純CSS文字聚光燈效果~#web前端

          個視頻用CSS來寫聚光燈的效果,大家可以先想一下思路。

          ·來看一下代碼,這里就寫了一個h1標簽,還有一些基本的樣式。

          ·先來看下布局,這里需要把hello world多準備一份,可以直接復制一份h1或者添加一個偽元素,保持html結構簡潔一點。

          ·<htmicontent里面可以直接把hello world寫在里面,或者用之前說的attr()屬性獲取函數,然后定義一個data-text屬性,在h1里面就來定義這個屬性,再把hello world放在里面,這樣子也可以,再給它定一下位。

          ·現在這兩個hello world通過定位重疊在一起了,然后要對偽元素的hello world裁切一小部分出來。

          ·這里用clip-path屬性裁一個小圓出來用circle,半徑大小為100像素,位置在水平的最左邊。現在只是顯示了水平最左邊的這小部分了,把它調到中間位置看一下。

          ·是不是可以通過一個動畫控制它水平方向的值就可以了?來寫個動畫,這里設置三個區間,一開始的時候是在最左邊的,100%的時候也是回到最左邊,50%的時候就去到最右邊,這里改成100%就可以了。

          ·來綁定一下動畫,聚光燈的效果其實就已經完成了,但是背景不怎么好看,給它加一個漸變的背景。

          ·這里準備了一張比較好看的漸變背景圖,再給它放大一點,背景圖是出來了,但是要讓背景只是顯示在文字里面,這里就要對背景進行裁切,讓它按照文本來裁切。

          ·現在背景其實已經裁出來了,只是因為字體顏色把背景給擋住了,把字體的顏色改成透明就可以了,動畫的時間稍微改長一點。

          聚光燈的效果就完成了。

          這個視頻就到這里,感謝大家的收看。

          知道瀏覽器和服務端是通過 HTTP 協議進行數據傳輸的,而 HTTP 協議又是純文本協議,那么瀏覽器在得到服務端傳輸過來的 HTML 字符串,是如何解析成真實的 DOM 元素的呢,也就是我們常說的生成 DOM Tree,最近了解到狀態機這樣一個概念,于是就萌生一個想法,實現一個 innerHTML 功能的函數,也算是小小的實踐一下。

          函數原型

          我們實現一個如下的函數,參數是 DOM 元素和 HTML 字符串,將 HTML 字符串轉換成真實的 DOM 元素且 append 在參數一傳入的 DOM 元素中。

          function html(element, htmlString) {
           // 1. 詞法分析
           // 2. 語法分析
           // 3. 解釋執行
          }
          復制代碼
          

          在上面的注釋我已經注明,這個步驟我們分成三個部分,分別是詞法分析、語法分析和解釋執行。

          詞法分析

          詞法分析是特別重要且核心的一部分,具體任務就是:把字符流變成 token 流。

          詞法分析通常有兩種方案,一種是狀態機,一種是正則表達式,它們是等效的,選擇你喜歡的就好。我們這里選擇狀態機。

          首先我們需要確定 token 種類,我們這里不考慮太復雜的情況,因為我們只對原理進行學習,不可能像瀏覽器那樣有強大的容錯能力。除了不考慮容錯之外,對于自閉合節點、注釋、CDATA 節點暫時均不做考慮。

          接下來步入主題,假設我們有如下節點信息,我們會分出哪些 token 來呢。

          <p class="a" data="js">測試元素</p>
          復制代碼
          

          對于上述節點信息,我們可以拆分出如下 token

          • 開始標簽:<p
          • 屬性標簽:class="a"
          • 文本節點:測試元素
          • 結束標簽:</p>

          狀態機的原理,將整個 HTML 字符串進行遍歷,每次讀取一個字符,都要進行一次決策(下一個字符處于哪個狀態),而且這個決策是和當前狀態有關的,這樣一來,讀取的過程就會得到一個又一個完整的 token,記錄到我們最終需要的 tokens 中。

          萬事開頭難,我們首先要確定起初可能處于哪種狀態,也就是確定一個 start 函數,在這之前,對詞法分析類進行簡單的封裝,具體如下

          function HTMLLexicalParser(htmlString, tokenHandler) {
           this.token=[];
           this.tokens=[];
           this.htmlString=htmlString
           this.tokenHandler=tokenHandler
          }
          復制代碼
          

          簡單解釋下上面的每個屬性

          • token:token 的每個字符
          • tokens:存儲一個個已經得到的 token
          • htmlString:待處理字符串
          • tokenHandler:token 處理函數,我們每得到一個 token 時,就已經可以進行流式解析

          我們可以很容易的知道,字符串要么以普通文本開頭,要么以<開頭,因此 start 代碼如下

          HTMLLexicalParser.prototype.start=function(c) {
           if(c==='<') {
           this.token.push(c)
           return this.tagState
           } else {
           return this.textState(c)
           }
          }
          復制代碼
          

          start處理的比較簡單,如果是<字符,表示開始標簽或結束標簽,因此我們需要下一個字符信息才能確定到底是哪一類 token,所以返回tagState函數去進行再判斷,否則我們就認為是文本節點,返回文本狀態函數。

          接下來分別展開tagState和textState函數。tagState根據下一個字符,判斷進入開始標簽狀態還是結束標簽狀態,如果是/表示是結束標簽,否則是開始標簽,textState用來處理每一個文本節點字符,遇到<表示得到一個完整的文本節點 token,代碼如下

          HTMLLexicalParser.prototype.tagState=function(c) {
           this.token.push(c)
           if(c==='/') {
           return this.endTagState
           } else {
           return this.startTagState
           }
          }
          HTMLLexicalParser.prototype.textState=function(c) {
           if(c==='<') {
           this.emitToken('text', this.token.join(''))
           this.token=[]
           return this.start(c)
           } else {
           this.token.push(c)
           return this.textState
           }
          }
          復制代碼
          

          這里初次見面的函數是emitToken、startTagState和endTagState。

          emitToken用來將產生的完整 token 存儲在 tokens 中,參數是 token 類型和值。

          startTagState用來處理開始標簽,這里有三種情形

          • 如果接下來的字符是字母,則認定依舊處于開始標簽態
          • 遇到空格,則認定開始標簽態結束,接下來是處理屬性了
          • 遇到>,同樣認定為開始標簽態結束,但接下來是處理新的節點信息

          endTagState用來處理結束標簽,結束標簽不存在屬性,因此只有兩種情形

          • 如果接下來的字符是字母,則認定依舊處于結束標簽態
          • 遇到>,同樣認定為結束標簽態結束,但接下來是處理新的節點信息

          邏輯上面說的比較清楚了,代碼也比較簡單,看看就好啦

          HTMLLexicalParser.prototype.emitToken=function(type, value) {
           var res={
           type,
           value
           }
           this.tokens.push(res)
           // 流式處理
           this.tokenHandler && this.tokenHandler(res)
          }
          HTMLLexicalParser.prototype.startTagState=function(c) {
           if(c.match(/[a-zA-Z]/)) {
           this.token.push(c.toLowerCase())
           return this.startTagState
           }
           if(c===' ') {
           this.emitToken('startTag', this.token.join(''))
           this.token=[]
           return this.attrState
           }
           if(c==='>') {
           this.emitToken('startTag', this.token.join(''))
           this.token=[]
           return this.start
           }
          }
          HTMLLexicalParser.prototype.endTagState=function(c) {
           if(c.match(/[a-zA-Z]/)) {
           this.token.push(c.toLowerCase())
           return this.endTagState
           }
           if(c==='>') {
           this.token.push(c)
           this.emitToken('endTag', this.token.join(''))
           this.token=[]
           return this.start
           }
          }
          復制代碼
          

          最后只有屬性標簽需要處理了,也就是上面看到的attrState函數,也處理三種情形

          • 如果是字母、單引號、雙引號、等號,則認定為依舊處于屬性標簽態
          • 如果遇到空格,則表示屬性標簽態結束,接下來進入新的屬性標簽態
          • 如果遇到>,則認定為屬性標簽態結束,接下來開始新的節點信息

          代碼如下

          HTMLLexicalParser.prototype.attrState=function(c) {
           if(c.match(/[a-zA-Z'"=]/)) {
           this.token.push(c)
           return this.attrState
           }
           if(c===' ') {
           this.emitToken('attr', this.token.join(''))
           this.token=[]
           return this.attrState
           }
           if(c==='>') {
           this.emitToken('attr', this.token.join(''))
           this.token=[]
           return this.start
           }
          }
          復制代碼
          

          最后我們提供一個parse解析函數,和可能用到的getOutPut函數來獲取結果即可,就不啰嗦了,上代碼

          HTMLLexicalParser.prototype.parse=function() {
           var state=this.start;
           for(var c of this.htmlString.split('')) {
           state=state.bind(this)(c)
           }
          }
          HTMLLexicalParser.prototype.getOutPut=function() {
           return this.tokens
          }
          復制代碼
          

          接下來簡單測試一下,對于<p class="a" data="js">測試并列元素的</p><p class="a" data="js">測試并列元素的</p>HTML 字符串,輸出結果為

          看上去結果很 nice,接下來進入語法分析步驟

          語法分析

          首先們需要考慮到的情況有兩種,一種是有多個根元素的,一種是只有一個根元素的。

          我們的節點有兩種類型,文本節點和正常節點,因此聲明兩個數據結構。

          function Element(tagName) {
           this.tagName=tagName
           this.attr={}
           this.childNodes=[]
          }
          function Text(value) {
           this.value=value || ''
          }
          復制代碼
          

          目標:將元素建立起父子關系,因為真實的 DOM 結構就是父子關系,這里我一開始實踐的時候,將 childNodes 屬性的處理放在了 startTag token 中,還給 Element 增加了 isEnd 屬性,實屬愚蠢,不但復雜化了,而且還很難實現。仔細思考 DOM 結構,token 也是有順序的,合理利用棧數據結構,這個問題就變的簡單了,將 childNodes 處理放在 endTag 中處理。具體邏輯如下

          • 如果是 startTag token,直接 push 一個新 element
          • 如果是 endTag token,則表示當前節點處理完成,此時出棧一個節點,同時將該節點歸入棧頂元素節點的 childNodes 屬性,這里需要做個判斷,如果出棧之后棧空了,表示整個節點處理完成,考慮到可能有平行元素,將元素 push 到 stacks。
          • 如果是 attr token,直接寫入棧頂元素的 attr 屬性
          • 如果是 text token,由于文本節點的特殊性,不存在有子節點、屬性等,就認定為處理完成。這里需要做個判斷,因為文本節點可能是根級別的,判斷是否存在棧頂元素,如果存在直接壓入棧頂元素的 childNodes 屬性,不存在 push 到 stacks。

          代碼如下

          function HTMLSyntacticalParser() {
           this.stack=[]
           this.stacks=[]
          }
          HTMLSyntacticalParser.prototype.getOutPut=function() {
           return this.stacks
          }
          // 一開始搞復雜了,合理利用基本數據結構真是一件很酷炫的事
          HTMLSyntacticalParser.prototype.receiveInput=function(token) {
           var stack=this.stack
           if(token.type==='startTag') {
           stack.push(new Element(token.value.substring(1)))
           } else if(token.type==='attr') {
           var t=token.value.split('='), key=t[0], value=t[1].replace(/'|"/g, '')
           stack[stack.length - 1].attr[key]=value
           } else if(token.type==='text') {
           if(stack.length) {
           stack[stack.length - 1].childNodes.push(new Text(token.value))
           } else {
           this.stacks.push(new Text(token.value))
           }
           } else if(token.type==='endTag') {
           var parsedTag=stack.pop()
           if(stack.length) {
           stack[stack.length - 1].childNodes.push(parsedTag)
           } else {
           this.stacks.push(parsedTag)
           }
           }
          }
          復制代碼
          

          簡單測試如下:

          沒啥大問題哈

          解釋執行

          對于上述語法分析的結果,可以理解成 vdom 結構了,接下來就是映射成真實的 DOM,這里其實比較簡單,用下遞歸即可,直接上代碼吧

          function vdomToDom(array) {
           var res=[]
           for(let item of array) {
           res.push(handleDom(item))
           }
           return res
          }
          function handleDom(item) {
           if(item instanceof Element) {
           var element=document.createElement(item.tagName)
           for(let key in item.attr) {
           element.setAttribute(key, item.attr[key])
           }
           if(item.childNodes.length) {
           for(let i=0; i < item.childNodes.length; i++) {
           element.appendChild(handleDom(item.childNodes[i]))
           }
           }
           return element
           } else if(item instanceof Text) {
           return document.createTextNode(item.value)
           }
          }
          復制代碼
          

          實現函數

          上面三步驟完成后,來到了最后一步,實現最開始提出的函數

          function html(element, htmlString) {
           // parseHTML
           var syntacticalParser=new HTMLSyntacticalParser()
           var lexicalParser=new HTMLLexicalParser(htmlString, syntacticalParser.receiveInput.bind(syntacticalParser))
           lexicalParser.parse()
           var dom=vdomToDom(syntacticalParser.getOutPut())
           var fragment=document.createDocumentFragment()
           dom.forEach(item=> {
           fragment.appendChild(item)
           })
           element.appendChild(fragment)
          }
          復制代碼
          

          三個不同情況的測試用例簡單測試下

          html(document.getElementById('app'), '<p class="a" data="js">測試并列元素的</p><p class="a" data="js">測試并列元素的</p>')
          html(document.getElementById('app'), '測試<div>你好呀,我測試一下沒有深層元素的</div>')
          html(document.getElementById('app'), '<div class="div"><p class="p">測試一下嵌套很深的<span class="span">p的子元素</span></p><span>p同級別</span></div>')
          復制代碼
          

          聲明:簡單測試下都沒啥問題,本次實踐的目的是對 DOM 這一塊通過詞法分析和語法分析生成 DOM Tree 有一個基本的認識,所以細節問題肯定還是存在很多的。

          總結

          其實在了解了原理之后,這一塊代碼寫下來,并沒有太大的難度,但卻讓我很興奮,有兩個成果吧

          • 了解并初步實踐了一下狀態機
          • 數據結構的魅力

          *1.深拷貝*/

          //(1)

          function deepClone(obj) {

          if (!obj && typeof obj !=='object') {

          return;

          }

          var newObj=obj.constructor===Array ? [] : {};

          for (var key in obj) {

          if (obj[key]) {

          if (obj[key] && typeof obj[key]==='object') {

          newObj[key]=obj[key].constructor===Array ? [] : {};

          //遞歸

          newObj[key]=deepClone(obj[key]);

          } else {

          newObj[key]=obj[key];

          }

          }

          }

          return newObj;

          }

          //使用方法:

          var _arr=[{ a: 1, b: 2 }, { c: 3, d: 4 }];

          var _newArr=deepClone(_arr);

          //(2)比較粗魯的復制

          JSON.parse(JSON.stringify(_arr));

          /*2.合并數組*/

          //(1)concat方法

          var _arr1=["a", "b", "c"];

          var _arr2=["1", "2", "3"];

          var newArr=_arr1.concat(_arr2);

          // 注:使用concat方法合并數組時,不會改變原有數組的結構。

          //(2)push.apply()方法

          var _arr1=["a", "b", "c"];

          var _arr2=["1", "2", "3"];

          _arr1.push.apply(_arr1, _arr2);

          //使用此方法合并的數組會改變合并到的數組

          /*3.判斷數據類型(準確)*/

          Object.prototype.toString.call();

          //使用方法:

          var _obj={};

          Object.prototype.toString.call(_obj)==[object Object];

          /*4.將函數中的arguments參數轉成數組*/

          var _arguments=Array.prototype.slice.apply(arguments);

          //使用方法:

          function add(x, y) {

          var _argus=Array.prototype.slice.call(arguments);

          console.log(_argus);

          }

          /*5.節流函數*/

          function delayFn(fn, delay, mustDelay) {

          var timer=null;

          var t_start;

          return function() {

          var context=this,

          args=arguments,

          t_cur=+new Date();

          clearTimeout(timer); //清除上一次定時器

          if (!t_start) {

          t_start=t_cur; //設置觸發事件

          }

          if (t_cur - t_start >=mustDelay) { //判斷時間差是否大于間隔時間

          fn.apply(context, args);

          t_start=t_cur;

          } else {

          timer=setTimeout(function() { //延遲執行函數

          fn.apply(context, args);

          }, delay);

          }

          };

          }

          //使用方法:

          var count=0;

          function fn() {

          count++;

          console.log(count);

          }

          document.onmousemove=delayFn(fn, 100, 200); //100ms內連續觸發的調用,后一個調用會把前一個調用的等待處理掉,但每隔200ms至少執行一次

          /*6累加函數*/

          var arr1=arr.reduce(function(con, info) {

          if (con) {

          return con;

          }

          return info._show;

          }, false)

          /*7手機端收起彈出鍵盤*/

          var hideKeyboard=function() {

          document.activeElement.blur(); //ios

          $("input").blur();

          };

          /*8時間格式化*/

          Date.prototype.Format=function(fmt) {

          var o={

          "M+": this.getMonth() + 1, //月份

          "d+": this.getDate(), //日

          "h+": this.getHours(), //小時

          "m+": this.getMinutes(), //分

          "s+": this.getSeconds(), //秒

          "q+": Math.floor((this.getMonth() + 3) / 3), //季度

          "S": this.getMilliseconds() //毫秒

          };

          if (/(y+)/.test(fmt)) fmt=fmt.replace(RegExp., (this.getFullYear() + "").substr(4 - RegExp..length));

          for (var k in o)

          if (new RegExp("(" + k + ")").test(fmt)) fmt=fmt.replace(RegExp., (RegExp..length==1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));

          return fmt;

          }

          //使用方法:(時間轉成yyyy-MM-dd hh:mm:ss)

          var time1=new Date().Format("yyyy-MM-dd");

          var time2=new Date().Format("yyyy-MM-dd hh:mm:ss");

          /*9標準時間轉時間戳*/

          var time1=Date.parse(new Date());

          /*10時間戳轉標準時間*/

          new Date(time1).Format("yyyy-MM-dd hh:mm:ss");

          /*11.JSON數組去重*/

          /*

          * JSON數組去重

          * @param: [array] json Array

          * @param: [string] 唯一的key名,根據此鍵名進行去重

          */

          function uniqueArray(array, key) {

          var result=[array[0]];

          for (var i=1; i < array.length; i++) {

          var item=array[i];

          var repeat=false;

          for (var j=0; j < result.length; j++) {

          if (item[key]==result[j][key]) {

          repeat=true;

          break;

          }

          }

          if (!repeat) {

          result.push(item);

          }

          }

          return result;

          }

          //使用方法:

          var _json=[{ id: 'aaa', name: 'bb' }, { id: 'bbb', name: 'xx' }, { id: 'aaa', name: 'ccc' }]

          uniqueArray(_json, 'id');

          /*12.JSON數組去重多字段*/

          var arr=[

          { "name": "1", "value": "qqq", "age": "10" },

          { "name": "1", "value": "qqq", "age": "10" },

          { "name": "2", "value": "eee", "age": "20" },

          { "name": "4", "value": "rrr", "age": "50" },

          { "name": "5", "value": "ttt", "age": "100" }

          ];

          for (var i=0; i < arr.length; i++) {

          for (var j=i + 1; j < arr.length;) {

          if (arr[i].name==arr[j].name && arr[i].value==arr[j].value && arr[i].age==arr[j].age) {

          arr.splice(j, 1);

          } else j++;

          }

          }

          //*13.普通數組去重*/

          Array.prototype.unique=function() {

          var res=[];

          var json={};

          for (var i=0; i < this.length; i++) {

          if (!json[this[i]]) {

          res.push(this[i]);

          json[this[i]]=1;

          }

          }

          return res;

          }

          //使用方法:

          var arr=[112,112,34,'xx',112,112,34,'xx','str','str1'];

          unique(arr);

          /*14.console.log && console.dir*/

          //使用方法:

          var obj={name:'xx',age:18};

          console.log(obj);

          console.dir(obj);

          //區別

          //console.dir比log方法更易讀,信息也更豐富。能夠輸出原始屬性

          /*14.獲取當前時間的時間戳(三種方法)*/

          var d1=new Date().getTime();

          var d2=new Date().valueOf();

          var d3=+new Date();

          /*15 替代arguments.callee()方法*/

          //給內部函數命名

          //接受參數n=5,不用for循環輸出數組【1,2,3,4,5】

          //(1)arguments.callee()遞歸寫法

          function show(n) {

          var arr=[];

          return (function () {

          arr.unshift(n);

          n--;

          if (n !=0) {

          arguments.callee();

          }

          return arr;

          })()

          }

          show(5)//[1,2,3,4,5]

          //(2)內部函數明明寫法

          function show(n) {

          var arr=[];

          return (function fn() {

          arr.unshift(n);

          n--;

          if (n !=0) {

          fn();

          }

          return arr;

          })()

          }

          show(5)//[1,2,3,4,5]

          /*16 預防XSS攻擊*/

          //(1)使用正則替換關鍵字符

          function safeStr(str){

          return str.replace(/</g,'<').replace(/>/g,'>').replace(/"/g, """).replace(/'/g, "'");

          }

          //(2)使用正則轉碼,解碼

          var HtmlUtil={

          /*1.用正則表達式實現html轉碼*/

          htmlEncodeByRegExp:function (str){

          var s="";

          if(str.length==0) return "";

          s=str.replace(/&/g,"&");

          s=s.replace(/</g,"<");

          s=s.replace(/>/g,">");

          s=s.replace(/ /g," ");

          s=s.replace(/\'/g,"'");

          s=s.replace(/\"/g,""");

          return s;

          },

          /*2.用正則表達式實現html解碼*/

          htmlDecodeByRegExp:function (str){

          var s="";

          if(str.length==0) return "";

          s=str.replace(/&/g,"&");

          s=s.replace(/</g,"<");

          s=s.replace(/>/g,">");

          s=s.replace(/ /g," ");

          s=s.replace(/'/g,"\'");

          s=s.replace(/"/g,"\"");

          return s;

          }

          };

          /*17。多維數組轉一維數組*/ /*參考https://www.cnblogs.com/haoxl/p/6818657.html*/

          //(1)使用數組map()方法,對數組中的每一項運行給定函數,返回每次函數調用的結果組成的數組

          var arr=[1,[2,[[3,4],5],6]];

          function unid(arr){

          var arr1=(arr + '').split(',');//將數組轉字符串后再以逗號分隔轉為數組

          var arr2=arr1.map(function(x){

          return Number(x);

          });

          return arr2;

          }

          console.log(unid(arr));

          //(2)只適用于二維數組轉一維數組

          var arr=[1,[2,3],[4,5]];

          arr1=[].concat.apply([],arr);

          console.log(arr1);

          /*18.鼠標滑過顯示溢出的文本*/

          function ellipsis(e) {

          var target=e.target;

          var containerLength=$(target).width();

          var textLength=target.scrollWidth;

          var text=$(target).text();

          if (textLength > containerLength) {

          $(target).attr("title", text);

          }

          }

          $("#xxx").on("mouseenter", ellipsis)


          主站蜘蛛池模板: 东京热人妻无码一区二区av| 久久99国产精一区二区三区| 亚洲熟女综合色一区二区三区| 亚洲一区精彩视频| 无码人妻精一区二区三区| 国产主播福利一区二区| 最美女人体内射精一区二区| 久久婷婷色一区二区三区| 亚洲日本精品一区二区| 无码人妻一区二区三区免费| 日韩久久精品一区二区三区 | 亚洲av日韩综合一区久热| 无码精品人妻一区二区三区人妻斩| 无码人妻一区二区三区在线视频| 亚洲第一区二区快射影院| 无码国产精品一区二区高潮| 色一乱一伦一区一直爽| 国产suv精品一区二区33| 午夜视频在线观看一区| 国偷自产Av一区二区三区吞精| 国产高清一区二区三区| 视频一区二区三区在线观看| 秋霞无码一区二区| 亚洲AV无码一区二区三区网址| 青娱乐国产官网极品一区 | 国产伦精品一区二区三区无广告| 果冻传媒一区二区天美传媒| 亚洲av色香蕉一区二区三区 | 亚洲一区影音先锋色资源| 少妇特黄A一区二区三区| 日本人的色道www免费一区 | 欧洲精品一区二区三区| 亚洲日韩精品一区二区三区 | 丰满爆乳一区二区三区| 美女福利视频一区| 国产一区内射最近更新| 在线|一区二区三区四区| 亚洲精品色播一区二区| 中文字幕日韩丝袜一区| 无码午夜人妻一区二区三区不卡视频 | 一区二区三区福利|