◆ threshold を 0 にしてるときに intersection in なのに intersectionRatio が 0 のときがある
◆ intersection out でも 0 になるので intersectionRatio を使用した判断は無理
◆ in/out の判断をするには isIntersecting を使う

threshold と intersectionRatio

IntersectionObserver でうまく動かない時があるなと思って調べていたら intersectionRatio が threshold とちょうど同じ時でもコールバック関数が実行されていました
「threshold を超えたら」だと思ってたので intersectionRatio と 「>」 で今の状態が交差しているか判断してました
ちょうど 「===」 のときにコールバック関数が呼び出されて 「>」 になってからはコールバック関数が呼び出されないので交差したときに行うべき処理が行われていませんでした

全体が見えるようになってからという目的で threshold を 1 にしていた場合は交差率が 1 を超えないので 1 ちょうどなのはわかります
ただ ちょっとでも見えたらという目的で threshold を 0 にしていた場合に全く重なってないのに重なってる扱いでコールバック関数が呼び出されるのは意外です
重なってはないけど接している状態でほんの少しでも重なる側に動けば重なるというくらいなら このときに交差したときの処理をしても問題はないのですけど直感と違うなという気分になります
0 と 1 で扱いを変えずに統一するためでしょうか

isIntersecting

困るとすれば in か out の判断です
今の交差率を表示するとかならどっちでもいいのですが intersection in と intersection out で別の処理をしたいということは多いです
例えば見えるようになったらロードして 見えなくなったらアンロードするなどです

threshold が 0 の場合 上記のように見えるようになったときでも 0 となることがあります
見えなくなったときも 0 です
交差率による判断はできません
そういう時用に isIntersecting というプロパティがあるのでこっちを使いましょう
isIntersecting プロパティは in だと true で out だと false になります

issue

困る人もいそうかなとネットで調べてみると issue がありました

https://github.com/w3c/IntersectionObserver/issues/211

Polyfill 問題みたいです
isIntersecting プロパティがなくてそれの Polyfill で判断できないとか
流し読みした感じ Edge の場合は === 0 にならないみたいでうまくいってそうです

Intersection 表示ツール

最初に調べてた時 起きたり起きなかったりで楽に調べれるようにちょっとしたページを作りました
threshold が 0, 0.5, 1 のそれぞれの場合でスクロールして IntersectionObserverEntry の情報を見えるようにしてます

マウスみたいな大きめの幅でまとまって動くタイプだとちょうど 0 に重ねるのが難しく intersectionRatio が threshold と一致する場合を考慮してなくてもそれによる問題もほとんど起きません
しかし タッチパッドのスクロールみたいになめらかで 1px 単位で動かせるものだと ちょうど重なることが頻発し動かないケースが出てきます

<!doctype html>

<style>
* {
box-sizing: border-box;
}

body {
margin: 0;
}

.window {
width: 100vw;
height: 100vh;
display: flex;
}

.col {
flex: 1 0 400px;
display: flex;
flex-flow: column;
border-right: 1px solid silver;
}

.col h1 {
flex: none;
border-bottom: 1px solid silver;
margin: 0;
padding: 5px;
font-size: 16px;
text-align: center;
}

.col .scroller {
flex: 1 1 0;
overflow-y: scroll;
}

.col .log {
flex: none;
height: 300px;
background: #e7f5ec;
border-top: 1px solid silver;
overflow: hidden;
font-size: 12px;
padding: 5px;
font-family: monaco, consolas, monospace;
white-space: pre-wrap;
}

.box {
width: 100px;
height: 200px;
background: paleturquoise;
margin: 10px 50px;
}

.logline {
animation: flash 1s cubic-bezier(0.6, -0.28, 0.74, 0.05) 0s;
}

.log .true {
color: mediumblue;
font-weight: bold;
}

.log .false {
color: crimson;
font-weight: bold;
}

@keyframes flash {
0% { background: yellow; };
}
</style>

<script type="module">
const createLogLine = ({ id, isIntersecting, intersectionRatio }) => {
const div = document.createElement("div")
div.className = "logline"
const isIntersecting_html = `<b class="${isIntersecting}">${isIntersecting ? "true " : "false"}</b>`
div.innerHTML = [
`[${id}]`.padEnd(4, " "),
`intersecting: ${isIntersecting_html}`,
`ratio: <b>${intersectionRatio}</b>`
].join(" ")
return div
}

const containers = Array.from(
document.querySelectorAll(".col"),
(elem, i) => {
const scroller = elem.querySelector(".scroller")
const log = elem.querySelector(".log")
const threshold = +elem.dataset.thld
const io = new IntersectionObserver(entries => {
console.group()
for (const entry of entries) {
const { isIntersecting, intersectionRatio } = entry
const id = entry.target.dataset.id
console.log(i + ":" + id, { isIntersecting, intersectionRatio })

log.prepend(createLogLine({ id, isIntersecting, intersectionRatio }))
}
console.groupEnd()
}, { threshold })
return { col: elem, scroller, io }
}
)

for (let i = 0; i < 100; i++) {
const margin = ~~(Math.random() * 300)

for (const c of containers) {
const div = document.createElement("div")
div.className = "box"
div.style.marginBottom = margin + "px"
div.dataset.id = i
div.textContent = i
c.io.observe(div)
c.scroller.append(div)
}
}

// clear first intersection logs
setTimeout(() => {
console.clear()
for (const elem of document.querySelectorAll(".log")) {
elem.innerHTML = ""
}
}, 100)
</script>

<div class="window">
<div class="col" data-thld="0">
<h1>Threshold 0</h1>
<div class="scroller"></div>
<div class="log"></div>
</div>
<div class="col" data-thld="0.5">
<h1>Threshold 0.5</h1>
<div class="scroller"></div>
<div class="log"></div>
</div>
<div class="col" data-thld="1">
<h1>Threshold 1</h1>
<div class="scroller"></div>
<div class="log"></div>
</div>
</div>