亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

IntersectionObserver API 詳解篇

 更新時(shí)間:2016年12月11日 20:57:00   投稿:mdxy-dxy  
這篇文章主要介紹了IntersectionObserver API 詳解篇,需要的朋友可以參考下

溫馨提示:本文目前僅適用于在 Chrome 51 及以上中瀏覽。

2016.11.1 追加,F(xiàn)irefox 52 也已經(jīng)實(shí)現(xiàn)。

2016.11.29 追加,F(xiàn)irefox 的人擔(dān)心目前規(guī)范不夠穩(wěn)定,未來很難保證向后兼容,所以禁用了這個(gè) API,需要手動(dòng)打開 dom.IntersectionObserver.enabled 才行。

IntersectionObserver API 是用來監(jiān)視某個(gè)元素是否滾動(dòng)進(jìn)了瀏覽器窗口的可視區(qū)域(視口)或者滾動(dòng)進(jìn)了它的某個(gè)祖先元素的可視區(qū)域內(nèi)。它的主要功能是用來實(shí)現(xiàn)延遲加載和展現(xiàn)量統(tǒng)計(jì)。先來看一段視頻簡介:

再來看看名字,名字里第一個(gè)單詞 intersection 是交集的意思,小時(shí)候數(shù)學(xué)里面就學(xué)過:

不過在網(wǎng)頁里,元素都是矩形的:

第二個(gè)單詞 observer 是觀察者的意思,和 MutationObserver 以及已死的 Object.observe 中的 observe(r) 一個(gè)意思。

下面列出了這個(gè) API 中所有的參數(shù)、屬性、方法:

// 用構(gòu)造函數(shù)生成觀察者實(shí)例
let observer = new IntersectionObserver((entries, observer) => {
 // 回調(diào)函數(shù)中可以拿到每次相交發(fā)生時(shí)所產(chǎn)生的交集的信息
 for (let entry of entries) {
 console.log(entry.time)
 console.log(entry.target)
 console.log(entry.rootBounds)
 console.log(entry.boundingClientRect
 console.log(entry.intersectionRect)
 console.log(entry.intersectionRatio)
 }
}, { // 構(gòu)造函數(shù)的選項(xiàng)
 root: null,
 threshold: [0, 0.5, 1],
 rootMargin: "50px, 0px"
})

// 實(shí)例屬性
observer.root
observer.rootMargin
observer.thresholds

// 實(shí)例方法
observer.observe()
observer.unobserve()
observer.disconnect()
observer.takeRecords()

然后分三小節(jié)詳細(xì)介紹它們:

構(gòu)造函數(shù)

new IntersectionObserver(callback, options)

callback 是個(gè)必選參數(shù),當(dāng)有相交發(fā)生時(shí),瀏覽器便會(huì)調(diào)用它,后面會(huì)詳細(xì)介紹;options 整個(gè)參數(shù)對(duì)象以及它的三個(gè)屬性都是可選的:

root

IntersectionObserver API 的適用場(chǎng)景主要是這樣的:一個(gè)可以滾動(dòng)的元素,我們叫它根元素,它有很多后代元素,想要做的就是判斷它的某個(gè)后代元素是否滾動(dòng)進(jìn)了自己的可視區(qū)域范圍。這個(gè) root 參數(shù)就是用來指定根元素的,默認(rèn)值是 null。

如果它的值是 null,根元素就不是個(gè)真正意義上的元素了,而是這個(gè)瀏覽器窗口了,可以理解成 window,但 window 也不是元素(甚至不是節(jié)點(diǎn))。這時(shí)當(dāng)前窗口里的所有元素,都可以理解成是 null 根元素的后代元素,都是可以被觀察的。

下面這個(gè) demo 演示了根元素為 null 的用法:

<div id="info">我藏在頁面底部,請(qǐng)向下滾動(dòng)</div>
<div id="target"></div>

<style>
 #info {
 position: fixed;
 }

 #target {
 position: absolute;
 top: calc(100vh + 500px);
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => {
 if (!target.isIntersecting) {
 info.textContent = "我出來了"
 target.isIntersecting = true
 } else {
 info.textContent = "我藏在頁面底部,請(qǐng)向下滾動(dòng)"
 target.isIntersecting = false
 }
 }, {
 root: null // null 的時(shí)候可以省略
 })

 observer.observe(target)
</script>

需要注意的是,這里我通過在 target 上添加了個(gè)叫 isIntersecting 的屬性來判斷它是進(jìn)來還是離開了,為什么這么做?先忽略掉,下面會(huì)有一小節(jié)專門解釋。

根元素除了是 null,還可以是目標(biāo)元素任意的祖先元素:

<div id="root">
 <div id="info">向下滾動(dòng)就能看到我</div>
 <div id="target"></div>
</div>

<style>
 #root {
 position: relative;
 width: 200px;
 height: 100vh;
 margin: 0 auto;
 overflow: scroll;
 border: 1px solid #ccc;
 }
 
 #info {
 position: fixed;
 }
 
 #target {
 position: absolute;
 top: calc(100vh + 500px);
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => {
 if (!target.isIntersecting) {
 info.textContent = "我出來了"
 target.isIntersecting = true
 } else {
 info.textContent = "向下滾動(dòng)就能看到我"
 target.isIntersecting = false
 }
 }, {
 root: root
 })

 observer.observe(target)
</script>

需要注意的一點(diǎn)是,如果 root 不是 null,那么相交區(qū)域就不一定在視口內(nèi)了,因?yàn)?root 和 target 的相交也可能發(fā)生在視口下方,像下面這個(gè) demo 所演示的:

<div id="root">
 <div id="info">慢慢向下滾動(dòng)</div>
 <div id="target"></div>
</div>

<style>
 #root {
 position: relative;
 width: 200px;
 height: calc(100vh + 500px);
 margin: 0 auto;
 overflow: scroll;
 border: 1px solid #ccc;
 }
 
 #info {
 position: fixed;
 }
 
 #target {
 position: absolute;
 top: calc(100vh + 1000px);
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => {
 if (!target.isIntersecting) {
 info.textContent = "我和 root 相交了,但你還是看不見"
 target.isIntersecting = true
 } else {
 info.textContent = "慢慢向下滾動(dòng)"
 target.isIntersecting = false
 }
 }, {
 root: root
 })

 observer.observe(target)
</script>

總結(jié)一下:這一小節(jié)我們講了根元素的兩種類型,null 和任意的祖先元素,其中 null 值表示根元素為當(dāng)前窗口(的視口)。

threshold

當(dāng)目標(biāo)元素和根元素相交時(shí),用相交的面積除以目標(biāo)元素的面積會(huì)得到一個(gè) 0 到 1(0% 到 100%)的數(shù)值:

下面這句話很重要,IntersectionObserver API 的基本工作原理就是:當(dāng)目標(biāo)元素和根元素相交的面積占目標(biāo)元素面積的百分比到達(dá)或跨過某些指定的臨界值時(shí)就會(huì)觸發(fā)回調(diào)函數(shù)。threshold 參數(shù)就是用來指定那個(gè)臨界值的,默認(rèn)值是 0,表示倆元素剛剛挨上就觸發(fā)回調(diào)。有效的臨界值可以是在 0 到 1 閉區(qū)間內(nèi)的任意數(shù)值,比如 0.5 表示當(dāng)相交面積占目標(biāo)元素面積的一半時(shí)觸發(fā)回調(diào)。而且可以指定多個(gè)臨界值,用數(shù)組形式,比如 [0, 0.5, 1],表示在兩個(gè)矩形開始相交,相交一半,完全相交這三個(gè)時(shí)刻都要觸發(fā)一次回調(diào)函數(shù)。如果你傳了個(gè)空數(shù)組,它會(huì)給你自動(dòng)插入 0,變成 [0],也等效于默認(rèn)值 0。

<

下面的動(dòng)畫演示了當(dāng) threshold 參數(shù)為 [0, 0.5, 1] 時(shí),向下滾動(dòng)頁面時(shí)回調(diào)函數(shù)是在何時(shí)觸發(fā)的:

不僅當(dāng)目標(biāo)元素從視口外移動(dòng)到視口內(nèi)時(shí)會(huì)觸發(fā)回調(diào),從視口內(nèi)移動(dòng)到視口外也會(huì):

你可以在這個(gè) demo 里驗(yàn)證上面的兩個(gè)動(dòng)畫:

你可以在這個(gè) demo 里驗(yàn)證上面的兩個(gè)動(dòng)畫:

<div id="info">
 慢慢向下滾動(dòng),相交次數(shù):
 <span id="times">0</span>
</div>
<div id="target"></div>

<style>
 #info {
 position: fixed;
 }
 
 #target {
 position: absolute;
 top: 200%;
 width: 100px;
 height: 100px;
 background: red;
 margin-bottom: 100px;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => {
 times.textContent = +times.textContent + 1
 }, {
 threshold: [0, 0.5, 1]
 })

 observer.observe(target)
</script>

threshold 數(shù)組里的數(shù)字的順序沒有強(qiáng)硬要求,為了可讀性,最好從小到大書寫。如果指定的某個(gè)臨界值小于 0 或者大于 1,瀏覽器會(huì)報(bào)錯(cuò):

<script>
new IntersectionObserver(() => {}, {
 threshold: 2 // SyntaxError: Failed to construct 'Intersection': Threshold values must be between 0 and 1.
})
</script> 

rootMagin

本文一開始就說了,這個(gè) API 的主要用途之一就是用來實(shí)現(xiàn)延遲加載,那么真正的延遲加載會(huì)等 img 標(biāo)簽或者其它類型的目標(biāo)區(qū)塊進(jìn)入視口才執(zhí)行加載動(dòng)作嗎?顯然,那就太遲了。我們通常都會(huì)提前幾百像素預(yù)先加載,rootMargin 就是用來干這個(gè)的。rootMargin 可以給根元素添加一個(gè)假想的 margin,從而對(duì)真實(shí)的根元素區(qū)域進(jìn)行縮放。比如當(dāng) root 為 null 時(shí)設(shè)置 rootMargin: "100px",實(shí)際的根元素矩形四條邊都會(huì)被放大 100px,像這樣:

效果可以想象到,如果 threshold 為 0,那么當(dāng)目標(biāo)元素距離視口 100px 的時(shí)候(無論哪個(gè)方向),回調(diào)函數(shù)就提前觸發(fā)了??紤]到常見的頁面都沒有橫向滾動(dòng)的需求,rootMargin 參數(shù)的值一般都是 "100px 0px",這種形式,也就是左右 margin 一般都是 0px. 下面是一個(gè)用 IntersectionObserver 實(shí)現(xiàn)圖片在距視口 500px 的時(shí)候延遲加載的 demo:

<div id="info">圖片在頁面底部,仍未加載,請(qǐng)向下滾動(dòng)</div>
<img id="img" src="data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA="
  data-src="https://img.alicdn.com/bao/uploaded/i7/TB1BUK4MpXXXXa1XpXXYXGcGpXX_M2.SS2">

<style>
 #info {
 position: fixed;
 }

 #img {
 position: absolute;
 top: 300%;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => {
 observer.unobserve(img)
 info.textContent = "開始加載圖片!"
 img.src = img.dataset.src
 }, {
 rootMargin: "500px 0px"
 })

 observer.observe(img)
</script>

注意 rootMargin 的值雖然和 CSS 里 margin 的值的格式一樣,但存在一些限制,rootMargin 只能用 px 和百分比兩種單位,用其它的單位會(huì)報(bào)錯(cuò),比如用 em:

<script>
new IntersectionObserver(() => {}, {
 rootMargin: "10em" // SyntaxError: Failed to construct 'Intersection': rootMargin must be specified in pixels or percent.
})
</script>

rootMargin 用百分比的話就是相對(duì)根元素的真實(shí)尺寸的百分比了,比如 rootMargin: "0px 0px 50% 0px",表示根元素的尺寸向下擴(kuò)大了 50%。

如果使用了負(fù) margin,真實(shí)的根元素區(qū)域會(huì)被縮小,對(duì)應(yīng)的延遲加載就會(huì)延后,比如用了 rootMargin: "-100px" 的話,目標(biāo)元素滾動(dòng)進(jìn)根元素可視區(qū)域內(nèi)部 100px 的時(shí)候才有可能觸發(fā)回調(diào)。

實(shí)例

實(shí)例屬性

root

該觀察者實(shí)例的根元素(默認(rèn)值為 null):

new IntersectionObserver(() => {}).root // null
new IntersectionObserver(() => {}, {root: document.body}).root // document.body

rootMargin

rootMargin 參數(shù)(默認(rèn)值為 "0px")經(jīng)過序列化后的值:

new IntersectionObserver(() => {}).rootMargin // "0px 0px 0px 0px"
new IntersectionObserver(() => {}, {rootMargin: "50px"}).rootMargin // "50px 50px 50px 50px"
new IntersectionObserver(() => {}, {rootMargin: "50% 0px"}).rootMargin // "50% 0px 50% 0px"
new IntersectionObserver(() => {}, {rootMargin: "50% 0px 50px"}).rootMargin // 50% 0px 50px 0px" 
new IntersectionObserver(() => {}, {rootMargin: "1px 2px 3px 4px"}).rootMargin // "1px 2px 3px 4px"

thresholds

threshold 參數(shù)(默認(rèn)值為 0)經(jīng)過序列化后的值,即便你傳入的是一個(gè)數(shù)字,序列化后也是個(gè)數(shù)組,目前 Chrome 的實(shí)現(xiàn)里數(shù)字的精度會(huì)有丟失,但無礙:

new IntersectionObserver(() => {}).thresholds // [0]
new IntersectionObserver(() => {}, {threshold: 1}).thresholds // [1]
new IntersectionObserver(() => {}, {threshold: [0.3, 0.6]}).thresholds // [[0.30000001192092896, 0.6000000238418579]]
Object.isFrozen(new IntersectionObserver(() => {}).thresholds) // true, 是個(gè)被 freeze 過的數(shù)組

這三個(gè)實(shí)例屬性都是用來標(biāo)識(shí)一個(gè)觀察者實(shí)例的,都是讓人來讀的,在代碼中沒有太大用途。

實(shí)例方法

observe()

觀察某個(gè)目標(biāo)元素,一個(gè)觀察者實(shí)例可以觀察任意多個(gè)目標(biāo)元素。注意,這里可能有同學(xué)會(huì)問:能不能 delegate?能不能只調(diào)用一次 observe 方法就能觀察一個(gè)頁面里的所有 img 元素,甚至那些未產(chǎn)生的?答案是不能,這不是事件,沒有冒泡。

unobserve()

取消對(duì)某個(gè)目標(biāo)元素的觀察,延遲加載通常都是一次性的,observe 的回調(diào)里應(yīng)該直接調(diào)用 unobserve() 那個(gè)元素.

disconnect()

取消觀察所有已觀察的目標(biāo)元素

takeRecords()

理解這個(gè)方法需要講點(diǎn)底層的東西:在瀏覽器內(nèi)部,當(dāng)一個(gè)觀察者實(shí)例在某一時(shí)刻觀察到了若干個(gè)相交動(dòng)作時(shí),它不會(huì)立即執(zhí)行回調(diào),它會(huì)調(diào)用 window.requestIdleCallback() (目前只有 Chrome 支持)來異步的執(zhí)行我們指定的回調(diào)函數(shù),而且還規(guī)定了最大的延遲時(shí)間是 100 毫秒,相當(dāng)于瀏覽器會(huì)執(zhí)行:

requestIdleCallback(() => {
 if (entries.length > 0) {
 callback(entries, observer)
 }
}, {
 timeout: 100
})

你的回調(diào)可能在隨后 1 毫秒內(nèi)就執(zhí)行,也可能在第 100 毫秒才執(zhí)行,這是不確定的。在這不確定的 100 毫秒之間的某一刻,假如你迫切需要知道這個(gè)觀察者實(shí)例有沒有觀察到相交動(dòng)作,你就得調(diào)用 takeRecords() 方法,它會(huì)同步返回包含若干個(gè) IntersectionObserverEntry 對(duì)象的數(shù)組(IntersectionObserverEntry 對(duì)象包含每次相交的信息,在下節(jié)講),如果該觀察者實(shí)例此刻并沒有觀察到相交動(dòng)作,那它就返回個(gè)空數(shù)組。

注意,對(duì)于同一個(gè)相交信息來說,同步的 takeRecords() 和異步的回調(diào)函數(shù)是互斥的,如果回調(diào)先執(zhí)行了,那么你手動(dòng)調(diào)用 takeRecords() 就必然會(huì)拿到空數(shù)組,如果你已經(jīng)通過 takeRecords() 拿到那個(gè)相交信息了,那么你指定的回調(diào)就不會(huì)被執(zhí)行了(entries.length > 0 是 false)。

這個(gè)方法的真實(shí)使用場(chǎng)景很少,我舉不出來,我只能寫出一個(gè)驗(yàn)證上面兩段話(時(shí)序無規(guī)律)的測(cè)試代碼:

<script>
 setInterval(() => {
 let observer = new IntersectionObserver(entries => {
 if (entries.length) {
 document.body.innerHTML += "<p>異步的 requestIdleCallback() 回調(diào)先執(zhí)行了"
 }
 })

 requestAnimationFrame(() => {
 setTimeout(() => {
 if (observer.takeRecords().length) {
  document.body.innerHTML += "<p>同步的 takeRecords() 先執(zhí)行了"
 }
 }, 0)
 })

 observer.observe(document.body)

 scrollTo(0, 1e10)
 }, 100)
</script>

回調(diào)函數(shù)

new IntersectionObserver(function(entries, observer) {
 for (let entry of entries) {
 console.log(entry.time)
 console.log(entry.target)
 console.log(entry.rootBounds)
 console.log(entry.boundingClientRect
 console.log(entry.intersectionRect)
 console.log(entry.intersectionRatio)
 }
})

回調(diào)函數(shù)共有兩個(gè)參數(shù),第二個(gè)參數(shù)就是觀察者實(shí)例本身,一般沒用,因?yàn)閷?shí)例通常我們已經(jīng)賦值給一個(gè)變量了,而且回調(diào)函數(shù)里的 this 也是那個(gè)實(shí)例。第一個(gè)參數(shù)是個(gè)包含有若干個(gè) IntersectionObserverEntry 對(duì)象的數(shù)組,也就是和 takeRecords() 方法的返回值一樣。每個(gè) IntersectionObserverEntry 對(duì)象都代表一次相交,它的屬性們就包含了那次相交的各種信息。entries 數(shù)組中 IntersectionObserverEntry 對(duì)象的排列順序是按照它所屬的目標(biāo)元素當(dāng)初被 observe() 的順序排列的。

time

相交發(fā)生時(shí)距離頁面打開時(shí)的毫秒數(shù)(有小數(shù)),也就是相交發(fā)生時(shí) performance.now() 的返回值,比如 60000.560000000005,表示是在頁面打開后大概 1 分鐘發(fā)生的相交。在回調(diào)函數(shù)里用 performance.now() 減去這個(gè)值,就能算出回調(diào)函數(shù)被 requestIdleCallback 延遲了多少毫秒:

<script>
 let observer = new IntersectionObserver(([entry]) => {
 document.body.textContent += `相交發(fā)生在 ${performance.now() - entry.time} 毫秒前`
 })

 observer.observe(document.documentElement)
</script>

你可以不停刷新上面這個(gè) demo,那個(gè)毫秒數(shù)最多 100 出頭,因?yàn)闉g覽器內(nèi)部設(shè)置的最大延遲就是 100。

target

相交發(fā)生時(shí)的目標(biāo)元素,因?yàn)橐粋€(gè)根元素可以觀察多個(gè)目標(biāo)元素,所以這個(gè) target 不一定是哪個(gè)元素。

rootBounds

一個(gè)對(duì)象值,表示發(fā)生相交時(shí)根元素可見區(qū)域的矩形信息,像這樣:

{
 "top": 0,
 "bottom": 600,
 "left": 0,
 "right": 1280,
 "width": 1280,
 "height": 600
}

boundingClientRect

發(fā)生相交時(shí)目標(biāo)元素的矩形信息,等價(jià)于 target.getBoundingClientRect()。

intersectionRect

根元素和目標(biāo)元素相交區(qū)域的矩形信息。

intersectionRatio

0 到 1 的數(shù)值,表示相交區(qū)域占目標(biāo)元素區(qū)域的百分比,也就是 intersectionRect 的面積除以 boundingClientRect 的面積得到的值。

貼邊的情況是特例

上面已經(jīng)說過,IntersectionObserver API 的基本工作原理就是檢測(cè)相交率的變化。每個(gè)觀察者實(shí)例為所有的目標(biāo)元素都維護(hù)著一個(gè)上次相交率(previousThreshold)的字段,在執(zhí)行 observe() 的時(shí)候會(huì)給 previousThreshold 賦初始值 0,然后每次檢測(cè)到新的相交率滿足(到達(dá)或跨過)了 thresholds 中某個(gè)指定的臨界值,且那個(gè)臨界值和當(dāng)前的 previousThreshold 值不同,就會(huì)觸發(fā)回調(diào),并把滿足的那個(gè)新的臨界值賦值給 previousThreshold,依此反復(fù),很簡單,對(duì)吧。

但是不知道你有沒有注意到,前面講過,當(dāng)目標(biāo)元素從距離根元素很遠(yuǎn)到和根元素貼邊,這時(shí)也會(huì)觸發(fā)回調(diào)(假如 thresholds 里有 0),但這和工作原理相矛盾啊,離的很遠(yuǎn)相交率是 0,就算貼邊,相交率還是 0,值并沒有變,不應(yīng)該觸發(fā)回調(diào)啊。的確,這和基本工作原理矛盾,但這種情況是特例,目標(biāo)元素從根元素外部很遠(yuǎn)的地方移動(dòng)到和根元素貼邊,也會(huì)當(dāng)做是滿足了臨界值 0,即便 0 等于 0。

還有一個(gè)反過來的特例,就是目標(biāo)元素從根元素內(nèi)部的某個(gè)地方(相交率已經(jīng)是 1)移動(dòng)到和根元素貼邊(還是 1),也會(huì)觸發(fā)回調(diào)(假如 thresholds 里有 1)。

目標(biāo)元素寬度或高度為 0 的情況也是特例

很多時(shí)候我們的目標(biāo)元素是個(gè)空的 img 標(biāo)簽或者是一個(gè)空的 div 容器,如果沒有設(shè)置 CSS,這些元素的寬和高都是 0px,那渲染出的矩形面積就是 0px2,那算相交率的時(shí)候就會(huì)遇到除以 0 這種在數(shù)學(xué)上是非法操作的問題,即便在 JavaScript 里除以 0 并不會(huì)拋異常還是會(huì)得到 Infinity,但相交率一直是 Infinity 也就意味著回調(diào)永遠(yuǎn)不會(huì)觸發(fā),所以這種情況必須特殊對(duì)待。

特殊對(duì)待的方式就是:0 面積的目標(biāo)元素的相交率要么是 0 要么是 1。無論是貼邊還是移動(dòng)到根元素內(nèi)部,相交率都是 1,其它情況都是 0。1 到 0 會(huì)觸發(fā)回調(diào),0 到 1也會(huì)觸發(fā)回調(diào),就這兩種情況:

由于這個(gè)特性,所以為 0 面積的目標(biāo)元素設(shè)置臨界值是沒有意義的,設(shè)置什么值、設(shè)置幾個(gè),都是一個(gè)效果。

但是注意,相交信息里的 intersectionRatio 屬性永遠(yuǎn)是 0,很燒腦,我知道:

<div id="target"></div>

<script>
 let observer = new IntersectionObserver(([entry]) => {
 alert(entry.intersectionRatio)
 })

 observer.observe(target)
</script>

observe() 之前就已經(jīng)相交了的情況是特例嗎?

不知道你們有沒有這個(gè)疑問,反正我有過。observe() 一個(gè)已經(jīng)和根元素相交的目標(biāo)元素之后,再也不滾動(dòng)頁面,意味著之后相交率再也不會(huì)變化,回調(diào)不應(yīng)該發(fā)生,但還是發(fā)生了。這是因?yàn)椋涸趫?zhí)行 observe() 的時(shí)候,瀏覽器會(huì)將 previousThreshold 初始化成 0,而不是初始化成當(dāng)前真正的相交率,然后在下次相交檢測(cè)的時(shí)候就檢測(cè)到相交率變化了,所以這種情況不是特殊處理。

瀏覽器何時(shí)進(jìn)行相交檢測(cè),多久檢測(cè)一次?

我們常見的顯示器都是 60hz 的,就意味著瀏覽器每秒需要繪制 60 次(60fps),大概每 16.667ms 繪制一次。如果你使用 200hz 的顯示器,那么瀏覽器每 5ms 就要繪制一次。我們把 16.667ms 和 5ms 這種每次繪制間隔的時(shí)間段,稱之為 frame(幀,和 html 里的 frame 不是一個(gè)東西)。瀏覽器的渲染工作都是以這個(gè)幀為單位的,下圖是 Chrome 中每幀里瀏覽器要干的事情(我在原圖的基礎(chǔ)上加了 Intersection Observations 階段):

Intersection Observations In A Frame

可以看到,相交檢測(cè)(Intersection Observations)發(fā)生在 Paint 之后 Composite 之前,多久檢測(cè)一次是根據(jù)顯示設(shè)備的刷新率而定的。但可以肯定的是,每次繪制不同的畫面之前,都會(huì)進(jìn)行相交檢測(cè),不會(huì)有漏網(wǎng)之魚。

一次性到達(dá)或跨過的多個(gè)臨界值中選一個(gè)最近的

如果一個(gè)觀察者實(shí)例設(shè)置了 11 個(gè)臨界值:[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],那么當(dāng)目標(biāo)元素和根元素從完全不相交狀態(tài)滾動(dòng)到相交率為 1 這一段時(shí)間里,回調(diào)函數(shù)會(huì)觸發(fā)幾次?答案是:不確定。要看滾動(dòng)速度,如果滾動(dòng)速度足夠慢,每次相交率到達(dá)下一個(gè)臨界值的時(shí)間點(diǎn)都發(fā)生在了不同的幀里(瀏覽器至少繪制了 11 次),那么就會(huì)有 11 次相交被檢測(cè)到,回調(diào)函數(shù)就會(huì)被執(zhí)行 11 次;如果滾動(dòng)速度足夠快,從不相交到完全相交是發(fā)生在同一個(gè)幀里的,瀏覽器只繪制了一次,瀏覽器雖然知道這一次滾動(dòng)操作就滿足了 11 個(gè)指定的臨界值(從不相交到 0,從 0 到 0.1,從 0.1 到 0.2 ··· ),但它只會(huì)考慮最近的那個(gè)臨界值,那就是 1,回調(diào)函數(shù)只觸發(fā)一次:

<div id="info">相交次數(shù):
 <span id="times">0</span>
 <button onclick="document.scrollingElement.scrollTop = 10000">一下滾動(dòng)到最低部</button>
</div>
<div id="target"></div>

<style>
 #info {
 position: fixed;
 }

 #target {
 position: absolute;
 top: 200%;
 width: 100px;
 height: 100px;
 background: red;
 margin-bottom: 100px;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => {
 times.textContent = +times.textContent + 1
 }, {
 threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] // 11 個(gè)臨界值
 })

 observer.observe(target)
</script>

離開視口的時(shí)候也一個(gè)道理,假如根元素和目標(biāo)元素的相交率先從完全相交變成了 0.45,然后又從 0.45 變成了完全不相交,那么回調(diào)函數(shù)只會(huì)觸發(fā)兩次。

如何判斷當(dāng)前是否相交?

我上面有幾個(gè) demo 都用了幾行看起來挺麻煩的代碼來判斷目標(biāo)元素是不是在視口內(nèi):

if (!target.isIntersecting) {
 // 相交
 target.isIntersecting = true
} else {
 // 不想交
 target.isIntersecting = false
}

為什么?難道用 entry.intersectionRatio > 0 判斷不可以嗎:

<div id="info">不可見,請(qǐng)非常慢的向下滾動(dòng)</div>
<div id="target"></div>

<style>
 #info {
 position: fixed;
 }

 #target {
 position: absolute;
 top: 200%;
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(([entry]) => {
 if (entry.intersectionRatio > 0) {
 // 快速滾動(dòng)會(huì)執(zhí)行到這里
 info.textContent = "可見了"
 } else {
 // 慢速滾動(dòng)會(huì)執(zhí)行到這里
 info.textContent = "不可見,請(qǐng)非常慢的向下滾動(dòng)"
 }
 })

 observer.observe(target)
</script>

粗略一看,貌似可行,但你別忘了上面講的貼邊的情況,如果你滾動(dòng)頁面速度很慢,當(dāng)目標(biāo)元素的頂部和視口底部剛好挨上時(shí),瀏覽器檢測(cè)到相交了,回調(diào)函數(shù)觸發(fā)了,但這時(shí) entry.intersectionRatio 等于 0,會(huì)進(jìn)入 else 分支,繼續(xù)向下滾,回調(diào)函數(shù)再不會(huì)觸發(fā)了,提示文字一直停留在不可見狀態(tài);但如果你滾動(dòng)速度很快,當(dāng)瀏覽器檢測(cè)到相交時(shí),已經(jīng)越過了 0 那個(gè)臨界值,存在了實(shí)際的相交面積,entry.intersectionRatio > 0 也就為 true 了。所以這樣寫會(huì)導(dǎo)致代碼執(zhí)行不穩(wěn)定,不可行。

除了通過在元素身上添加新屬性來記錄上次回調(diào)觸發(fā)時(shí)是進(jìn)還是出外,我還想到另外一個(gè)辦法,那就是給 threshold 選項(xiàng)設(shè)置一個(gè)很小的接近 0 的臨界值,比如 0.000001,然后再用 entry.intersectionRatio > 0 判斷,這樣就不會(huì)受貼邊的情況影響了,也就不會(huì)受滾動(dòng)速度影響了:

<div id="info">不可見,以任意速度向下滾動(dòng)</div>
<div id="target"></div>

<style>
 #info {
 position: fixed;
 }

 #target {
 position: absolute;
 top: 200%;
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(([entry]) => {
 if (entry.intersectionRatio > 0) {
 info.textContent = "可見了"
 } else {
 info.textContent = "不可見,以任意速度向下滾動(dòng)"
 }
 }, {
 threshold: [0.000001]
 })

 observer.observe(target)
</script>

目標(biāo)元素不是根元素的后代元素的話會(huì)怎樣?

如果在執(zhí)行 observe() 時(shí),目標(biāo)元素不是根元素的后代元素,瀏覽器也并不會(huì)報(bào)錯(cuò),Chrome 從 53 開始會(huì)對(duì)這種用法發(fā)出警告(是我提議的),從而提醒開發(fā)者這種用法有可能是不對(duì)的。為什么不更嚴(yán)格點(diǎn),直接報(bào)錯(cuò)?因?yàn)樵氐膶蛹?jí)關(guān)系是可以變化的,可能有人會(huì)寫出這樣的代碼:

<div id="root"></div>
<div id="target"></div>

<style>
 #target {
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => alert("看見我了"), {root: root})
 observer.observe(target) // target 此時(shí)并不是 root 的后代元素,Chrome 控制臺(tái)會(huì)發(fā)出警告:target element is not a descendant of root.
 root.appendChild(target) // 現(xiàn)在是了,觸發(fā)回調(diào)
</script>

又或者被 observe 的元素此時(shí)還未添加到 DOM 樹里:

<div id="root"></div>

<style>
 #target {
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => alert("看見我了"), {root: root})
 let target = document.createElement("div") // 還不在 DOM 樹里
 observer.observe(target) // target 此時(shí)并不是 root 的后代元素,Chrome 控制臺(tái)會(huì)發(fā)出警告:target element is not a descendant of root.
 root.appendChild(target) // 現(xiàn)在是了,觸發(fā)回調(diào)
</script>

也就是說,只要在相交發(fā)生時(shí),目標(biāo)元素是根元素的后代元素,就可以了,執(zhí)行 observe() 的時(shí)候可以不是。

是后代元素還不夠,根元素必須是目標(biāo)元素的祖先包含塊

要求目標(biāo)元素是根元素的后代元素只是從 DOM 結(jié)構(gòu)上說的,一個(gè)較容易理解的限制,另外一個(gè)不那么容易理解的限制是從 CSS 上面說的,那就是:根元素矩形必須是目標(biāo)元素矩形的祖先包含塊(包含塊也是鏈?zhǔn)降模拖裨玩湥?。比如下面這個(gè) demo 所演示的,兩個(gè)做隨機(jī)移動(dòng)的元素 a 和 b,a 是 b 的父元素,但它倆的 position 都是 fixed,導(dǎo)致 a 不是 b 的包含塊,所以這是個(gè)無效的觀察操作,嘗試把 fixed 改成 relative 就發(fā)現(xiàn)回調(diào)觸發(fā)了:

<div id="a">
 <div id="b"></div>
</div>
<div id="info">0%</div>

<style>
 #a, #b {
 position: fixed; /* 嘗試改成 relative */
 width: 200px;
 height: 200px;
 opacity: 0.8;
 }

 #a {
 background: red
 }

 #b {
 background: blue
 }

 #info {
 width: 200px;
 margin: 0 auto;
 }

 #info::before {
 content: "Intersection Ratio: ";
 }
</style>

<script>
 let animate = (element, oldCoordinate = {x: 0, y: 0}) => {
 let newCoordinate = {
 x: Math.random() * (innerWidth - element.clientWidth),
 y: Math.random() * (innerHeight - element.clientHeight)
 }
 let keyframes = [oldCoordinate, newCoordinate].map(coordinateToLeftTop)
 let duration = calcDuration(oldCoordinate, newCoordinate)

 element.animate(keyframes, duration).onfinish = () => animate(element, newCoordinate)
 }

 let coordinateToLeftTop = coordinate => ({
 left: coordinate.x + "px",
 top: coordinate.y + "px"
 })

 let calcDuration = (oldCoordinate, newCoordinate) => {
 // 移動(dòng)速度為 0.3 px/ms
 return Math.hypot(oldCoordinate.x - newCoordinate.x, oldCoordinate.y - newCoordinate.y) / 0.3
 }

 animate(a)
 animate(b)
</script>


<script>
 let thresholds = Array.from({
 length: 200
 }, (k, v) => v / 200) // 200 個(gè)臨界值對(duì)應(yīng) 200px

 new IntersectionObserver(([entry]) => {
 info.textContent = (entry.intersectionRatio * 100).toFixed(2) + "%"
 }, {
 root: a,
 threshold: thresholds
 }).observe(b)
</script>

從 DOM 樹中刪除目標(biāo)元素會(huì)怎么樣?

假設(shè)現(xiàn)在根元素和目標(biāo)元素已經(jīng)是相交狀態(tài),這時(shí)假如把目標(biāo)元素甚至是根元素從 DOM 樹中刪除,或者通過 DOM 操作讓目標(biāo)元素不在是根元素的后代元素,再或者通過改變 CSS 屬性導(dǎo)致根元素不再是目標(biāo)元素的包含塊,又或者通過 display:none 隱藏某個(gè)元素,這些操作都會(huì)讓兩者的相交率突然變成 0,回調(diào)函數(shù)就有可能被觸發(fā):

<div id="info"> 刪除目標(biāo)元素也會(huì)觸發(fā)回調(diào)
 <button onclick="document.body.removeChild(target)">刪除 target</button>
</div>
<div id="target"></div>


<style>
 #info {
 position: fixed;
 }
 
 #target {
 position: absolute;
 top: 100px;
 width: 100px;
 height: 100px;
 background: red;
 }
</style>

<script>
 let observer = new IntersectionObserver(() => {
 if (!document.getElementById("target")) {
 info.textContent = "target 被刪除了"
 }
 })

 observer.observe(target)
</script>

關(guān)于 iframe

在 IntersectionObserver API 之前,你無法在一個(gè)跨域的 iframe 頁面里判斷這個(gè) iframe 頁面或者頁面里的某個(gè)元素是否出現(xiàn)在了頂層窗口的視口里,這也是為什么要發(fā)明 IntersectionObserver API 的一個(gè)很重要的原因。請(qǐng)看下圖演示:

無論怎么動(dòng),無論多少層 iframe, IntersectionObserver 都能精確的判斷出目標(biāo)元素是否出現(xiàn)在了頂層窗口的視口里,無論跨域不跨域。

前面講過根元素為 null 表示實(shí)際的根元素是當(dāng)前窗口的視口,現(xiàn)在更明確點(diǎn),應(yīng)該是最頂層窗口的視口。

如果當(dāng)前頁面是個(gè) iframe 頁面,且和頂層頁面跨域,在根元素為 null 的前提下觸發(fā)回調(diào)后,你拿到的 IntersectionObserverEntry 對(duì)象的 rootBounds 屬性會(huì)是 null;即便兩個(gè)頁面沒有跨域,那么 rootBounds 屬性所拿到的矩形的坐標(biāo)系統(tǒng)和 boundingClientRect 以及 intersectionRect 這兩個(gè)矩形也是不一樣的,前者坐標(biāo)系統(tǒng)的原點(diǎn)是頂層窗口的左上角,后兩者是當(dāng)前 iframe 窗口左上角。

鑒于互聯(lián)網(wǎng)上的廣告 90% 都是跨域的 iframe,我想 IntersectionObserver API 能夠大大簡化這些廣告的延遲加載和真實(shí)曝光量統(tǒng)計(jì)的實(shí)現(xiàn)。

根元素不能是其它 frame 下的元素

如果沒有跨域的話,根元素可以是上層 frame 中的某個(gè)祖先元素嗎?比如像下面這樣:

<div id="root">
 <iframe id="iframe"></iframe>
</div>

<script>
 let iframeHTML = `
 <div id="target"></div>

 <style>
 #target {
 width: 100px;
 height: 100px;
 background: red;
 }
 </style>

 <script>
 let observer = new IntersectionObserver(() => {
 alert("intersecting")
 }, {
 root: top.root
 })

 observer.observe(target)
 <\/script>`

 iframe.src = URL.createObjectURL(new Blob([iframeHTML], {"type": "text/html"}))
</script>

我不清楚上面這個(gè) demo 中 root 算不算 target 的祖先包含塊,但規(guī)范明確規(guī)定了這種觀察操作無效,根元素不能是來自別的 frame??偨Y(jié)一下就是:根元素要么是 null,要么是同 frame 里的某個(gè)祖先包含塊元素。

真的只是判斷兩個(gè)元素相交嗎?

實(shí)際情況永遠(yuǎn)沒表面看起來那么簡單,瀏覽器真的只是判斷兩個(gè)矩形相交嗎?看下面的代碼:

<div id="parent">
 <div id="target"></div>
</div>

<style>
 #parent {
 width: 20px;
 height: 20px;
 background: red;
 overflow: hidden;
 }

 #target {
 width: 100px;
 height: 100px;
 background: blue;
 }
</style>

<script>
 let observer = new IntersectionObserver(([entry]) => {
 alert(`相交矩形為: ${entry.intersectionRect.width} x ${entry.intersectionRect.width}`)
 })

 observer.observe(target)
</script>

這個(gè) demo 里根元素為當(dāng)前視口,目標(biāo)元素是個(gè) 100x100 的矩形,如果真的是判斷兩個(gè)矩形的交集那么簡單,那這個(gè)相交矩形就應(yīng)該是 100 x 100,但彈出來的相交矩形是 20 x 20。因?yàn)槠鋵?shí)在相交檢測(cè)之前,有個(gè)裁減目標(biāo)元素矩形的步驟,裁減完才去和根元素判斷相交,裁減的基本思想就是,把目標(biāo)元素被“目標(biāo)元素和根元素之間存在的那些元素”遮擋的部分裁掉,具體裁減步驟是這樣的(用 rect 代表最終的目標(biāo)元素矩形):

1、讓 rect 為目標(biāo)元素矩形
2、讓 current 為目標(biāo)元素的父元素
3、如果 current 不是根元素,則進(jìn)行下面的循環(huán):
如果 current 的 overflow 不是 visible(是 scroll 或 hidden 或 auto) 或者 current 是個(gè) iframe 元素(iframe 天生自帶 overflow: auto),則:
讓 rect 等于 rect 和 current 的矩形(要排除滾動(dòng)條區(qū)域)的交集
讓 current 為 current 的父元素(iframe 里的 html 元素的父元素就是父頁面里的 iframe 元素)
也就是說,實(shí)際上是順著目標(biāo)元素的 DOM 樹一直向上循環(huán)求交集的過程。再看上面的 demo,目標(biāo)元素矩形一開始是 100x100,然后和它的父元素相交成了 20x20,然后 body 元素和 html 元素沒有設(shè)置 overflow,所以最終和視口做交集的是 20x20 的矩形。

關(guān)于雙指縮放

移動(dòng)端設(shè)備和 OS X 系統(tǒng)上面,允許用戶使用兩根手指放大頁面中的某一部分:

如果頁面某一部分被放大了,那同時(shí)也就意味著頁面邊緣上某些區(qū)域顯示在了視口的外面:

這些情況下 IntersectionObserver API 都不會(huì)做專門處理,無論是根元素還是目標(biāo)元素,它們的矩形都是縮放前的真實(shí)尺寸(就像 getBoundingClientRect() 方法所表現(xiàn)的一樣),而且即便相交真的發(fā)生在了那些因縮放導(dǎo)致用戶眼睛看不到的區(qū)域內(nèi),回調(diào)函數(shù)也照樣觸發(fā)。如果你用的 Mac 系統(tǒng),你現(xiàn)在就可以測(cè)試一下上面的任意一個(gè) demo。

關(guān)于垃圾回收

一個(gè)觀察者實(shí)例無論對(duì)根元素還是目標(biāo)元素,都是弱引用的,就像 WeakMap 對(duì)自己的 key 是弱引用一樣。如果目標(biāo)元素被垃圾回收了,關(guān)系不大,瀏覽器就不會(huì)再檢測(cè)它了;如果是根元素被垃圾回收了,那就有點(diǎn)問題了,根元素沒了,但觀察者實(shí)例還在,如果這時(shí)使用哪個(gè)觀察者實(shí)例會(huì)怎樣:

<div id="root"></div>
<div id="target"></div>

<script>
 let observer = new IntersectionObserver(() => {}, {root: root}) // root 元素一共有兩個(gè)引用,一個(gè)是 DOM 樹里的引用,一個(gè)是全局變量 root 的引用
 document.body.removeChild(root) // 從 DOM 樹里移除
 root = null // 全局變量置空
 setTimeout(() => {
 gc() // 手動(dòng) gc,需要在啟動(dòng) Chrome 時(shí)傳入 --js-flags='--expose-gc' 選項(xiàng)
 console.log(observer.root) // null,觀察者實(shí)例的根元素已經(jīng)被垃圾回收了
 observer.observe(target) // Uncaught InvalidStateError: observe() called on an IntersectionObserver with an invalid root,執(zhí)行 observer 的任意方法都會(huì)報(bào)錯(cuò)。
 })
</script>

也就是說,那個(gè)觀察者實(shí)例也相當(dāng)于死了。這個(gè)報(bào)錯(cuò)是從 Chrome 53 開始的(我提議的),51 和 52 上只會(huì)靜默失敗。

后臺(tái)標(biāo)簽頁

由于 Chrome 不會(huì)渲染后臺(tái)標(biāo)簽頁,所以也就不會(huì)檢測(cè)相交了,當(dāng)你切換到前后才會(huì)繼續(xù)。你可以通過 Command/Ctrl + 左鍵打開上面任意的 demo 試試。

吐槽命名

threshold 和 thresholds

構(gòu)造函數(shù)的參數(shù)里叫 threshold,實(shí)例的屬性里叫 thresholds。道理我都懂,前者既能是一個(gè)單數(shù)形式的數(shù)字,也能是一個(gè)復(fù)數(shù)形式的數(shù)組,所以用了單數(shù)形式,而后者序列化出來只能是個(gè)數(shù)組,所以就用了復(fù)數(shù)了。但是統(tǒng)一更重要吧,我覺的都用復(fù)數(shù)形式?jīng)]什么問題,一開始研究這個(gè) API 的時(shí)候我嘗試傳了 {thresholds: [1]},試了半天才發(fā)現(xiàn)多了個(gè) s,坑死了。

disconnect

什么?disconnect?什么意思?connect 什么了?我只知道 observe 和 unobserve,你他么的叫 unobserveAll 會(huì)死啊。這個(gè)命名很容易讓人不明覺厲,結(jié)果是個(gè)很簡單的東西。叫這個(gè)其實(shí)是為了和 MutationObserver 以及 PerformanceObserver 統(tǒng)一。

rootBounds & boundingClientRect & intersectionRect

這三者都是返回一個(gè)矩形信息的,本是同類,但是名字沒有一點(diǎn)規(guī)律,讓人無法記憶。我建議叫 rootRect & targetRect & intersectionRect,一遍就記住了,真不知道寫規(guī)范的人怎么想的。

Polyfil

寫規(guī)范的人會(huì)在 Github 倉庫上維護(hù)一個(gè) polyfill,目前還未完成。但 polyfill 顯然無法支持 iframe 內(nèi)元素的檢測(cè),不少細(xì)節(jié)也無法模擬。

其它瀏覽器實(shí)現(xiàn)進(jìn)度

Firefox:https://bugzilla.mozilla.org/show_bug.cgi?id=1243846

Safari:https://bugs.webkit.org/show_bug.cgi?id=159475

Edge:https://developer.microsoft.com/en-us/microsoft-edge/platform/status/intersectionobserver

總結(jié)

雖然目前該 API 的規(guī)范已經(jīng)有一年歷史了,但仍非常不完善,大量的細(xì)節(jié)都沒有規(guī)定;Chrome 的實(shí)現(xiàn)也有半年了,但還是有不少 bug(大多是疑似 bug,畢竟規(guī)范不完善)。因此,本文中有些細(xì)節(jié)我故意略過,比如目標(biāo)元素大于根元素,甚至根元素面積為 0,支不支持 svg 這些,因?yàn)槲乙膊恢朗裁词钦_的表現(xiàn)。

2016-8-2 追記:今天被同事問了個(gè)真實(shí)需求,“統(tǒng)計(jì)淘寶搜索頁面在頁面打開兩秒后展現(xiàn)面積超過 50% 的寶貝”,我立刻想到了用 IntersectionObserver:

setTimeout(() => {
 let observer = new IntersectionObserver(entries => {
 entries.forEach(entry => {
 console.log(entry.target) // 拿到了想要的寶貝元素
 })
 observer.disconnect() // 統(tǒng)計(jì)到就不在需要繼續(xù)觀察了
 }, {
 threshold: 0.5 // 只要展現(xiàn)面積達(dá)到 50% 的寶貝元素 
 })

 // 觀察所有的寶貝元素
 Array.from(document.querySelectorAll("#mainsrp-itemlist .item")).forEach(item => observer.observe(item))
}, 2000)

不需要你進(jìn)行任何數(shù)學(xué)計(jì)算,真是簡單到爆,當(dāng)然,因?yàn)榧嫒菪詥栴},這個(gè)代碼不能被采用。

相關(guān)文章

  • JavaScript代碼性能優(yōu)化總結(jié)篇

    JavaScript代碼性能優(yōu)化總結(jié)篇

    本文給大家總結(jié)了有關(guān)js代碼性能優(yōu)化的相關(guān)知識(shí),非常不錯(cuò),感興趣的朋友一起學(xué)習(xí)吧
    2016-05-05
  • 微信小程序?qū)崿F(xiàn)導(dǎo)航欄選項(xiàng)卡效果

    微信小程序?qū)崿F(xiàn)導(dǎo)航欄選項(xiàng)卡效果

    這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)導(dǎo)航欄選項(xiàng)卡效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • js清空表單數(shù)據(jù)的兩種方式(遍歷+reset)

    js清空表單數(shù)據(jù)的兩種方式(遍歷+reset)

    這篇文章主要介紹了js清空表單數(shù)據(jù)的兩種方式(遍歷+reset),需要的朋友可以參考下
    2014-07-07
  • JavaScript壓縮并加密圖片的方法你了解嗎

    JavaScript壓縮并加密圖片的方法你了解嗎

    這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)學(xué)生成績管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • 讓IE8支持DOM 2(不用框架?。? src=

    讓IE8支持DOM 2(不用框架!)

    眾所周知,IE8開放了對(duì)DOM原型的支持以及ECMA v5的兩個(gè)新方法——Object.defineProperty和Object.getOwnPropertyDexcriptor(單詞好長……),并且這兩個(gè)新方法居然只能用于DOM。
    2009-12-12
  • 原生JS實(shí)現(xiàn)pc端輪播圖效果

    原生JS實(shí)現(xiàn)pc端輪播圖效果

    這篇文章主要為大家詳細(xì)介紹了原生JS實(shí)現(xiàn)pc端輪播圖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • ES6 系列之 Generator 的自動(dòng)執(zhí)行的方法示例

    ES6 系列之 Generator 的自動(dòng)執(zhí)行的方法示例

    這篇文章主要介紹了ES6 系列之 Generator 的自動(dòng)執(zhí)行的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-10-10
  • 微信小程序傳值常用的4種方式

    微信小程序傳值常用的4種方式

    微信小程序開發(fā)中的大部分知識(shí)點(diǎn)和前端開發(fā)是一模一樣的,這篇文章主要給大家介紹了關(guān)于微信小程序傳值常用的4種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-05-05
  • JS實(shí)現(xiàn)字符串中去除指定子字符串方法分析

    JS實(shí)現(xiàn)字符串中去除指定子字符串方法分析

    這篇文章主要介紹了JS實(shí)現(xiàn)字符串中去除指定子字符串方法,結(jié)合實(shí)例形式分析了javascript使用字符串替換與分割、聚合兩種子字符串去除相關(guān)操作技巧,需要的朋友可以參考下
    2018-05-05
  • JS+CSS實(shí)現(xiàn)的藍(lán)色table選項(xiàng)卡效果

    JS+CSS實(shí)現(xiàn)的藍(lán)色table選項(xiàng)卡效果

    這篇文章主要介紹了JS+CSS實(shí)現(xiàn)的藍(lán)色table選項(xiàng)卡效果,通過鼠標(biāo)事件調(diào)用自定義函數(shù)實(shí)現(xiàn)頁面元素樣式的遍歷與動(dòng)態(tài)切換效果,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-10-10

最新評(píng)論