天小胖在coding的時候,遇到一個問題,就是要使用一個動態(tài)的文本作為div的背景,想著使用絕對定位堆疊來實現(xiàn),或者偽元素實現(xiàn),終歸不夠優(yōu)雅,直到看到張鑫旭大佬的這篇文章,只能說,很當優(yōu)雅!
在實際開發(fā)中,有時候希望文字內(nèi)容可以作為背景圖片顯示,一方面是希望利用背景圖片的優(yōu)勢,例如可以平鋪,另外一方面是常見的替換元素不能使用偽元素創(chuàng)建文字,此時只能寄希望與背景圖。
關(guān)鍵如何把文字變成背景圖呢?
通常CSS開發(fā)人員的做法是把文字導出來轉(zhuǎn)換成圖片,然后作為背景圖顯示,但是這樣成本有些高,也不利于日后的維護。
這里給大家介紹一種實用的技術(shù),可以讓文字作為CSS背景圖片。
SVG雖然是XML語言構(gòu)成的,但是本質(zhì)上就是一個圖像,是可以作為圖像使用的,例如:
<img src="zhangxinxu.svg">
此時的zhangxinxu.svg就是一個圖像,同樣的,也可以作為背景圖顯示,例如:
.example {
background: url(zhangxinxu.svg);
}
但是,這里的SVG文件都是獨立的SVG文件,和把文字導出成PNG圖片沒有任何區(qū)別,根本沒有意義嘛!
對的,請不用急,是這樣的,SVG作為一個矢量圖像,和通常的位圖有一點不一樣,那就是SVG圖像可以直接以源代碼的方式內(nèi)聯(lián)在Web頁面中。
關(guān)于這個特性,可以參考我之前的這篇文章:“學習了,CSS中內(nèi)聯(lián)SVG圖片有比Base64更好的形式 ? 張鑫旭-鑫空間-鑫生活”。
例如下面是一段顯示文本的SVG代碼:
<svg xmlns="http://www.w3.org/2000/svg">
<text>文字內(nèi)容</text>
</svg>
是可以直接作為background-image使用的,例如:
.by-zhangxinxu {
background-image: url('<svg xmlns="http://www.w3.org/2000/svg"><text>文字內(nèi)容</text></svg>');
}
由于安全性限制,目前需要對部分字符進行轉(zhuǎn)義,因此,實際的CSS代碼是這樣的:
.exmaple {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Ctext%3E文字內(nèi)容%3C/text%3E%3C/svg%3E");
}
相比PNG圖像的文字背景,使用SVG內(nèi)聯(lián)的好處在于,我們可以輕松修改文字的內(nèi)容,同時我們可以隨意設(shè)置文字的字號大小,顏色、描邊效果等等。
但是,每次都手寫一段SVG代碼好難哦,尤其很多前端小伙伴對SVG并不是很熟悉。
不要緊,考慮到這種情況存在,我專門做了一個生成SVG文字背景圖的工具。
您可以狠狠地點擊這里:文字轉(zhuǎn)SVG圖像在線轉(zhuǎn)換工具 ? 張鑫旭-鑫空間-鑫生活
界面截圖示意如下:
最上面是配置區(qū)域,可以設(shè)置作為背景的文字內(nèi)容,可以設(shè)置文字的顏色、透明度以及位置等信息。
中間是代碼區(qū),左側(cè)是原始SVG,可以點擊圖標下載SVG文件到本地;右側(cè)是轉(zhuǎn)義的可以直接內(nèi)聯(lián)使用的SVG代碼,HTML和CSS中均可內(nèi)聯(lián)使用。
最下面是預覽區(qū)域。
如果需要其他配置項
實際開發(fā)中的需求是千變?nèi)f化的,工具目前內(nèi)置的配置項不一定能覆蓋所有的場景,此時可以這么處理,直接修改左側(cè)文本域中的SVG代碼,此時右側(cè)的轉(zhuǎn)義SVG代碼會自動同步,例如,如果我們希望背景文字帶有旋轉(zhuǎn)效果,這樣可以作為水印圖片使用,則可以在已經(jīng)生成的SVG代碼中的元素上設(shè)置45度旋轉(zhuǎn)相關(guān)的代碼。
一種方法是直接在<svg>元素上設(shè)置傳統(tǒng)DOM元素的CSS style設(shè)置,例如:
另外一種方法就是<text>元素上使用SVG元素自動的transform屬性進行設(shè)置,但是SVG中的transform變換坐標和CSS是很不一樣的,直接<text transform="rotate(-45)">是不會有預期的旋轉(zhuǎn)效果的,因為默認SVG的變換中心點是左上角,因此,設(shè)置transform="rotate(-45)"會讓文字不可見。
關(guān)于SVG的transform變換坐標體系可以參考我之前寫的這篇文章:“理解SVG transform坐標變換”
<text>元素也能圍繞中心點變換有2個方法,一種是使用translate()函數(shù)先偏移、然后再旋轉(zhuǎn),然后在偏移復原,這種方法啰嗦了一點,另外一種方法就是使用SVG中rotate()函數(shù)的可選參數(shù),也就是第2個參數(shù),就是可以指定旋轉(zhuǎn)的中心點坐標,這個特性SVG獨有,Canvas中是沒有的。
代碼演示如下截圖所示:
其中,可以看到專門設(shè)置了SVG元素的width寬和height高,因為如果不設(shè)置,按照目前的CSS background-size的尺寸渲染規(guī)范,SVG的尺寸會采用容器的尺寸,rotate()函數(shù)的第2個參數(shù)就需要設(shè)置為容器元素的寬高的一半才能讓文字居中旋轉(zhuǎn)。
更新于 2022-01-25
dominant-baseline="middle" 只是近似垂直居中,如果 SVG 圖片高度較小,會看到往上偏移了一點距離,此時可以試試修改為 dominant-baseline="central"。
這里拋磚引玉,舉幾個使用文字作為背景圖的例子。
例如為防止截圖,會給頁面,或者聊天軟件背景等增加文字水印。
以前幾乎都是通過生成一個專門的PNG圖片實現(xiàn),現(xiàn)在可以直接代碼內(nèi)聯(lián),例如點擊下面這個按鈕,大家就可以看到我這篇文章的實時水印效果了。
不要害怕,用力點擊我
相關(guān)CSS代碼如下所示:
.target {
background: url("data:image/svg+xml,%3Csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3E%3Ctext x='50%25' y='50%25' font-size='14' fill-opacity='0.5' text-anchor='middle' dominant-baseline='middle' transform='rotate(-45, 100 100)'%3Ezhangxinxu.com%3C/text%3E%3C/svg%3E");
}
截圖效果如下所示:
常規(guī)的占位符都是在輸入框的左上方,或者右上方,如果我們希望提示的占位符在右下角,則就可以使用這里的文本圖像技術(shù)實現(xiàn)。
比方說下面這個多行文本域輸入框,當你輸入內(nèi)容,右下角的提示內(nèi)容就會消失,沒有內(nèi)容的時候就又會顯示(實時效果,可以親自體驗下)。
相關(guān)HTML和CSS代碼如下所示:
<textarea class="custom-placeholder" required></textarea>
.custom-placeholder {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Ctext x='100%25' y='96%25' font-size='12' fill='%23a2a9b6' font-family='system-ui, sans-serif' text-anchor='end'%3E富強民主文明和諧美麗%3C/text%3E%3C/svg%3E") no-repeat right 10px bottom 5px;
}
.custom-placeholder:valid {
background: none;
}
因為這里的文本是右對齊,下對齊,因此,設(shè)置text-anchor屬性值是end,同時x, y屬性值都是100%或者接近100%。
HTML5 <video>元素中的視頻如果因為地址錯誤等原因無法播放,是沒有辦法像普通元素那樣寫入一段錯誤提示文字,因為<video>元素是替換元素,寫在標簽里面的內(nèi)容都會被忽略。
此時,可以讓視頻播放出錯的時候以背景圖的形式顯示文字就可以了。
比方說下面這個實時例子,就是一個故意寫錯了地址的MP4視頻,大家可以看到“視頻無法預覽”的白色提示文字,就是使用這篇文章提供的技術(shù)實現(xiàn)的。
相關(guān)代碼如下所示:
<video src="xxx.png" type="video/mp4" width="360" height="240" onerror="this.classList.add('error')"></video>
video.error{
background: #000 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Ctext x='50%25' y='50%25' font-size='14' fill='%23ffffff' font-family='system-ui, sans-serif' text-anchor='middle' dominant-baseline='middle'%3E視頻無法預覽%3C/text%3E%3C/svg%3E") no-repeat center;
}
簡單易懂體驗絕佳老少皆宜。
如果想讓文字內(nèi)容直接作為背景圖片顯示,可以使用SVG元素作為中間橋梁實現(xiàn)。
關(guān)鍵問題是SVG元素的獲得不太容易,因此,我就專門給大家開發(fā)了一個工具,通過簡單配置實現(xiàn)想要的CSS文字背景代碼。
同時介紹了如果通過修改左側(cè)輸入框得到自定義的文字背景效果。
最后,介紹了3個具有代表性的案例,展示了文字作為背景圖片的一些妙用,拋磚引玉,希望可以啟發(fā)大家在實際開發(fā)中的應用。
OK,技術(shù)本身并不難,難的是當遇到類似場景的時候可以想到可以直接使用代碼實現(xiàn),而不是導出圖片。
好,以上就是本文的全部內(nèi)容,感謝您的閱讀,如果您覺得本文內(nèi)容還挺有幫助的,歡迎分享。
本文轉(zhuǎn)自張鑫旭大佬的分享,原文地址:如何讓文字作為CSS背景圖片顯示? ? 張鑫旭-鑫空間-鑫生活,張大佬經(jīng)常分享一些很不錯的css idea,推薦大家經(jīng)常刷刷他的博客,他的書也不錯,大家也可以買來讀一讀!
們可能看到過這樣的一個效果:頁面中的內(nèi)容滾動穿過某個固定元素時,會產(chǎn)生虛化的視覺效果,能看出文字的輪廓,但是卻無法看到具體的內(nèi)容,給人一種哎呦?不錯哦的感覺。
我們來拿element-plus官網(wǎng)作為案例,看看它是如何實現(xiàn)的,然后我們再自己照著實現(xiàn)一版。
比如正常的文字顯示是這樣的:
正常顯示
虛化之后看起來是這個樣子:
虛化一下
這個是element-plus官網(wǎng)中首頁的效果,頂部有一個固定高度的頭部橫條,當頁面滾動的時候,橫條下面的文字就會顯示成上面圖中虛化后的樣子。
首先來分析一下,如果是我們自己實現(xiàn)一個這樣的效果,該怎么做呢?看來這個頭部橫條不能是一個純白色的背景,因為要能看到文字,所以應該要有一個透明度,但是不能完全透明,因為它看起來不是那么的黑,然后我們觀察到有毛玻璃的效果,所以我們要給它模糊一下。
先來做一個最初的原圖,在這個基礎(chǔ)上嘗試幾種方案:
原圖
從上面的分析中我們可以知道,主要的手段就是:背景+模糊。
我們先來用第一種方案做一下:背景透明度+模糊
效果
它的代碼非常簡單
{
background-color: rgba(255,255,255,0.5);
backdrop-filter: blur(4px);
}
我們可以通過背景透明度和模糊的程度來調(diào)整視覺效果。
其中vite的官網(wǎng)就是這樣實現(xiàn)的,只是參數(shù)有所改變,我們來看下它的效果和代碼:
效果
{
backdrop-filter: saturate(50%) blur(8px);
background: rgba(255,255,255,.7);
}
我們再看elemen-plus官網(wǎng)中的第二種方案:背景圖+模糊
效果
它的代碼也很簡單,我們可以對它簡單的分析一下。
{
background-image: radial-gradient(transparent 1px,#ffffff 1px);
background-size: 4px 4px;
backdrop-filter: saturate(50%) blur(4px);
}
這里用到了徑向漸變,徑向漸變默認從中心向外擴散,其中transparent和#ffffff表示從透明過渡到純白,兩個1px的作用就是在1px處直接從透明變?yōu)榘咨划a(chǎn)生漸變的過渡效果,因此從中心到1px距離處都是透明的,也就是半徑為1px的圓內(nèi)都為透明色,從1px之外都是顯示成白色,它這里在實現(xiàn)的時候使用了單位區(qū)域4px的大小來繪制背景,然后通過背景重復的方式來平鋪整個元素。
可以這樣來理解,單位區(qū)域內(nèi),透明色為半徑是1px的圓的范圍,那么直徑就是2px,為了分布平均,因此左右和上下都加上了1px的純白,我們來設(shè)定一下background-repeat為no-repeat,看一下單位區(qū)域的效果,為了明顯我們把顏色改一下:
單位區(qū)域
這就是單位區(qū)域在頁面中左上角顯示的樣子,紅色就是原來的透明色,黑色就是原來的白色,為了看得明顯,我把頁面放大了500%,如果我把背景設(shè)置為重復:
背景重復
可以看到就是這個樣子,因此它后面的文字就會有一部分通過透明區(qū)域顯示出來:
顯示
這個時候我們再加上模糊效果,那么就會顯示成官網(wǎng)中的樣子了。
那么還有沒有其他的方法呢?
有!思路跟第二種類似,只不過我們不用徑向漸變,而是使用線性漸變。
我們可以看到,徑向漸變有誤傷的像素,4px的大小會覆蓋兩個像素點,使我們看不見后面的文字,而且圓形不能使文字完全露出來。
最好是一個像素看不見,一個像素能看見,這樣就像蒙了一層紗布一樣均勻。
要想實現(xiàn)這樣的效果,我們首先把單位區(qū)域設(shè)置成2px大小的正方形,然后借鑒一下CSS3 Patterns Gallary的鬼斧神工:
單位區(qū)域
我們通過設(shè)置透明色和黑色來展示樣子,同樣頁面放大了500%。代碼如下:
{
background-image: linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%, black), linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%, black);
background-size: 2px 2px;
background-position: 0 0, 1px 1px;
}
我們再把背景重復一下:
背景重復
這樣就變成了一個透明像素一個黑色像素。我們把黑色變成白色再看實際的文字顯示:
效果
可以看到,文字就好像每隔一個像素點都被掏空了一樣。
然后我們再加上之前用到的模糊,調(diào)整一下視覺效果:
效果
這樣就有了毛玻璃疊加紗布的視覺效果。完整代碼如下:
{
background-image: linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%, black), linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%, black);
background-size: 2px 2px;
background-position: 0 0, 1px 1px;
backdrop-filter: blur(4px);
}
原理不難,技術(shù)也是很簡單,都是一些好玩的css特性,實現(xiàn)的方式也很多。
創(chuàng)造力是無窮的,就看誰的花樣多!
我們開發(fā)系統(tǒng)的時候,可能會接到這樣的需求:不要讓用戶復制頁面上的文字或者圖片,不要讓用戶調(diào)試我們的頁面,更甚至也不要讓用戶進行打印操作等等。
聽起來是不是讓人很頭大,這咋實現(xiàn)啊?這有必要嗎?這能禁住么?
如果你沒做過這些,或者沒接到過這樣的需求,那你也應該看到過某個網(wǎng)站做了一些這樣的措施。
既然要做,我們就得想方案,先來看看禁止復制都有哪些方法。
假設(shè)我們有這樣一段代碼:
<div style="padding-left: 56px;">
<textarea rows="5" cols="33"></textarea>
</div>
<pre>
海客談瀛洲,煙濤微茫信難求,
越人語天姥,云霞明滅或可睹。
天姥連天向天橫,勢拔五岳掩赤城。
天臺四萬八千丈,對此欲倒東南傾。
我欲因之夢吳越,一夜飛度鏡湖月。
湖月照我影,送我至剡溪。
</pre>
接下來就通過這個例子來論述我們的方案:
x效果
這是一個css屬性,標識了元素及其子元素的文本不可被選中,因此設(shè)定之后,文本將不能夠被選中,因此也就不能復制:
<pre style="user-select: none;">
海客談瀛洲,煙濤微茫信難求,
越人語天姥,云霞明滅或可睹。
天姥連天向天橫,勢拔五岳掩赤城。
天臺四萬八千丈,對此欲倒東南傾。
我欲因之夢吳越,一夜飛度鏡湖月。
湖月照我影,送我至剡溪。
</pre>
我們在這段文本上,加上這個樣式。
效果
可以看到,文字壓根就不能選擇,從鼠標形狀也能看出來。
由于用在進行復制操作的時候,會觸發(fā)copy事件,我們可以通過監(jiān)聽它來做一些處理,使得復制的行為發(fā)生改變:
<div>
<div style="padding-left: 56px;">
<textarea rows="5" cols="33"></textarea>
</div>
<pre id="content">
海客談瀛洲,煙濤微茫信難求,
越人語天姥,云霞明滅或可睹。
天姥連天向天橫,勢拔五岳掩赤城。
天臺四萬八千丈,對此欲倒東南傾。
我欲因之夢吳越,一夜飛度鏡湖月。
湖月照我影,送我至剡溪。
</pre>
</div>
<script>
let c=document.getElementById('content')
c.removeEventListener("copy", copyFilter)
c.addEventListener("copy", copyFilter)
function copyFilter(e) {
let cp=e.clipboardData || window.clipboardData
if(!cp) {
return
}
let text=window.getSelection().toString()
if(text) {
e.preventDefault()
cp.setData("text/plain", "你復制了一段魔法")
}
}
</script>
先獲取到我們要禁止復制的元素,然后給它添加一個copy的事件監(jiān)聽,在添加監(jiān)聽之前,要先移除一下,這樣是為了避免局部刷新的時候重復添加,然后我們通過copyFilter函數(shù)來對這次操作進行處理。
先獲取剪貼板對象,如果當前事件對象里不存在,那就從window里面取,然后我們通過getSelection再拿到選取的內(nèi)容,因為我們對剪貼板對象進行修改,所以要阻止默認行為,然后把剪貼板的內(nèi)容重新賦值,可以是示例中那樣的一段文字,也可以設(shè)置為空,甚至是任意其他內(nèi)容,然后我們就可以看到產(chǎn)生的效果了:
效果
雖然能復制文本,但是由于我們攔截了復制操作,更改了它的行為,因此再粘貼的時候就變成了我們更改的樣子,也做到了禁止復制的功能。
這種方式對于使用快捷鍵或者右鍵的方式都是有效的。
這種情況主要是在可編輯區(qū)域,比如文本框、文本域、設(shè)置為contenteditable的元素等,用戶可以對文字進行剪切操作,雖然上面禁止了復制,但是剪切是另一個操作,不攔截的話還是相當于能復制出來。
copy和cut只是觸發(fā)的事件不同而已,但是它們都是執(zhí)行相同的邏輯處理:
<div>
<div style="padding-left: 56px;">
<textarea rows="5" cols="33"></textarea>
</div>
<pre id="content" contenteditable>
海客談瀛洲,煙濤微茫信難求,
越人語天姥,云霞明滅或可睹。
天姥連天向天橫,勢拔五岳掩赤城。
天臺四萬八千丈,對此欲倒東南傾。
我欲因之夢吳越,一夜飛度鏡湖月。
湖月照我影,送我至剡溪。
</pre>
</div>
<script>
let c=document.getElementById('content')
c.removeEventListener("cut", copyFilter)
c.addEventListener("cut", copyFilter)
function copyFilter(e) {
let cp=e.clipboardData || window.clipboardData
if(!cp) {
return
}
let text=window.getSelection().toString()
if(text) {
e.preventDefault()
cp.setData("text/plain", "你復制了一段魔法")
}
}
</script>
這里我為了方便,給元素添加了contenteditable屬性,讓它變成可編輯的,copyFilter函數(shù)沒有變化,我們只是添加了一個剪切事件的監(jiān)聽,然后它們的處理函數(shù)都是copyFilter。看下效果:
效果
可以看到,首先我們對文字進行剪切,沒有出現(xiàn)預期的效果,這時因為我們在代碼里面對剪切進行了攔截,并阻止了它的默認行為,然后我們在粘貼的時候,文字也改變成我們設(shè)置的了。
雖然我們可以通過上面的幾種方法禁止在頁面上復制,但是用戶也可能開啟打印預覽模式,在這種情況下,也是可以進行復制的,我們要想對打印頁面進行一些控制,那么就要用到媒體查詢,先看下打印的樣子:
效果
雖然我們做了限制,但是在打印頁面沒有生效,現(xiàn)在我們針對這個場景更改一下代碼:
@media print {
html {
display: none;
}
}
通過添加上面這個樣式規(guī)則,我們能夠使頁面在打印的時候,內(nèi)容隱藏起來,這樣就無法進行復制了:
效果
能夠看到,點擊打印的時候,預覽頁面一片空白,,這樣就禁止了在打印頁面進行復制的操作。當然了,你其實也可以設(shè)置其他的樣式屬性來做些控制,但要記住寫在打印的媒體查詢里面,只有這樣才會在打印頁面生效。
還有一種方式就是,通過設(shè)定一個偽元素,讓它全面覆蓋文本內(nèi)容,這樣鼠標就不能選到實際的文本,改造一下代碼:
.content {
position: relative;
}
.content::before {
content: '';
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
<pre id="content" class="content">
海客談瀛洲,煙濤微茫信難求,
越人語天姥,云霞明滅或可睹。
天姥連天向天橫,勢拔五岳掩赤城。
天臺四萬八千丈,對此欲倒東南傾。
我欲因之夢吳越,一夜飛度鏡湖月。
湖月照我影,送我至剡溪。
</pre>
現(xiàn)在就不能在元素上面選中文字了,不過用戶也可能還有一些操作,比如在內(nèi)容區(qū)域外面ctrl+a全選,或者在外面拖動鼠標來全選,如果是這種情形,那么我們可以通過監(jiān)聽鍵盤和鼠標事件來禁止全選等操作。
由于用戶有很多種操作的方式,鍵盤全選、鼠標全選、鍵盤右鍵、鼠標右鍵等等,我們?nèi)绻F舉的話,情況太多了,因此我們只監(jiān)聽鼠標按下和抬起事件,以及鍵盤的按下事件:
document.removeEventListener("mousedown", haveSelect)
document.addEventListener("mousedown", haveSelect)
document.removeEventListener("mouseup", haveSelect)
document.addEventListener("mouseup", haveSelect)
document.removeEventListener("keydown", haveSelect)
document.addEventListener("keydown", haveSelect)
function haveSelect() {
window.getSelection().removeAllRanges()
}
主要就是在removeAllRanges方法上面,能夠在觸發(fā)上面事件的時候,將所選區(qū)域清空,也就是不管你選沒選,咋選的,反正就是你只要進行了操作,那我就那可能選擇的區(qū)域給你清空,這樣你就啥也干不了了。
效果
好,這樣就可以啦,無論怎么選,即使出現(xiàn)了選區(qū),但是只要你再按了鼠標或者鍵盤,那么選區(qū)就會直接消失,就能達到不能復制的效果,因為你發(fā)現(xiàn)啥都做不了。
這里額外說明一下,對于事件的監(jiān)聽,一定要用addEventListener來實現(xiàn),因為它會將多個綁定的事件都添加上去,當觸發(fā)的時候就會按照綁定的順序進行執(zhí)行,如果是用賦值的方式,那么后面的會覆蓋前面的,而且賦值的方式很容易被篡改,可以很輕松的讓你的綁定函數(shù)不能執(zhí)行從而失效,而使用addEventListener就不會被人為覆蓋,只能通過綁定的函數(shù)句柄來手動移除,也就是說要移除的時候,必須使用跟綁定時使用同一個函數(shù)才行。
通過CSS的方式禁止復制,可以很容易的被用戶解除,只能是設(shè)置的稍微復雜一點,增加難度。而通過JS的方式禁止復制,也可以通過禁用頁面JavaScript代碼來解除,因此我們可以將內(nèi)容通過js來渲染,這樣如果頁面禁用了js,那么內(nèi)容也不會渲染。
對于禁止調(diào)試,主要是指用戶打開控制臺,控制臺也就是開發(fā)者工具,我為了方便稱之為控制臺,想要對頁面進行調(diào)試時,我們做一些處理,阻止這種行為,最大可能的攔截通過控制臺對系統(tǒng)的調(diào)試。
主要的方法有幾種,由于打開控制臺是瀏覽器提供的調(diào)試功能,因此我們沒法攔截打開操作,即使通過事件監(jiān)聽不允許快捷鍵這樣做,但是也可以通過其他方式進行打開,因此我們的主要關(guān)注點就在于打開控制臺之后,我們能做哪些事情來限制用戶行為。
打開控制臺的快捷鍵主要有F12和ctrl+shift+i,我們先把這倆給禁用了:
document.removeEventListener("keydown", disableDevShortcut)
document.addEventListener("keydown", disableDevShortcut)
function disableDevShortcut(e) {
console.log(e)
if(e.keyCode===123) {
e.preventDefault()
}else if(e.keyCode===73 && e.ctrlKey && e.shiftKey) {
e.preventDefault()
}
}
這樣在使用這兩個快捷鍵的時候,頁面沒有任何反應,控制臺也不會喚起,因為我們阻止了它們的默認行為。
除了通過快捷鍵,還可以使用右鍵的方式,并點擊檢查也會調(diào)出控制臺。
效果
這種情景,我們可以通過禁止在頁面上使用右鍵的方式,來阻止打開控制臺:
document.removeEventListener("contextmenu", cancelContextmenu)
document.addEventListener("contextmenu", cancelContextmenu)
function cancelContextmenu(e) {
e.preventDefault()
}
現(xiàn)在就不能通過右鍵打開控制臺了,但是相應的整個右鍵功能也都不能使用了。
如果用戶最終打開了控制臺,比如通過在瀏覽器的更多功能中來打開的話,那么我就需要采取其他的措施,其中之一就是給代碼設(shè)置無限斷點,因為斷點只在控制臺打開的時候才會發(fā)生作用,從而不必擔心非調(diào)試模式下的程序正常運行。
無限斷點的主要思路就是利用定時器等手段,頻繁的觸發(fā)斷點效果,使得不能輕松的調(diào)試程序,先看下代碼:
;(()=> {
function breakDebugger() {
if(new checkDebugger().check) {
breakDebugger()
}
}
function checkDebugger() {
const now=new Date();
eval('(function () {debugger;false;})()')
const dur=Date.now() - now
if(dur < 5) {
return {check: false}
}else {
return {check: true}
}
}
setInterval(()=> {
eval('(function () {debugger;true;})()')
breakDebugger()
}, 500)
})()
我們利用一個立即執(zhí)行的自執(zhí)行函數(shù),來使我們的代碼被封裝在一個固定塊內(nèi),不與其他部分有任何影響。
這里主要做了兩步:
第一步設(shè)置一個重復執(zhí)行的定時器,其中包括了一個斷點和一個函數(shù)調(diào)用。
第二步通過函數(shù)來遞歸調(diào)用斷點,主要使用了實例化對象的方式和時間差的判斷。
這樣做的主要作用就是在設(shè)置無限斷點的同時,也能夠讓每次的斷點都是被重新生成的,看下效果,一目了然:
效果
發(fā)現(xiàn)沒有,我們通過這種方式,只要打開了控制臺,那么就會進入到無限斷點的循環(huán)中,使得不能做任何其他事情,而且每個斷點的生成都會開辟一個新的虛擬運行環(huán)境,這種情況下,只有關(guān)閉控制臺,才能結(jié)束斷點。
即使使用右鍵選擇Never parse here,也毫無作用,雖然可以通過Deactive breakpoints按鈕來徹底禁用斷點,就是下面這個按鈕:
按鈕
但是,如果這樣做的話,那么用戶也就同時失去了調(diào)試其他代碼的能力。
況且,我們接下來還會介紹其他的控制手段,可以配合著使用。
我們?nèi)绻苡幸环N手段,可以知道用戶開啟了控制臺,換句話說只要控制臺被打開,就通知我們或者被我們監(jiān)測到,那么我們就可以執(zhí)行一些控制手段,這種效果肯定是很理想的,遺憾的是還沒有這種api暴露給我們?nèi)プ屛覀兡軌蜻@樣做。
不過我們可以通過其他的方式,利用既有的一些能力來實現(xiàn)這一點,這里我還是使用循環(huán)定時器,來不斷的去嗅探用戶是否開啟了控制臺,直接看代碼:
;(()=> {
setInterval(function() {
let foo=document.createElement('a')
let a1=+new Date()
console.table(foo)
let a2=+new Date()
if(a2 - a1 > 1) {
location.href='about:blank'
}
console.clear()
}, 500)
})()
同樣,通過一個自執(zhí)行函數(shù),我們開啟了一個循環(huán)定時器,然后在回調(diào)方法里面,我們就去實現(xiàn)上面的目標,也是分為了兩步:
第一步創(chuàng)建一個a元素,然后通過表格的形式將它打印出來,并記錄下消耗的時間。
第二步判斷耗時的長短來控制是否跳轉(zhuǎn)到空白頁,然后清空控制臺。
這種方式主要是利用了console.table的特性,它會將元素以表格的形式輸出到控制臺,大概就像下面的樣子:
效果
由于太多了,我就沒有全部截下來,如果沒有打開控制臺的話,使用console.table輸出我們創(chuàng)建的a標簽是很快的,有多快呢,就是js執(zhí)行一條語句的速度,所以打印a1和a2的時間間隔非常短,幾乎為0,因為他們快到差不多是同時執(zhí)行的,給大家打印看一下:
效果
我們先不打開控制臺,等輸出完畢再打開,很清楚的發(fā)現(xiàn),我們沒打開控制臺的時候,輸出的a就是它標簽,而且時間間隔是0毫秒。
現(xiàn)在我們打開控制臺的時候刷新一下頁面,看看控制臺的輸出:
效果
這次就變成了以table的形式輸出a元素,而且它的耗時明顯增多,不再是0毫秒,而是耗費了10毫秒,雖然打開控制臺的時候多次刷新頁面,每次輸出的毫秒數(shù)是不同的,但是跟關(guān)閉控制臺的時候輸出的耗時差距非常明顯,因此我們就可以在這個上面做文章。
我在上面的代碼中假定了,只要是大于1毫秒的耗時,那就表示用戶打開了控制臺,然后我們就把頁面給跳轉(zhuǎn)到空白頁,當然了你也可以做一些任何你想做的操作,比如彈出一個提示,或者把body內(nèi)容置空等等等等。
回到我們上面的代碼,看一下它實際發(fā)生的作用和帶給我們的效果:
效果
哈哈,古德古德,平時瀏覽一切正常,只要剛一打開控制臺,瞬間頁面就被跳轉(zhuǎn)走了,什么都干不了。這樣我們就通過這種方式,達到了限制打開控制臺的目的,也就是在當前頁沒法調(diào)試,一打開就跳轉(zhuǎn)。
這種辦法由于是繞路實現(xiàn)的,那么你可能會有疑問,它穩(wěn)定嗎?會不會誤判,我可以對它絕對放心嗎?
理論上來說,通過輸出的執(zhí)行時間是不太能精確掌握的,但是我們可以再做一些其他的措施來逼近真相:
;(()=> {
setInterval(function() {
let foo=document.createElement('a')
let a1=+new Date()
console.table(foo)
let a2=+new Date()
if(a2 - a1 > 1) {
let time=0
for(let i=0; i < 10; i++) {
let a1=+new Date()
console.table(foo)
let a2=+new Date()
time +=a2 - a1
}
if(time > 20) {
location.href='about:blank'
}
}
console.clear()
}, 500)
})()
我又改造了一下判斷的邏輯,當發(fā)現(xiàn)輸出耗時為2毫秒甚至更多的時候,我立馬再進行一次真?zhèn)闻袛啵簿褪钦f,萬一由于其他的影響,導致我第7行的代碼誤判了,那么我再同步執(zhí)行一個循環(huán),連續(xù)輸出10次,把他們的耗時總和計算出來,然后判斷是否大于20毫秒,如果還是耗時過高的話,那么就可以非常肯定的知道用戶是打開了控制臺,這個時候就可以放心的做一些處理了。
其實限制用戶行為的方法有很多很多,上面列出了一些主要的,多種方法還是要結(jié)合著使用。你也可以自由發(fā)揮,多使用一些其他的手段,也會增加用戶復制或者調(diào)試的難度,比如防止用戶重寫console的方法,或者清除所有定時器等。
甚至也可以將你的內(nèi)容繪制到canvas上面來防止復制,多加一些js的處理工作,防止禁用js的時候,我們的代碼不生效,只有在js可用的時候再去渲染內(nèi)容等。也可以在綜合考慮的情況下加上代碼混淆、代碼加密等措施。
話說回來,大家都是同路人,何必相互為難,哈哈哈,不過提這個需求的人也著實會為難我們,既然提了那就盡力去做,能做到什么程度,只能說是盡量做到極致。
希望上面的內(nèi)容能夠幫助到你,也希望能夠?qū)δ阌兴鶈l(fā)。
謝謝
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。