Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 无遮挡很爽很污很黄很色的网站,国产三级在线播放,欧美xx高清

          整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線:

          JavaScript黑暗技巧:禁止瀏覽器點(diǎn)擊“后退”

          JavaScript黑暗技巧:禁止瀏覽器點(diǎn)擊“后退”按鈕

          覽網(wǎng)頁(yè)時(shí),當(dāng)從A頁(yè)面點(diǎn)擊跳轉(zhuǎn)到B頁(yè)面后,一般情況下,可以點(diǎn)擊瀏覽器上的“后退”按鈕返回A頁(yè)面。

          如果進(jìn)入B頁(yè)面后,B頁(yè)面想讓訪問(wèn)者留下,禁止返回,是否可以實(shí)現(xiàn)呢?

          這簡(jiǎn)直是要控制瀏覽器的行為,雖然有些邪惡,但確實(shí)可以實(shí)現(xiàn),使用特殊的JavaScript代碼就可實(shí)現(xiàn)。

          方法如下:

          實(shí)現(xiàn)原理:

          監(jiān)聽(tīng)瀏覽器的popstate事件,該事件會(huì)在用戶(hù)點(diǎn)擊瀏覽器的回退按鈕時(shí)被觸發(fā)。

          然后,使用History.pushState()方法向當(dāng)前瀏覽器會(huì)話的歷史堆棧中添加一個(gè)陷阱狀態(tài),該狀態(tài)會(huì)使“回退”操作無(wú)效。

          JS源碼:

          <script>

          //瀏覽器返回鍵事件

          pushHistory();

          window.addEventListener("popstate", function(e) {

          //判斷移動(dòng)端

          var userAgentInfo=navigator.userAgent;

          var Agents=new Array("Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod");

          var equipmentType=false;

          for (var v=0; v < Agents.length; v++) {

          if (userAgentInfo.indexOf(Agents[v]) !=-1) {

          equipmentType=true;

          break;

          }

          }

          if (equipmentType) {

          $("#mask-back").show();

          $("#mask-back .back-close").on("click", function() {

          $("#mask-back").hide();

          })

          }

          pushHistory(); //注,此處調(diào)用,可以讓用戶(hù)一直停留著這個(gè)頁(yè)面

          }, false);

          function pushHistory() {

          var stateeee={

          title: "title",

          url: "#"

          };

          window.history.pushState(stateeee, "title", "#");

          }

          </script>

          測(cè)試方法:

          建立兩個(gè)文件:a.html、b.html。

          a文件內(nèi)容簡(jiǎn)單寫(xiě)一句代碼:

          <a href="b.html">goto b.html</a>。

          b文件內(nèi)容寫(xiě)入上面的源碼。

          打開(kāi)a頁(yè)面,點(diǎn)擊鏈接進(jìn)入b頁(yè)面,這時(shí)再點(diǎn)擊瀏覽器上的“后退”按鈕,會(huì)發(fā)現(xiàn):操作無(wú)效,無(wú)法后退。

          提升強(qiáng)度:

          這個(gè)黑暗的技巧,雖然使用了不常見(jiàn)的技術(shù)方法,但查看頁(yè)面源碼很容易發(fā)現(xiàn)其實(shí)現(xiàn)原理。

          為了保護(hù)代碼、防止代碼被分析,可以對(duì)上面的JavaScript代碼加密,加密使用JShaman。

          進(jìn)入JShaman官網(wǎng),貼入代碼:

          在配置中,勾選“字符串加密”:

          然后生成混淆加密的JavaScript代碼:

          復(fù)制粘貼回b文件:

          這時(shí),代碼成為加密狀態(tài),雖然不影響運(yùn)行,是萬(wàn)萬(wàn)不可能了。

          為圍繞 Servo 開(kāi)源網(wǎng)絡(luò)引擎并使其可用于嵌入式目的的新努力的一部分,Servo 引擎在最近幾周內(nèi)增加了許多新功能。在過(guò)去的一個(gè)月里,Servo 增加了更多的功能,包括最終支持"text-align: justify"以及 text-align-last、line-height 和 vertical-align 屬性。

          此外,新的布局引擎還開(kāi)始支持粘貼定位和表格。此外,Servo 正在從浮點(diǎn)坐標(biāo)轉(zhuǎn)向定點(diǎn)坐標(biāo)。

          此外,Servo 還更新了 WebGPU、GStreamer 和 ANGLE 庫(kù)。

          同時(shí),Servo 的基本示例 Web 瀏覽器現(xiàn)已支持......后退和前進(jìn)按鈕:

          此外,Linux 基金會(huì)歐洲利益相關(guān)者和 Igalia 等人還對(duì)代碼進(jìn)行了穩(wěn)定性改進(jìn)、崩潰修復(fù)和其他一般修復(fù)。

          有關(guān) Servo 改進(jìn)的更多詳情,請(qǐng)?jiān)L問(wèn)Servo.org 博客。

          S 鼠標(biāo)框選(頁(yè)面選擇)時(shí)返回對(duì)應(yīng)的 HTML 或文案內(nèi)容

          一、需求背景

          1、項(xiàng)目需求

          當(dāng)用戶(hù)進(jìn)行鼠標(biāo)框選選擇了頁(yè)面上的內(nèi)容時(shí),把選擇的內(nèi)容進(jìn)行上報(bào)。

          2、需求解析

          雖然這需求就一句話的事,但是很顯然,沒(méi)那么簡(jiǎn)單...

          因?yàn)槭髽?biāo)框選說(shuō)起來(lái)簡(jiǎn)單,就是選擇的內(nèi)容,但是這包含很多中情況,比如:只選擇文案、選擇圖片、選擇輸入框、輸入框中的內(nèi)容選擇、iframe、等。

          簡(jiǎn)單總結(jié),分為以下幾點(diǎn):

          1. 選擇文案時(shí)
          2. 選擇圖片、svg、iframe、video、audio 等標(biāo)簽時(shí)
          3. 選擇 input、select、textarea 等標(biāo)簽時(shí)
          4. 選擇 input、textarea 標(biāo)簽內(nèi)容時(shí)
          5. 選擇類(lèi)似 字符時(shí)
          6. 鍵盤(pán)全選時(shí)
          7. 鼠標(biāo)右鍵選擇
          8. 以上各模塊結(jié)合時(shí)
          9. 當(dāng)包含標(biāo)簽的時(shí)候,返回 html 結(jié)構(gòu),只有文本時(shí)返回文本內(nèi)容

          二、技術(shù)要點(diǎn)

          鼠標(biāo)框選包含以下幾點(diǎn):

          1. debounce 防抖
          2. addEventListener 事件監(jiān)聽(tīng)
          3. Range 對(duì)象
          4. Selection 對(duì)象

          1、debounce

          老生常談的技術(shù)點(diǎn)了,這里不能用節(jié)流,因?yàn)榭隙ú荒苣闶髽?biāo)選擇的時(shí)候,隔一段時(shí)間返回一段內(nèi)容,肯定是選擇之后一起返回。

          這里用 debounce 主要也是用在事件監(jiān)聽(tīng)和事件處理上。

          • 【debounce 掘金】
          • 【debounce CSDN】

          2、addEventListener

          事件監(jiān)聽(tīng),因?yàn)槭髽?biāo)選擇,不僅僅是鼠標(biāo)按下到鼠標(biāo)抬起,還包括雙擊、右鍵、全選。

          需要使用事件監(jiān)聽(tīng)對(duì)事件作處理。

          • 【addEventListener MDN】

          3、Range

          Range 接口表示一個(gè)包含節(jié)點(diǎn)與文本節(jié)點(diǎn)的一部分的文檔片段。

          Range 是瀏覽器原生的對(duì)象。

          3.1. 創(chuàng)建 Range 實(shí)例,并設(shè)置起始位置

          <body>
            <ul>
              <li>Vite</li>
              <li>Vue</li>
              <li>React</li>
              <li>VitePress</li>
              <li>NaiveUI</li>
            </ul>
          </body>
          <script>
            // 創(chuàng)建 Range 對(duì)象
            const range=new Range()
            const liDoms=document.querySelectorAll("li");
            // Range 起始位置在 li 2
            range.setStartBefore(liDoms[1]);
            // Range 結(jié)束位置在 li 3
            range.setEndAfter(liDoms[2]);
            // 獲取 selection 對(duì)象
            const selection=window.getSelection();
            // 添加光標(biāo)選擇的范圍
            selection.addRange(range);
          </script>


          可以看到,選擇內(nèi)容為第二行和第三行

          3.1.1 瀏覽器兼容情況


          3.2. Range 屬性

          1. startContainer:起始節(jié)點(diǎn)。
          2. startOffset:起始節(jié)點(diǎn)偏移量。
          3. endContainer:結(jié)束節(jié)點(diǎn)。
          4. endOffset:結(jié)束節(jié)點(diǎn)偏移量。
          5. collapsed:范圍的開(kāi)始和結(jié)束是否為同一點(diǎn)。
          6. commonAncestorContainer:返回完整包含 startContainer 和 endContainer 的最深一級(jí)的節(jié)點(diǎn)。

          3.2.1. 用我們上面創(chuàng)建的實(shí)例來(lái)看下 range 屬性的值


          3.2.2. 如果我們只選擇文本內(nèi)容時(shí)

          只選擇 li 中的 itePres


          可以看出 range 屬性對(duì)應(yīng)的值


          3.3. Range 方法

          1. cloneContents():復(fù)制范圍內(nèi)容,并將復(fù)制的內(nèi)容作為 DocumentFragment 返回。
          2. cloneRange():創(chuàng)建一個(gè)具有相同起點(diǎn)/終點(diǎn)的新范圍, 非引用,可以隨意改變,不會(huì)影響另一方。
          3. collapse(toStart):如果 toStart=true 則設(shè)置 end=start,否則設(shè)置 start=end,從而折疊范圍。
          4. compareBoundaryPoints(how, sourceRange):兩個(gè)范圍邊界點(diǎn)進(jìn)行比較,返回一個(gè)數(shù)字 -1、0、1。
          5. comparePoint(referenceNode, offset):返回-1、0、1具體取決于 是 referenceNode 在 之前、相同還是之后。
          6. createContextualFragment(tagString):返回一個(gè) DocumentFragment。
          7. deleteContents():刪除框選的內(nèi)容。
          8. extractContents():從文檔中刪除范圍內(nèi)容,并將刪除的內(nèi)容作為 DocumentFragment 返回。
          9. getBoundingClientRect():和 dom 一樣,返回 DOMRect 對(duì)象。
          10. getClientRects():返回可迭代的對(duì)象序列 DOMRect。
          11. insertNode(node):在范圍的起始處將 node 插入文檔。
          12. intersectsNode(referenceNode):判斷與給定的 node 是否相交。
          13. selectNode(node):設(shè)置范圍以選擇整個(gè) node。
          14. selectNodeContents(node):設(shè)置范圍以選擇整個(gè) node 的內(nèi)容。
          15. setStart(startNode, startOffset):設(shè)置起點(diǎn)。
          16. setEnd(endNode, endOffset):設(shè)置終點(diǎn)。
          17. setStartBefore(node):將起點(diǎn)設(shè)置在 node 前面。
          18. setStartAfter(node):將起點(diǎn)設(shè)置在 node 后面。
          19. setEndBefore(node):將終點(diǎn)設(shè)置為 node 前面。
          20. setEndAfter(node):將終點(diǎn)設(shè)置為 node 后面。
          21. surroundContents(node):使用 node 將所選范圍內(nèi)容包裹起來(lái)。

          3.4. 創(chuàng)建 Range 的方法

          3.4.1. Document.createRange

          const range=document.createRange();

          3.4.2. Selection 的 getRangeAt() 方法

          const range=window.getSelection().getRangeAt(0)

          3.4.3. caretRangeFromPoint() 方法

          if (document.caretRangeFromPoint) {
              range=document.caretRangeFromPoint(e.clientX, e.clientY);
          }

          3.4.4. Range() 構(gòu)造函數(shù)

          const range=new Range()

          3.5. Range 兼容性


          4、Selection

          Selection 對(duì)象表示用戶(hù)選擇的文本范圍或插入符號(hào)的當(dāng)前位置。它代表頁(yè)面中的文本選區(qū),可能橫跨多個(gè)元素。

          4.1. 獲取文本對(duì)象

          window.getSelection()



          4.2. Selection 術(shù)語(yǔ)

          4.2.1. 錨點(diǎn) (anchor)

          錨指的是一個(gè)選區(qū)的起始點(diǎn)(不同于 HTML 中的錨點(diǎn)鏈接)。當(dāng)我們使用鼠標(biāo)框選一個(gè)區(qū)域的時(shí)候,錨點(diǎn)就是我們鼠標(biāo)按下瞬間的那個(gè)點(diǎn)。在用戶(hù)拖動(dòng)鼠標(biāo)時(shí),錨點(diǎn)是不會(huì)變的。

          4.2.2. 焦點(diǎn) (focus)

          選區(qū)的焦點(diǎn)是該選區(qū)的終點(diǎn),當(dāng)你用鼠標(biāo)框選一個(gè)選區(qū)的時(shí)候,焦點(diǎn)是你的鼠標(biāo)松開(kāi)瞬間所記錄的那個(gè)點(diǎn)。隨著用戶(hù)拖動(dòng)鼠標(biāo),焦點(diǎn)的位置會(huì)隨著改變。

          4.2.3. 范圍 (range)

          范圍指的是文檔中連續(xù)的一部分。一個(gè)范圍包括整個(gè)節(jié)點(diǎn),也可以包含節(jié)點(diǎn)的一部分,例如文本節(jié)點(diǎn)的一部分。用戶(hù)通常下只能選擇一個(gè)范圍,但是有的時(shí)候用戶(hù)也有可能選擇多個(gè)范圍。

          4.2.4. 可編輯元素 (editing host)

          一個(gè)用戶(hù)可編輯的元素(例如一個(gè)使用 contenteditable 的 HTML 元素,或是在啟用了 designMode 的 Document 的子元素)。

          4.3. Selection 的屬性

          首先要清楚,選擇的起點(diǎn)稱(chēng)為錨點(diǎn)(anchor),終點(diǎn)稱(chēng)為焦點(diǎn)(focus)。

          1. anchorNode:選擇的起始節(jié)點(diǎn)。
          2. anchorOffset:選擇開(kāi)始的 anchorNode 中的偏移量。
          3. focusNode:選擇的結(jié)束節(jié)點(diǎn)。
          4. focusOffset:選擇開(kāi)始處 focusNode 的偏移量。
          5. isCollapsed:如果未選擇任何內(nèi)容(空范圍)或不存在,則為 true。
          6. rangeCount:選擇中的范圍數(shù),之前說(shuō)過(guò),除 Firefox 外,其他瀏覽器最多為1。
          7. type:類(lèi)型:None、Caret、Range

          4.4. Selection 方法

          1. addRange(range): 將一個(gè) Range 對(duì)象添加到當(dāng)前選區(qū)。
          2. collapse(node, offset): 將選區(qū)折疊到指定的節(jié)點(diǎn)和偏移位置。
          3. collapseToEnd(): 將選區(qū)折疊到當(dāng)前選區(qū)的末尾。
          4. collapseToStart(): 將選區(qū)折疊到當(dāng)前選區(qū)的起始位置。
          5. containsNode(node, partlyContained): 判斷選區(qū)是否包含指定的節(jié)點(diǎn),可以選擇是否部分包含。
          6. deleteFromDocument(): 從文檔中刪除選區(qū)內(nèi)容。
          7. empty(): 從選區(qū)中移除所有范圍(同 `removeAllRanges()``,已廢棄)。
          8. extend(node, offset): 將選區(qū)的焦點(diǎn)節(jié)點(diǎn)擴(kuò)展到指定的節(jié)點(diǎn)和偏移位置。
          9. getRangeAt(index): 返回選區(qū)中指定索引處的 Range 對(duì)象。
          10. removeAllRanges(): 移除所有選區(qū)中的范圍。
          11. removeRange(range): 從選區(qū)中移除指定的 Range 對(duì)象。
          12. selectAllChildren(node): 選中指定節(jié)點(diǎn)的所有子節(jié)點(diǎn)。
          13. setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset): 設(shè)置選區(qū)的起始和結(jié)束節(jié)點(diǎn)及偏移位置。
          14. setPosition(node, offset):collapse 的別名

          4.5. Selection 兼容性


          三、項(xiàng)目實(shí)現(xiàn)

          1、實(shí)現(xiàn)思路

          1. 先獲取選擇的內(nèi)容,開(kāi)發(fā) getSelectContent 函數(shù)
          2. 對(duì)獲取的內(nèi)容進(jìn)行判斷,是否存在 selection 實(shí)例,沒(méi)有直接返回 null
          3. 判斷 selection 實(shí)例的 isCollapsed 屬性 沒(méi)有選中,對(duì) selection 進(jìn)行 toString().trim() 操作,判斷內(nèi)容 有內(nèi)容,直接返回 text 類(lèi)型 無(wú)內(nèi)容,返回 null 有選中,則判斷內(nèi)容
          4. 判斷選中的內(nèi)容有沒(méi)有節(jié)點(diǎn) 沒(méi)有節(jié)點(diǎn),則和沒(méi)有選中一樣處理,進(jìn)行 toString().trim() 操作,判斷內(nèi)容 有內(nèi)容,直接返回 text 類(lèi)型 無(wú)內(nèi)容,返回 null 有節(jié)點(diǎn),進(jìn)行 toString().trim() 操作,判斷內(nèi)容 沒(méi)有內(nèi)容,判斷是否有特殊節(jié)點(diǎn) 有 'iframe', 'svg', 'img', 'audio', 'video' 節(jié)點(diǎn),返回 html 類(lèi)型 有 'input', 'textarea', 'select',判斷 value 值,是否存在 存在:返回 html 類(lèi)型 不存在:返回 null 沒(méi)有特殊節(jié)點(diǎn),返回 null 有內(nèi)容,返回 html 類(lèi)型
          5. 對(duì)鼠標(biāo) mousedown、mouseup 事件和 selectionchange、contextmenu、dblclick 事件進(jìn)行監(jiān)聽(tīng),觸發(fā) getSelectContent 函數(shù)
          6. 在需要的地方進(jìn)行 debounce 防抖處理

          2、簡(jiǎn)易流程圖


          2、Debounce 方法實(shí)現(xiàn)

          2.1. JS

          function debounce (fn, time=500) {
            let timeout=null; // 創(chuàng)建一個(gè)標(biāo)記用來(lái)存放定時(shí)器的返回值
            return function () {
              clearTimeout(timeout) // 每當(dāng)觸發(fā)時(shí),把前一個(gè) 定時(shí)器 clear 掉
              timeout=setTimeout(()=> { // 創(chuàng)建一個(gè)新的 定時(shí)器,并賦值給 timeout
                fn.apply(this, arguments)
              }, time)
            }
          }

          2.2. TS

          /**
           * debounce 函數(shù)類(lèi)型
           */
          type DebouncedFunction<F extends (...args: any[])=> any>=(...args: Parameters<F>)=> void
          /**
           * debounce 防抖函數(shù)
           * @param {Function} func 函數(shù)
           * @param {number} wait 等待時(shí)間
           * @param {false} immediate 是否立即執(zhí)行
           * @returns {DebouncedFunction}
           */
          function debounce<F extends (...args: any[])=> any>(
            func: F,
            wait=500,
            immediate=false
          ): DebouncedFunction<F> {
            let timeout: ReturnType<typeof setTimeout> | null
            return function (this: ThisParameterType<F>, ...args: Parameters<F>) {
              // eslint-disable-next-line @typescript-eslint/no-this-alias
              const context=this
              const later=function () {
                timeout=null
                if (!immediate) {
                  func.apply(context, args)
                }
              }
              const callNow=immediate && !timeout
              if (timeout) {
                clearTimeout(timeout)
              }
              timeout=setTimeout(later, wait)
              if (callNow) {
                func.apply(context, args)
              }
            }
          }

          3、獲取選擇的文本/html 元素

          3.1. 獲取文本/html 元素

          nterface IGetSelectContentProps {
            type: 'html' | 'text'
            content: string
          }
          /**
           * 獲取選擇的內(nèi)容
           * @returns {null | IGetSelectContentProps} 返回選擇的內(nèi)容
           */
          const getSelectContent=(): null | IGetSelectContentProps=> {
            const selection=window.getSelection()
            if (selection) {
              // 1. 是焦點(diǎn)在 input 輸入框
              // 2. 沒(méi)有選中
              // 3. 選擇的是輸入框
              if (selection.isCollapsed) {
                return selection.toString().trim().length
                  ? {
                      type: 'text',
                      content: selection.toString().trim()
                    }
                  : null
              }
              // 獲取選擇范圍
              const range=selection.getRangeAt(0)
              // 獲取選擇內(nèi)容
              const rangeClone=range.cloneContents()
              // 判斷選擇內(nèi)容里面有沒(méi)有節(jié)點(diǎn)
              if (rangeClone.childElementCount > 0) {
                // 創(chuàng)建 div 標(biāo)簽
                const container=document.createElement('div')
                // div 標(biāo)簽 append 復(fù)制節(jié)點(diǎn)
                container.appendChild(rangeClone)
                // 如果復(fù)制的內(nèi)容長(zhǎng)度為 0
                if (!selection.toString().trim().length) {
                  // 判斷是否有選擇特殊節(jié)點(diǎn)
                  const isSpNode=hasSpNode(container)
                  return isSpNode
                    ? {
                        type: 'html',
                        content: container.innerHTML
                      }
                    : null
                }
                return {
                  type: 'html',
                  content: container.innerHTML
                }
              } else {
                return selection.toString().trim().length
                  ? {
                      type: 'text',
                      content: selection.toString().trim()
                    }
                  : null
              }
            } else {
              return null
            }
          }
          /**
           * 判斷是否包含特殊元素
           * @param {Element} parent 父元素
           * @returns {boolean} 是否包含特殊元素
           */
          const hasSpNode=(parent: Element): boolean=> {
            const nodeNameList=['iframe', 'svg', 'img', 'audio', 'video']
            const inpList=['input', 'textarea', 'select']
            return Array.from(parent.children).some((node)=> {
              if (nodeNameList.includes(node.nodeName.toLocaleLowerCase())) return true
              if (
                inpList.includes(node.nodeName.toLocaleLowerCase()) &&
                (node as HTMLInputElement).value.trim().length
              )
                return true
              if (node.children) {
                return hasSpNode(node)
              }
              return false
            })
          }

          3.2. 只需要文本

          /**
           * 獲取框選的文案內(nèi)容
           * @returns {string} 返回框選的內(nèi)容
           */
          const getSelectTextContent=(): string=> {
            const selection=window.getSelection()
            return selection?.toString().trim() || ''
          }

          4、添加事件監(jiān)聽(tīng)

          // 是否時(shí)鼠標(biāo)點(diǎn)擊動(dòng)作
          let selectionchangeMouseTrack: boolean=false
          const selectionChangeFun=debounce(()=> {
            const selectContent=getSelectContent()
            console.log('selectContent', selectContent)
            // todo... 處理上報(bào)
            selectionchangeMouseTrack=false
          })
          // 添加 mousedown 監(jiān)聽(tīng)事件
          document.addEventListener('mousedown', ()=> {
            selectionchangeMouseTrack=true
          })
          // 添加 mouseup 監(jiān)聽(tīng)事件
          document.addEventListener(
            'mouseup',
            debounce(()=> {
              selectionChangeFun()
            }, 100)
          )
          // 添加 selectionchange 監(jiān)聽(tīng)事件
          document.addEventListener(
            'selectionchange',
            debounce(()=> {
              if (selectionchangeMouseTrack) return
              selectionChangeFun()
            })
          )
          // 添加 dblclick 監(jiān)聽(tīng)事件
          document.addEventListener('dblclick', ()=> {
            selectionChangeFun()
          })
          // 添加 contextmenu 監(jiān)聽(tīng)事件
          document.addEventListener(
            'contextmenu',
            debounce(()=> {
              selectionChangeFun()
            })
          )

          也可以進(jìn)行封裝

          /**
           * addEventlistener function 類(lèi)型
           */
          export interface IEventHandlerProps {
            [eventName: string]: EventListenerOrEventListenerObject
          }
          
          let selectionchangeMouseTrack: boolean=false
          const eventHandlers: IEventHandlerProps={
            // 鼠標(biāo) down 事件
            mousedown: ()=> {
              selectionchangeMouseTrack=true
            },
            // 鼠標(biāo) up 事件
            mouseup: debounce(()=> selectionChangeFun(), 100),
            // 選擇事件
            selectionchange:  debounce(()=> {
              if (selectionchangeMouseTrack) return
              selectionChangeFun()
            }),
            // 雙擊事件
            dblclick: ()=> selectionChangeFun(),
            // 右鍵事件
            contextmenu: debounce(()=> selectionChangeFun())
          }
          Object.keys(eventHandlers).forEach((event)=> {
            document.addEventListener(event, eventHandlers[event])
          })

          5、返回內(nèi)容

          5.1. 純文本內(nèi)容


          5.2. html 格式


          6. 完整 JS 代碼

          function debounce (fn, time=500) {
            let timeout=null; // 創(chuàng)建一個(gè)標(biāo)記用來(lái)存放定時(shí)器的返回值
            return function () {
              clearTimeout(timeout) // 每當(dāng)觸發(fā)時(shí),把前一個(gè) 定時(shí)器 clear 掉
              timeout=setTimeout(()=> { // 創(chuàng)建一個(gè)新的 定時(shí)器,并賦值給 timeout
                fn.apply(this, arguments)
              }, time)
            }
          }
          
          let selectionchangeMouseTrack=false
          document.addEventListener('mousedown', (e)=> {
            selectionchangeMouseTrack=true
            console.log('mousedown', e)
          })
          document.addEventListener('mouseup', debounce((e)=> {
            console.log('mouseup', e)
            selectionChangeFun()
          }, 100))
          document.addEventListener('selectionchange', debounce((e)=> {
            console.log('selectionchange', e)
            if (selectionchangeMouseTrack) return
            selectionChangeFun()
          }))
          document.addEventListener('dblclick', (e)=> {
            console.log('dblclick', e)
            selectionChangeFun()
          })
          document.addEventListener('contextmenu',debounce(()=> {
            selectionChangeFun()
          }))
          
          const selectionChangeFun=debounce(()=> {
            const selectContent=getSelectContent()
            selectionchangeMouseTrack=false
            console.log('selectContent', selectContent)
          })
          
          const getSelectContent=()=> {
            const selection=window.getSelection();
            if (selection) {
              // 1. 是焦點(diǎn)在 input 輸入框
              // 2. 沒(méi)有選中
              // 3. 選擇的是輸入框
              if (selection.isCollapsed) {
                return selection.toString().trim().length ? {
                  type: 'text',
                  content: selection.toString().trim()
                } : null
              }
              // 獲取選擇范圍
              const range=selection.getRangeAt(0);
              // 獲取選擇內(nèi)容
              const rangeClone=range.cloneContents()
              // 判斷選擇內(nèi)容里面有沒(méi)有節(jié)點(diǎn)
              if (rangeClone.childElementCount > 0) {
                const container=document.createElement('div');
                container.appendChild(rangeClone);
                if (!selection.toString().trim().length) {
                  const hasSpNode=getSpNode(container)
                  return hasSpNode ? {
                    type: 'html',
                    content: container.innerHTML
                  } : null
                }
                return {
                  type: 'html',
                  content: container.innerHTML
                }
              } else {
                return selection.toString().trim().length ? {
                  type: 'text',
                  content: selection.toString().trim()
                } : null
              }
            } else {
              return null
            }
          }
          
          const getSpNode=(parent)=> {
            const nodeNameList=['iframe', 'svg', 'img', 'audio', 'video']
            const inpList=['input', 'textarea', 'select']
            return Array.from(parent.children).some((node)=> {
              if (nodeNameList.includes(node.nodeName.toLocaleLowerCase())) return true
              if (inpList.includes(node.nodeName.toLocaleLowerCase()) && node.value.trim().length) return true
              if (node.children) {
                return getSpNode(node)
              }
              return false
            })
          }

          四、總結(jié)

          1. 鼠標(biāo)框選上報(bào)能監(jiān)控用戶(hù)在頁(yè)面的行為,能為后續(xù)的數(shù)據(jù)分析等提供便利
          2. 基于 JS 中的 Selection 和 Range 實(shí)現(xiàn)的,使用原生 JS
          3. 涉及到的操作比較多,包含鍵盤(pán)、鼠標(biāo)右鍵、全選等
          4. 能對(duì)框選的內(nèi)容進(jìn)行分類(lèi),區(qū)別 html 和 text,更方便的看出用戶(hù)選擇了哪些內(nèi)容

          主站蜘蛛池模板: 亚洲国产一区视频| 精品少妇一区二区三区视频| 国产一区二区三区电影| 中文字幕在线看视频一区二区三区 | 精品人体无码一区二区三区 | 亚洲色无码专区一区| 精品亚洲一区二区| 亚洲一区二区三区AV无码| 一区二区三区日本视频| 高清一区二区在线观看| 波多野结衣电影区一区二区三区 | 国产亚洲福利精品一区二区| 红桃AV一区二区三区在线无码AV| 一区二区三区无码高清| 综合激情区视频一区视频二区| 无码中文人妻在线一区| 视频一区二区精品的福利| 日本一区二区在线不卡| 日韩有码一区二区| 97久久精品无码一区二区| 亚洲sm另类一区二区三区| 中文字幕在线观看一区二区三区| 成人免费一区二区三区| 亚洲AV色香蕉一区二区| 欲色aV无码一区二区人妻 | 精品视频在线观看一区二区| 动漫精品第一区二区三区| 无码乱人伦一区二区亚洲| 日本中文一区二区三区亚洲| 国产福利电影一区二区三区久久久久成人精品综合 | 亚洲毛片αv无线播放一区| 国产精品无码一区二区三级 | 激情内射亚州一区二区三区爱妻| 天美传媒一区二区三区| 国产香蕉一区二区精品视频| 国产精品视频一区二区三区| 天天视频一区二区三区| 久久无码精品一区二区三区| 中文字幕不卡一区| 天堂一区二区三区精品| 国产激情一区二区三区成人91|