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 亚洲酒色1314狠狠做,亚洲高清资源,在线激情网站

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

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

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

          利用Css3樣式屬性Cursor來(lái)更換自定義個(gè)性化鼠

          利用Css3樣式屬性Cursor來(lái)更換自定義個(gè)性化鼠標(biāo)指針(光標(biāo))

          而今,我們縱向的回顧整個(gè)大前端的歷史,不難發(fā)現(xiàn),人們對(duì)前端的審美要求越來(lái)越高,越來(lái)越嚴(yán)苛,與此同時(shí),人們對(duì)追求美的體驗(yàn)是也極致的,從理性到感性,從平面到幾何,從現(xiàn)實(shí)到虛擬,所以從某種角度來(lái)說(shuō),作為前端工程師,他們所追求的東西往往和人類軟件核心理念南轅北轍,因?yàn)槿祟惖慕K極追求是個(gè)性,絕不是共性,換句話說(shuō),大家都一樣就不好玩兒了。

          那么作為web前端,追求個(gè)性的手段手段之一就是鼠標(biāo)指針的更換,早在css2時(shí)代,Cursor屬性就可以對(duì)象鼠標(biāo)指針光標(biāo)進(jìn)行控制,可以根據(jù)自身需要選擇設(shè)置鼠標(biāo)指針樣式,代碼如下:

          <head>
              <title>cursor屬性</title>
          </head>
          <body>
              <p>請(qǐng)把鼠標(biāo)移動(dòng)到單詞上,可以看到鼠標(biāo)指針發(fā)生變化:</p>
              <span style="cursor:auto">Auto......</span><br />
              <span style="cursor:crosshair">Crosshair......</span><br />
              <span style="cursor:default">Default......</span><br />
              <span style="cursor:pointer">Pointer......</span><br />
              <span style="cursor:move">Move......</span><br />
              <span style="cursor:e-resize">e-resize......</span><br />
              <span style="cursor:ne-resize">ne-resize......</span><br />
              <span style="cursor:nw-resize">nw-resize......</span><br />
              <span style="cursor:n-resize">n-resize......</span><br />
              <span style="cursor:se-resize">se-resize......</span><br />
              <span style="cursor:sw-resize">sw-resize......</span><br />
              <span style="cursor:s-resize">s-resize......</span><br />
              <span style="cursor:w-resize">w-resize......</span><br />
              <span style="cursor:text">text......</span><br />
              <span style="cursor:wait">wait......</span><br />
              <span style="cursor:help">help......</span>
          </body>
          </html>

          不過(guò)這些屬性僅僅是更換系統(tǒng)自定義的一些默認(rèn)樣式,毫無(wú)新意,當(dāng)然也不能滿足所有用戶的需求,特別對(duì)于一些追求時(shí)尚和個(gè)性化的Web應(yīng)用。因此,CSS允許用戶創(chuàng)建自己的鼠標(biāo)光標(biāo)圖片,并保存為 .cur 的光標(biāo)文件,然后通過(guò) cursor屬性來(lái)使用它們。如:

          cursor: url(cursors/cursor.cur) ;


          上述規(guī)則表示,要求瀏覽器加載名稱為 cursor.cur 光標(biāo)文件,并將它用作鼠標(biāo)光標(biāo)。當(dāng)然,瀏覽器也有可能不支持 .cur 格式的光標(biāo)文件,或光標(biāo)文件無(wú)法正常加載。因此,大多數(shù)瀏覽器要求必須指定一個(gè)備用的光標(biāo),否則,cursor屬性無(wú)效。如:

          cursor: url(cursors/cursor.cur), pointer;

          除了更換鼠標(biāo)指針,我們也可以稍加一些變化,比如鼠標(biāo)懸停在超鏈接的時(shí)候,語(yǔ)義化操作往往需要給用戶一點(diǎn)提示:

          a:hover, a:focus, a:active, a.active {
          	color: #fec503;
          	cursor:url(././mouse/breeze/Hand.cur), pointer;
          }


          效果是下面這樣:

          這里我使用的鼠標(biāo)風(fēng)格是在業(yè)界鼎鼎有名的 Breeze

          當(dāng)然了由于不同瀏覽器所支持的光標(biāo)文件格式不盡相同,Opera和IE僅支持 .cur 格式,F(xiàn)irefox、Chrome和Safari既支持 .cur 格式,也支持常見(jiàn)的 .jpg、.gif、.jpg 等格式。

          所以從通用性的角度來(lái)看,.cur格式是最保險(xiǎn)的,不過(guò)也不用擔(dān)心,如果出現(xiàn)不兼容的情況,系統(tǒng)會(huì)選擇默認(rèn)的樣式。

          值得一提的是,對(duì)于.cur文件來(lái)說(shuō),尺寸最好選擇不大于于32*32像素的,因?yàn)橥ㄟ^(guò)樣式進(jìn)行樣式的加載會(huì)損耗一些網(wǎng)頁(yè)性能,同時(shí)過(guò)大的光標(biāo)也會(huì)影響用戶的點(diǎn)選。

          最后,如果手里有.cur的鼠標(biāo)光標(biāo)圖片樣式,這些圖片不僅僅可以應(yīng)用在web網(wǎng)站上,像電腦系統(tǒng)也可以使用比如win10,ubuntu或者mac,這里推薦一個(gè)鼠標(biāo)指針風(fēng)格網(wǎng)站的下載地址:https://zhutix.com/tag/cursors/ 這上面的鼠標(biāo)指針風(fēng)格不能說(shuō)清新脫俗吧,但是也比那些爛大街的殺馬特造型要好看多了。

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

          一、需求背景

          1、項(xiàng)目需求

          當(dāng)用戶進(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. 選擇類似 字符時(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à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)。在用戶拖動(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)。隨著用戶拖動(dòng)鼠標(biāo),焦點(diǎn)的位置會(huì)隨著改變。

          4.2.3. 范圍 (range)

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

          4.2.4. 可編輯元素 (editing host)

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

          4.3. Selection 的屬性

          首先要清楚,選擇的起點(diǎn)稱為錨點(diǎn)(anchor),終點(diǎn)稱為焦點(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:類型: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 類型 無(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 類型 無(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 類型 有 'input', 'textarea', 'select',判斷 value 值,是否存在 存在:返回 html 類型 不存在:返回 null 沒(méi)有特殊節(jié)點(diǎn),返回 null 有內(nèi)容,返回 html 類型
          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ù)類型
           */
          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 類型
           */
          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)控用戶在頁(yè)面的行為,能為后續(xù)的數(shù)據(jù)分析等提供便利
          2. 基于 JS 中的 Selection 和 Range 實(shí)現(xiàn)的,使用原生 JS
          3. 涉及到的操作比較多,包含鍵盤(pán)、鼠標(biāo)右鍵、全選等
          4. 能對(duì)框選的內(nèi)容進(jìn)行分類,區(qū)別 html 和 text,更方便的看出用戶選擇了哪些內(nèi)容

          人人都是產(chǎn)品經(jīng)理【起點(diǎn)學(xué)院】,BAT實(shí)戰(zhàn)派產(chǎn)品總監(jiān)手把手系統(tǒng)帶你學(xué)產(chǎn)品、學(xué)運(yùn)營(yíng)。

          產(chǎn)品設(shè)計(jì)時(shí)細(xì)節(jié)是產(chǎn)品經(jīng)理最頭疼的問(wèn)題,一個(gè)button,一個(gè)鏈接都要考慮太多的細(xì)節(jié)問(wèn)題。作者整理了常見(jiàn)的一些功能設(shè)計(jì)問(wèn)題,一篇文章看懂這些功能設(shè)計(jì)。來(lái)學(xué)習(xí)吧。

          定義

          鏈接也稱為超鏈接,所謂的超鏈接是指從一個(gè)網(wǎng)頁(yè)指向一個(gè)目標(biāo)的連接關(guān)系,這個(gè)目標(biāo)可以是另一個(gè)網(wǎng)頁(yè),也可以是相同網(wǎng)頁(yè)上的不同位置,還可以是一個(gè)圖片,一個(gè)電子郵件地址,一個(gè)文件,甚至是一個(gè)應(yīng)用程序。而在一個(gè)網(wǎng)頁(yè)中用來(lái)超鏈接的對(duì)象,可以是一段文本或者是一個(gè)圖片。當(dāng)瀏覽者單擊已經(jīng)鏈接的文字或圖片后,鏈接目標(biāo)將顯示在瀏覽器上,并且根據(jù)目標(biāo)的類型來(lái)打開(kāi)或運(yùn)行。

          樣式

          鏈接可以是一個(gè)字或是一段字這樣的文本,也可以是一個(gè)按鈕,一張圖片,當(dāng)你點(diǎn)擊后跳轉(zhuǎn)到另一個(gè)目標(biāo),當(dāng)你把鼠標(biāo)指針移到某個(gè)鏈接時(shí)會(huì)變成一個(gè)小手,當(dāng)然在手機(jī)上沒(méi)有這一特點(diǎn)。

          1. 文本樣式的鏈接

          文本樣式的鏈接一般在搜索引擎的網(wǎng)站呈現(xiàn)藍(lán)色字樣,大多會(huì)在下面加上下劃線以便識(shí)別,不過(guò)現(xiàn)如今考慮到不影響文本的可讀性與用戶體驗(yàn),逐漸取消了下劃線。而在一些別的網(wǎng)站考慮到界面設(shè)計(jì)風(fēng)格各方面的因素而不用藍(lán)色。

          谷歌的文本鏈接是藍(lán)色,沒(méi)有下劃線

          百度的文本鏈接也是藍(lán)色,關(guān)鍵詞是紅色,有下劃線

          而京東的文本鏈接有灰色,有白色,有黑色

          2. 按鈕樣式的鏈接

          按鈕樣式的鏈接比文本樣式的更容易識(shí)別,每一個(gè)按鈕都是一樣鏈接。

          按鈕樣式鏈接

          3. 圖片樣式的鏈接

          圖片樣式的鏈接可以是單獨(dú)的一張圖片,也可以是文字與按鈕一起組成一張圖片,只是鼠標(biāo)指針掃過(guò)圖片的任何一個(gè)部位都會(huì)變成小手。

          如桌面彈出這種游戲小窗口的圖片式鏈接

          由文字/圖/按鈕樣式一起構(gòu)成的一張圖片式按鈕,鼠標(biāo)可以點(diǎn)擊圖中任何一部位

          打開(kāi)方式

          鏈接打開(kāi)的方式有三種:第一種是在當(dāng)前頁(yè)面刷新跳轉(zhuǎn),國(guó)外的網(wǎng)站大多是這樣的打開(kāi)式;第二種是在新標(biāo)簽頁(yè)面打開(kāi)鏈接,國(guó)內(nèi)大多采用這種;第三種是提示用APP打開(kāi)。當(dāng)然現(xiàn)在出現(xiàn)了一種新的打開(kāi)方式,那就是二維碼掃描。

          提示用美拍APP打開(kāi)

          類型

          按照連接路徑的不同,網(wǎng)頁(yè)中超鏈接一般分為以下3種類型:內(nèi)部鏈接,錨點(diǎn)鏈接和外部鏈接。

          鏈接還可以分為動(dòng)態(tài)鏈接和靜態(tài)鏈接。動(dòng)態(tài)超鏈接指的是可以通過(guò)改變HTML代碼來(lái)實(shí)現(xiàn)動(dòng)態(tài)變化的鏈接,例如我們可以實(shí)現(xiàn)將鼠標(biāo)移動(dòng)到某個(gè)文字鏈接上,文字就會(huì)象動(dòng)畫(huà)一樣動(dòng)起來(lái)或改變顏色的效果,也可以實(shí)現(xiàn)鼠標(biāo)移到圖片上圖片就產(chǎn)生反色或朦朧等等的效果。而靜態(tài)鏈接,顧名思義,就是沒(méi)有動(dòng)態(tài)效果的鏈接。

          1. 內(nèi)部鏈接

          與外部鏈接(即反向鏈接)相反,內(nèi)部鏈接是指同一網(wǎng)站域名下的內(nèi)容頁(yè)面之間互相鏈接。如頻道、欄目、終極內(nèi)容頁(yè)之間的鏈接,乃至站內(nèi)關(guān)鍵詞之間的Tag鏈接都可以歸類為內(nèi)部鏈接,因此內(nèi)部鏈接我們也可以稱之為站內(nèi)鏈接,對(duì)內(nèi)部鏈接的優(yōu)化其實(shí)就是對(duì)網(wǎng)站的站內(nèi)鏈接的優(yōu)化。

          2. 錨點(diǎn)鏈接

          HTML中的鏈接,正確的說(shuō)法應(yīng)該稱作"錨點(diǎn)",它命名錨點(diǎn)鏈接(也叫書(shū)簽鏈接)常常用于那些內(nèi)容龐大繁瑣的網(wǎng)頁(yè),通過(guò)點(diǎn)擊命名錨點(diǎn),不僅讓我們能指向文檔,還能指向頁(yè)面里的特定段落,更能當(dāng)作"精準(zhǔn)鏈接"的便利工具,讓鏈接對(duì)象接近焦點(diǎn)。便于瀏覽者查看網(wǎng)頁(yè)內(nèi)容。類似于我們閱讀書(shū)籍時(shí)的目錄頁(yè)碼或章回提示。在需要指定到頁(yè)面的特定部分時(shí),標(biāo)記錨點(diǎn)是最佳的方法。

          3. 外部鏈接

          外部鏈接,又常被稱為:“反向鏈接”或“導(dǎo)入鏈接”,是指通過(guò)其他網(wǎng)站鏈接到你的網(wǎng)站的鏈接。

          外部鏈接指的是針對(duì)搜索引擎,與其它站點(diǎn)所做的友情鏈接。高質(zhì)量的外部鏈接指:和你的網(wǎng)站建立鏈接的網(wǎng)站知名度高,訪問(wèn)量大,同時(shí)相對(duì)的外部鏈接較少,有助于快速提升你的網(wǎng)站知名度和排名的其他網(wǎng)站的友情鏈接。

          如果按照使用對(duì)象的不同,網(wǎng)頁(yè)中的鏈接又可以分為:文本超鏈接,圖像超鏈接,E-mail鏈接,錨點(diǎn)鏈接,多媒體文件鏈接,空鏈接等。

          鏈接是一種對(duì)象,它以特殊編碼的文本或圖形的形式來(lái)實(shí)現(xiàn)鏈接,如果單擊該鏈接,則相當(dāng)于指示瀏覽器移至同一網(wǎng)頁(yè)內(nèi)的某個(gè)位置,或打開(kāi)一個(gè)新的網(wǎng)頁(yè),或打開(kāi)某一個(gè)新的WWW網(wǎng)站中的網(wǎng)頁(yè)。

          鏈接狀態(tài)

          鏈接在交互上一般會(huì)呈現(xiàn)4種狀態(tài),即默認(rèn)狀態(tài)/懸停時(shí)狀態(tài)/點(diǎn)擊時(shí)狀態(tài)/點(diǎn)擊后狀態(tài)。比如谷哥網(wǎng)站的交互體驗(yàn)。如下圖:

          點(diǎn)擊前

          懸停時(shí),下面浮現(xiàn)半透明線條

          點(diǎn)擊時(shí),有波紋暈開(kāi)的動(dòng)態(tài)效果

          點(diǎn)擊后,下面線條粗

          有時(shí)候是3種狀態(tài),比如百度網(wǎng)和知乎應(yīng)用:

          默認(rèn)狀態(tài)

          點(diǎn)擊時(shí)鏈接變紅

          點(diǎn)擊后鏈接變成紫色

          IOS系統(tǒng)知乎應(yīng)用的3種狀態(tài),而在Android系統(tǒng)沒(méi)有用力點(diǎn)擊這一狀態(tài)。

          默認(rèn)狀態(tài)

          點(diǎn)擊狀態(tài)

          用力點(diǎn)擊會(huì)彈出預(yù)覽小窗口

          有些時(shí)候只有2種狀態(tài),如下圖谷歌網(wǎng):

          默認(rèn)和點(diǎn)擊后狀態(tài)一樣

          鼠標(biāo)懸停時(shí)出現(xiàn)下劃線

          默認(rèn)狀態(tài)

          點(diǎn)擊時(shí)

          而有時(shí)候比如在APP里有時(shí)候就一直只有一種狀態(tài),也可以稱靜態(tài)鏈接,之前的可以稱之為動(dòng)態(tài)鏈接。在不同的使用場(chǎng)景會(huì)因?yàn)楫?dāng)時(shí)的情況選擇最合適的交互體驗(yàn)設(shè)計(jì)。有的情況下還會(huì)加上點(diǎn)擊的音效,使用戶體驗(yàn)更暢快,這在移動(dòng)端用的使用情況多一些。

          總之鏈接是網(wǎng)頁(yè)不可缺少的構(gòu)成部分,每一個(gè)鏈接的呈現(xiàn)都是經(jīng)過(guò)深思熟慮的。

          作者:潘瑤瓊(簡(jiǎn)書(shū)作者)

          本文由 @潘瑤瓊 授權(quán)發(fā)布于人人都是產(chǎn)品經(jīng)理,未經(jīng)作者許可,禁止轉(zhuǎn)載。


          主站蜘蛛池模板: 亚洲精品日韩一区二区小说| 中文字幕在线播放一区| 免费视频精品一区二区| 女人和拘做受全程看视频日本综合a一区二区视频 | 免费视频精品一区二区| 国产成人久久精品一区二区三区| 无码国产精品一区二区免费vr| 亚洲日本乱码一区二区在线二产线| 亚洲一区二区三区成人网站 | 一区二区三区免费电影| 日本一区二区三区精品中文字幕| 亚洲国产一区在线观看| 精品少妇一区二区三区在线| 日韩综合无码一区二区| 爱爱帝国亚洲一区二区三区| 久久一区二区三区精品| 亚洲国产高清在线一区二区三区 | 好湿好大硬得深一点动态图91精品福利一区二区 | 国产高清精品一区| 成人精品一区二区户外勾搭野战| 一区二区不卡视频在线观看| 国产一区二区三区播放心情潘金莲 | 日本在线视频一区| 亚洲av无码一区二区三区四区 | 无码AV动漫精品一区二区免费| 日本一区二区视频| 久久se精品一区精品二区| 一区二区三区久久精品| 波多野结衣中文字幕一区二区三区| 亚洲福利秒拍一区二区| 无码人妻精品一区二区| 亚洲视频在线一区| 无码人妻精品一区二区蜜桃网站| 亚洲国产av一区二区三区丶| 成人无码精品一区二区三区| 国产成人一区二区三区高清| 风流老熟女一区二区三区| 一区 二区 三区 中文字幕 | 久久4k岛国高清一区二区| 亚洲一区二区中文| 成人区人妻精品一区二区三区|