前在項(xiàng)目的過程中遇到了一個(gè)問題,某個(gè) div 希望始終顯示在最上面,而在之后的元素都顯示在它之下,當(dāng)時(shí)設(shè)置了 z-index 也沒有效果,不知道什么原因,因此找了一下 CSS 相關(guān)資料,解決了這個(gè)問題的同時(shí),也學(xué)習(xí)了很多知識(shí),特此和大家分享一下。
屏幕是一個(gè)二維平面,然而 HTML 元素卻是排列在三維坐標(biāo)系中, x 為水平方向, y 為垂直方向, z為屏幕由內(nèi)向外方向,我們在看屏幕的時(shí)候是沿著 z 軸方向從外向內(nèi)的。由此,元素在用戶視角就形成了層疊的關(guān)系,某個(gè)元素可能覆蓋了其他元素也可能被其他元素覆蓋;
這里有幾個(gè)重要的概念:層疊上下文 (堆疊上下文, Stacking Context)、層疊等級(jí) (層疊水平, Stacking Level)、層疊順序 (層疊次序, 堆疊順序, Stacking Order)、z-index、BFC(塊級(jí)格式化上下文,Block Formatting Context),這些概念共同決定了你看到元素的位置,下面我們就圍繞著這幾個(gè)概念來一起學(xué)習(xí)一下。
聲明:
1. 層疊上下文 (Stacking Context)
層疊上下文 (堆疊上下文, Stacking Context),是 HTML 中一個(gè)三維的概念。在 CSS2.1 規(guī)范中,每個(gè)元素的位置是三維的,當(dāng)元素發(fā)生層疊,這時(shí)它可能覆蓋了其他元素或者被其他元素覆蓋;排在 z 軸越靠上的位置,距離屏幕觀察者越近。
文章 <關(guān)于z-index 那些你不知道的事> 有一個(gè)很好的比喻,這里引用一下;
可以想象一張桌子,上面有一堆物品,這張桌子就代表著一個(gè)層疊上下文。如果在第一張桌子旁還有第二張桌子,那第二張桌子就代表著另一個(gè)層疊上下文。現(xiàn)在想象在第一張桌子上有四個(gè)小方塊,他們都直接放在桌子上。在這四個(gè)小方塊之上有一片玻璃,而在玻璃片上有一盤水果。這些方塊、玻璃片、水果盤,各自都代表著層疊上下文中一個(gè)不同的層疊層,而這個(gè)層疊上下文就是桌子。
每一個(gè)網(wǎng)頁都像一個(gè)房間,這個(gè)房間就是 <html></html>,其他層疊上下文就像這個(gè)房間里的桌子,HTML 標(biāo)簽中的一切都被置于這個(gè)房間中。
當(dāng)給一個(gè)元素的 position 值賦為 fixed 或 sticky 值時(shí),你就創(chuàng)建了一個(gè)新的層疊上下文,其中有著獨(dú)立于頁面上其他層疊上下文和層疊層的層疊層,這就相當(dāng)于你把另一張桌子帶到了房間里。
層疊上下文 1 (Stacking Context 1)是由文檔根元素形成的, 層疊上下文 2 和 3 (Stacking Context 2, 3) 都是層疊上下文 1 (Stacking Context 1) 上的層疊層。他們各自也都形成了新的層疊上下文,其中包含著新的層疊上下文。
在層疊上下文中,其子元素按照上面解釋的規(guī)則進(jìn)行層疊。形成層疊上下文的方法有:
總結(jié):
2. 層疊等級(jí) (Stacking Level)
層疊等級(jí) (層疊水平, Stacking Level) 決定了在同一個(gè)層疊上下文中,元素在 z 軸上的顯示的順序;
對于普通元素的層疊水平探討只局限于在當(dāng)前層疊上下文中:
層疊上下文本身是一個(gè)強(qiáng)力的「層疊結(jié)界」,普通的元素水平是無法突破這個(gè)結(jié)界和結(jié)界外的元素去較量層疊水平的。
— CSS 世界
另外,層疊等級(jí)并不一定由 z-index 決定,只有定位元素的層疊等級(jí)才由 z-index 決定,其他類型元素的層疊等級(jí)由層疊順序、他們在 HTML 中出現(xiàn)的順序、他們的祖先元素的層疊等級(jí)一同決定,詳細(xì)的規(guī)則見下面層疊順序的介紹。
3. z-index
在 CSS 2.1 中, 所有的盒模型元素都處于三維坐標(biāo)系中。除了我們常用的橫坐標(biāo)和縱坐標(biāo), 盒模型元素還可以沿著「z 軸」層疊擺放,當(dāng)他們相互覆蓋時(shí),z 軸順序就變得十分重要。
-- CSS 2.1 Section 9.9.1 - Layered presentation
z-index 只適用于定位的元素,對非定位元素?zé)o效,它可以被設(shè)置為正整數(shù)、負(fù)整數(shù)、 0、 auto,如果一個(gè)定位元素沒有設(shè)置 z-index,那么默認(rèn)為 auto;
元素的 z-index 值只在同一個(gè)層疊上下文中有意義。如果父級(jí)層疊上下文的層疊等級(jí)低于另一個(gè)層疊上下文的,那么它 z-index 設(shè)的再高也沒用。所以如果你遇到 z-index 值設(shè)了很大,但是不起作用的話,就去看看它的父級(jí)層疊上下文是否被其他層疊上下文蓋住了。
4. 層疊順序 (Stacking Order)
層疊順序 (層疊次序, 堆疊順序, Stacking Order) 描述的是元素在同一個(gè)層疊上下文中的順序規(guī)則(之前的層疊上下文和層疊等級(jí)是概念),從層疊的底部開始,共有七種層疊順序:
第 7 級(jí)順序的元素會(huì)顯示在之前順序元素的上方,也就是看起來覆蓋了更低級(jí)的元素:
除層疊順序優(yōu)先級(jí)規(guī)則之外,還有一條后來居上規(guī)則:同一個(gè)層疊順序的元素按照在 HTML 里出現(xiàn)的順序依次層疊。這兩個(gè)規(guī)則共同決定瀏覽器元素在文檔中是如何層疊的。
5. 文檔流 (Document Flow)
5.1 常規(guī)流 (Normal flow)
5.2 浮動(dòng) (Floats)
5.3 絕對定位 (Absolute positioning)
6. BFC (Block Formatting Context)
6.1 什么是 BFC
BFC (Block Formatting Context) 塊級(jí)格式化上下文,是用于布局塊級(jí)盒子的一塊渲染區(qū)域,相對應(yīng)的還有 IFC(Inline Formatting Context)內(nèi)聯(lián)格式化上下文,不是本文重點(diǎn),讀者可以自行查閱相關(guān)知識(shí)。
BFC 是 Web 頁面 CSS 視覺渲染的一部分,用于決定塊盒子的布局及浮動(dòng)相互影響范圍的一個(gè)區(qū)域。
— MDN - 塊格式化上下文
一個(gè) BFC 的范圍包含創(chuàng)建該上下文元素的所有子元素,但不包括創(chuàng)建了新 BFC 的子元素的內(nèi)部元素。這從另一方角度說明,一個(gè)元素不能同時(shí)存在于兩個(gè) BFC 中。因?yàn)槿绻粋€(gè)元素能夠同時(shí)處于兩個(gè) BFC 中,那么就意味著這個(gè)元素能與兩個(gè) BFC 中的元素發(fā)生作用,就違反了 BFC 的隔離作用。
觸發(fā) BFC 的方式有:
注意: display:table 也可以生成 BFC 的原因在于 Table 會(huì)默認(rèn)生成一個(gè)匿名的 table-cell,是這個(gè)匿名的 table-cell 生成了 BFC。
6.2 用法
1. 阻止相鄰元素的 margin 合并
屬于同一個(gè) BFC 的兩個(gè)相鄰塊級(jí)子元素的上下 margin 會(huì)發(fā)生重疊,(設(shè)置 writing-mode:tb-rl時(shí),水平 margin 會(huì)發(fā)生重疊)。所以當(dāng)兩個(gè)相鄰塊級(jí)子元素分屬于不同的 BFC 時(shí)可以阻止 margin 重疊。可以給任一個(gè)相鄰塊級(jí)盒子的外面包一個(gè) div,通過改變此 div 的屬性使兩個(gè)原盒子分屬于兩個(gè)不同的 BFC,以此來阻止 margin 重疊。
代碼和預(yù)覽參見:Codepen - 使用BFC阻止margin合并:https://codepen.io/SHERlocked93/pen/eVOevN
2. 阻止元素被浮動(dòng)元素覆蓋
一個(gè)正常文檔流的塊級(jí)元素可能被一個(gè) float 元素覆蓋,擠占正常文檔流,因此可以設(shè)置一個(gè)元素的 float、 display、 position 值等方式觸發(fā) BFC,以阻止被浮動(dòng)盒子覆蓋。
代碼和預(yù)覽參見:Codepen - 使用BFC阻止元素被浮動(dòng)元素覆蓋:https://codepen.io/SHERlocked93/pen/pazdzB
3. 包含浮動(dòng)元素
通過改變包含浮動(dòng)子元素的父盒子的屬性值,觸發(fā) BFC,以此來包含子元素的浮動(dòng)盒子。
代碼和預(yù)覽參見:Codepen - 使用BFC包含浮動(dòng)元素:https://codepen.io/SHERlocked93/pen/OQLOqG
7. 實(shí)戰(zhàn)
下面一起來看幾個(gè)例子實(shí)戰(zhàn)一下,幫助理解。
7.1 普通情況
三個(gè) relative 定位的 div 塊中各有 absolute 的不同顏色的 span.red、 span.green、 span.blue,它們都設(shè)置了 position:absolute;
代碼和預(yù)覽參見:Codepen - 普通情況:https://codepen.io/SHERlocked93/pen/aaPord
那么當(dāng)沒有元素包含 z-index 屬性時(shí),這個(gè)例子中的元素按照如下順序?qū)盈B(從底到頂順序):
紅綠藍(lán)都屬于 z-index 為 auto 的定位元素,因此按照 7 層層疊順序規(guī)則來說同屬于層疊順序第 6 級(jí),所以按 HTML 中的出現(xiàn)順序?qū)盈B:紅->綠->藍(lán)
7.2 在相同層疊上下文的父元素內(nèi)的情況
紅綠位于一個(gè) div.first-box 下,藍(lán)位于 div.second-box 下,紅綠藍(lán)都設(shè)置了 position:absolute, first-box 與 second-box 都設(shè)置了 position:relative;
代碼和預(yù)覽參見:Codepen - 父元素不同但都位于根元素下:https://codepen.io/SHERlocked93/pen/RYENBw
這個(gè)例子中,紅藍(lán)綠元素的父元素 first-box 與 second-box 都沒有生成新的層疊上下文,都屬于根層疊上下文中的元素,且都是層疊順序第 6 級(jí),所以按 HTML 中的出現(xiàn)順序?qū)盈B:紅->綠->藍(lán)
7.3 給子元素增加 z-index
紅綠位于一個(gè) div.first-box 下,藍(lán)黃位于 div.second-box 下,紅綠藍(lán)都設(shè)置了 position:absolute,如果這時(shí)給綠加一個(gè)屬性 z-index:1,那么此時(shí) .green 位于最上面;
如果再在 .second-box 下 .green 后加一個(gè)絕對定位的 span.gold,設(shè)置 z-index:-1,那么它將位于紅綠藍(lán)的下面;
代碼和預(yù)覽參見:Codepen - 設(shè)置了z-index:https://codepen.io/SHERlocked93/pen/gdZOrK
這個(gè)例子中,紅藍(lán)綠黃元素的父元素中都沒有生成新的層疊上下文,都屬于根層疊上下文中的元素
所以這個(gè)例子中的從底到高顯示的順序就是:黃->紅->藍(lán)->綠
7.4 在不同層疊上下文的父元素內(nèi)的情況
紅綠位于一個(gè) div.first-box 下,藍(lán)位于 div.second-box 下,紅綠藍(lán)都設(shè)置了 position:absolute,如果 first-box 的 z-index 設(shè)置的比 second-box 的大,那么此時(shí)無論藍(lán)的 z-index 設(shè)置的多大 z-index:999,藍(lán)都位于紅綠的下面;如果我們只更改紅綠的 z-index 值,由于這兩個(gè)元素都在父元素 first-box 產(chǎn)生的層疊上下文中,此時(shí)誰的 z-index 值大,誰在上面;
代碼和預(yù)覽參見:Codepen - 不同層疊上下文的父元素:https://codepen.io/SHERlocked93/pen/gdZbOJ
這個(gè)例子中,紅綠藍(lán)都屬于設(shè)置了 z-index 的定位元素,不過他們的父元素創(chuàng)建了新的層疊上下文;
所以這個(gè)例子中從低到到顯示的順序:藍(lán)->紅->綠
(我遇到的的情況就屬于這個(gè)例子類似情形)
7.5 給子元素設(shè)置 opacity
紅綠位于 div.first-box 下,藍(lán)位于 div.second-box 下,紅綠藍(lán)都設(shè)置了 position:absolute,綠設(shè)置了 z-index:1,那么此時(shí)綠位于紅藍(lán)的最上面;
如果此時(shí)給 first-box 設(shè)置 opacity:.99,這時(shí)無論紅綠的 z-index 設(shè)置的多大 z-index:999,藍(lán)都位于紅綠的上面;
如果再在 .second-box 下 .green 后加一個(gè) span.gold,設(shè)置 z-index:-1,那么它將位于紅綠藍(lán)的下面;
代碼和預(yù)覽參見:Codepen - opacity的影響:https://codepen.io/SHERlocked93/pen/GXPRWB
之前已經(jīng)介紹了,設(shè)置 opacity 也可以形成層疊上下文,因此:
所以這個(gè)例子中從低到到顯示的順序:黃->紅->綠->藍(lán)
關(guān)注微信公眾號(hào):安徽思恒信息科技有限公司,了解更多技術(shù)內(nèi)容……
篇文章主要講述了CSS樣式更改中的背景Background,這篇文章我們來談?wù)勛煮w設(shè)置Font&邊框Border的基礎(chǔ)用法。
<div style='font-family: sans-serif normal'></div>
可用字體:
Serif
Sans-serif
Monospace
Cursive
Fantasy
Times
Courier
<div style='font-style:normal'></div>
文本傾斜:
normal 文本正常顯示
italic 文本斜體顯示
oblique 文本傾斜顯示
<div style='font-variant:small-caps'></div>
normal 顯示標(biāo)準(zhǔn)字體。
small-caps 顯示小型大寫字母的字體。
<div style='font-weight:normal'></div>
normal 標(biāo)準(zhǔn)的字符
bold 粗體字符
bolder 更粗的字符
lighter 更細(xì)的字符
也可以使用數(shù)字表示,范圍為100~900
<div style='font-size:60px'></div>
smaller 變小
larger 變大
length 固定值
而且還支持百分比
首先說一下邊框風(fēng)格,它的風(fēng)格比較多,常用的一般是實(shí)線為主:
<div style='border-style:none'></div>
hidden 隱藏邊框
dotted 點(diǎn)狀邊框
dashed 虛線邊框
solid 實(shí)線邊框
double 雙線邊框
groove 3D凹槽邊框
ridge 3D壟狀邊框
inset 3D inset邊框
outset 3D outset邊框
邊框也有四面,所以也會(huì)有上下左右
所以有時(shí)候?yàn)榱烁_定位并修改樣式可以使用:
border-top-style 上邊框樣式
border-right-style 右邊框樣式
border-bottom-style 下邊框樣式
border-left-style 左邊框樣式
先定義邊框的寬度 風(fēng)格和顏色,然后定義邊框的其它屬性。
<div style='border-radius:25px;'></div>
2).邊框陰影
<div style='box-shadow:1px 2px 2px 2px red'></div>
參數(shù)含義:
邊框各個(gè)方向的大小和顏色
3).邊框圖片
<div style='border-image:url(1.png) 30 30 10 round'></div>
參數(shù)含義:
邊框圖片的路徑
圖片邊框向內(nèi)偏移
圖片邊框的寬度
邊框圖像區(qū)域超出邊框的量
圖像邊框是否應(yīng)平鋪(repeated)、鋪滿(rounded)或拉伸(stretched)。
這篇文章主要介紹了CSS樣式更改篇中的字體設(shè)置Font&邊框Border設(shè)置,希望讓大家對CSS選擇器有個(gè)簡單的認(rèn)識(shí)和了解。
****看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多的人****
IT共享之家
想要學(xué)習(xí)更多,請前往Python爬蟲與數(shù)據(jù)挖掘?qū)S镁W(wǎng)站:http://pdcfighting.com/
當(dāng)用戶進(jìn)行鼠標(biāo)框選選擇了頁面上的內(nèi)容時(shí),把選擇的內(nèi)容進(jìn)行上報(bào)。
雖然這需求就一句話的事,但是很顯然,沒那么簡單...
因?yàn)槭髽?biāo)框選說起來簡單,就是選擇的內(nèi)容,但是這包含很多中情況,比如:只選擇文案、選擇圖片、選擇輸入框、輸入框中的內(nèi)容選擇、iframe、等。
簡單總結(jié),分為以下幾點(diǎn):
鼠標(biāo)框選包含以下幾點(diǎn):
老生常談的技術(shù)點(diǎn)了,這里不能用節(jié)流,因?yàn)榭隙ú荒苣闶髽?biāo)選擇的時(shí)候,隔一段時(shí)間返回一段內(nèi)容,肯定是選擇之后一起返回。
這里用 debounce 主要也是用在事件監(jiān)聽和事件處理上。
事件監(jiān)聽,因?yàn)槭髽?biāo)選擇,不僅僅是鼠標(biāo)按下到鼠標(biāo)抬起,還包括雙擊、右鍵、全選。
需要使用事件監(jiān)聽對事件作處理。
Range 接口表示一個(gè)包含節(jié)點(diǎn)與文本節(jié)點(diǎn)的一部分的文檔片段。
Range 是瀏覽器原生的對象。
<body>
<ul>
<li>Vite</li>
<li>Vue</li>
<li>React</li>
<li>VitePress</li>
<li>NaiveUI</li>
</ul>
</body>
<script>
// 創(chuàng)建 Range 對象
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 對象
const selection = window.getSelection();
// 添加光標(biāo)選擇的范圍
selection.addRange(range);
</script>
可以看到,選擇內(nèi)容為第二行和第三行
只選擇 li 中的 itePres
可以看出 range 屬性對應(yīng)的值
const range = document.createRange();
const range = window.getSelection().getRangeAt(0)
if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(e.clientX, e.clientY);
}
const range = new Range()
Selection 對象表示用戶選擇的文本范圍或插入符號(hào)的當(dāng)前位置。它代表頁面中的文本選區(qū),可能橫跨多個(gè)元素。
window.getSelection()
錨指的是一個(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ì)變的。
選區(qū)的焦點(diǎn)是該選區(qū)的終點(diǎn),當(dāng)你用鼠標(biāo)框選一個(gè)選區(qū)的時(shí)候,焦點(diǎn)是你的鼠標(biāo)松開瞬間所記錄的那個(gè)點(diǎn)。隨著用戶拖動(dòng)鼠標(biāo),焦點(diǎn)的位置會(huì)隨著改變。
范圍指的是文檔中連續(xù)的一部分。一個(gè)范圍包括整個(gè)節(jié)點(diǎn),也可以包含節(jié)點(diǎn)的一部分,例如文本節(jié)點(diǎn)的一部分。用戶通常下只能選擇一個(gè)范圍,但是有的時(shí)候用戶也有可能選擇多個(gè)范圍。
一個(gè)用戶可編輯的元素(例如一個(gè)使用 contenteditable 的 HTML 元素,或是在啟用了 designMode 的 Document 的子元素)。
首先要清楚,選擇的起點(diǎn)稱為錨點(diǎn)(anchor),終點(diǎn)稱為焦點(diǎn)(focus)。
function debounce (fn, time = 500) {
let timeout = null; // 創(chuàng)建一個(gè)標(biāo)記用來存放定時(shí)器的返回值
return function () {
clearTimeout(timeout) // 每當(dāng)觸發(fā)時(shí),把前一個(gè) 定時(shí)器 clear 掉
timeout = setTimeout(() => { // 創(chuàng)建一個(gè)新的 定時(shí)器,并賦值給 timeout
fn.apply(this, arguments)
}, time)
}
}
/**
* 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)
}
}
}
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. 沒有選中
// 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)容里面有沒有節(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)容長度為 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
})
}
/**
* 獲取框選的文案內(nèi)容
* @returns {string} 返回框選的內(nèi)容
*/
const getSelectTextContent = (): string => {
const selection = window.getSelection()
return selection?.toString().trim() || ''
}
// 是否時(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)聽事件
document.addEventListener('mousedown', () => {
selectionchangeMouseTrack = true
})
// 添加 mouseup 監(jiān)聽事件
document.addEventListener(
'mouseup',
debounce(() => {
selectionChangeFun()
}, 100)
)
// 添加 selectionchange 監(jiān)聽事件
document.addEventListener(
'selectionchange',
debounce(() => {
if (selectionchangeMouseTrack) return
selectionChangeFun()
})
)
// 添加 dblclick 監(jiān)聽事件
document.addEventListener('dblclick', () => {
selectionChangeFun()
})
// 添加 contextmenu 監(jiān)聽事件
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])
})
function debounce (fn, time = 500) {
let timeout = null; // 創(chuàng)建一個(gè)標(biāo)記用來存放定時(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. 沒有選中
// 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)容里面有沒有節(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
})
}
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。