概念定义
旧模式是滚动监听加逐元素 getBoundingClientRect,布局工作量随事件频率与元素数相乘。Intersection Observer 反转模式:一次性注册元素,引擎在布局与绘制之后计算交集,只在状态变化时回调。
工作机制
观察者配置 root、扩大或收缩根盒的 rootMargin,以及 0 到 1 的阈值列表。条目携带当前比例、包围盒与 isIntersecting。回调在渲染后运行,测量稳定且不会强制布局;一个观察者可高效监视大量目标。
使用场景
首屏以下内容懒加载、区块可见即打点、滚动到底前预取下一页、离屏暂停视频或动画。与 content-visibility 组合跳过渲染,与原生 lazy loading 组合处理普通图片。
常见误区
它并非每次滚动都触发:回调按帧批处理、按阈值跨越驱动。条目是快照,快速滚动会合并跨越——需要统计每次通过的计数器应调阈值或加驻留逻辑。root 可以是任意可滚动祖先或元素,不只视口;给每个元素单独建观察者则会白白增加设置开销。
代码示例
<custom-counter start="10" step="2"></custom-counter>
<script>
class CustomCounter extends HTMLElement {
static get observedAttributes() {
return ['start', 'step'];
}
constructor() {
super();
this._count = 0;
this._step = 1;
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host { display: inline-flex; gap: 0.5rem; align-items: center; }
button { padding: 0.25rem 0.5rem; cursor: pointer; }
.value { font-weight: bold; min-width: 2rem; text-align: center; }
</style>
<button id="dec">-</button>
<span class="value">0</span>
<button id="inc">+</button>
`;
}
connectedCallback() {
this._count = parseInt(this.getAttribute('start') || '0');
this._step = parseInt(this.getAttribute('step') || '1');
this._update();
this.shadowRoot.getElementById('inc')
.addEventListener('click', () => this._increment());
this.shadowRoot.getElementById('dec')
.addEventListener('click', () => this._decrement());
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'step') this._step = parseInt(newVal || '1');
}
disconnectedCallback() {
console.log('Counter removed from DOM');
}
_increment() { this._count += this._step; this._update(); }
_decrement() { this._count -= this._step; this._update(); }
_update() {
this.shadowRoot.querySelector('.value').textContent = this._count;
this.dispatchEvent(new CustomEvent('change', {
detail: { value: this._count }
}));
}
}
customElements.define('custom-counter', CustomCounter);
document.querySelector('custom-counter')
.addEventListener('change', e => console.log('Count:', e.detail.value));
</script>
public class StringBuilderManipulation {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder(64);
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println("Built: " + sb);
sb.insert(5, " Java");
System.out.println("After insert: " + sb);
sb.delete(5, 10);
System.out.println("After delete: " + sb);
sb.replace(0, 5, "Hi");
System.out.println("After replace: " + sb);
sb.reverse();
System.out.println("Reversed: " + sb);
String csv = String.join(", ",
"Apple", "Banana", "Cherry", "Date");
System.out.println("CSV: " + csv);
String formatted = String.format(
"Name: %s, Age: %d, Score: %.2f",
"Alice", 30, 95.5
);
System.out.println(formatted);
String stripped = " padded ".strip();
System.out.println("Stripped: '" + stripped + "'");
}
}