Tags: TypeScript

IntersectionObserver でビューポート内の一番上にあるパラグラフを取得する

IntersectionObserver でビューポート内の一番上にあるパラグラフを取得する実装をした。

やりたいこととしては、個人サイトで掲載している小説のブックマーク機能を作りたくて、ブックマークしたときにビューポート内の一番上にある文章を取得したい。

/** 探索対象を囲む要素のクラス名 */
const WRAPPER_CLASS = "Wrapper";

/** 交差状態 */
const intersectionStatus: { id: string; isIntersecting: boolean }[] = [];

/** ビューポート内の一番上にあるパラグラフの id を取得 */
function getTopParagraphId(): string {
	for (const item of intersectionStatus) {
		if (item.isIntersecting) {
			const element = document.getElementById(item.id);
			if (!element) {
				continue;
			}
            // パラグラフの中身が空でない場合は id を返す
			if (element.innerHTML.trim().length > 0) {
				return item.id;
			}
		}
	}

    // すべてのパラグラフがビューポート外にある場合は一番下のパラグラフを返す
	return intersectionStatus[intersectionStatus.length - 1].id;
}

/** 探索対象内のすべての p タグに id を付与し、交差判定する */
function setIdAndIntersectionObserver() {
	const wrapperElements = document.getElementsByClassName(WRAPPER_CLASS);
	if (wrapperElements.length === 0) {
		return;
	}
	const element = wrapperElements[0];
	const paragraphs = element.getElementsByTagName("p");
	for (let index = 0; index < paragraphs.length; index++) {
		const id = `element-p-${index}`;
		paragraphs[index].setAttribute("id", id);
		intersectionStatus.push({ id, isIntersecting: false });
	}

    // 一つの IntersectionObserver インスタンスで複数要素を監視する
	const intersectionObserver = new IntersectionObserver((entries) => {
		for (const entry of entries) {
			const index = intersectionStatus.findIndex(
				(item) => item.id === entry.target.id,
			);
			if (index === -1) {
				continue;
			}
			intersectionStatus[index].isIntersecting = entry.isIntersecting;
		}
	});
	for (const paragraph of paragraphs) {
		intersectionObserver.observe(paragraph);
	}
}

使い方としては、ページ読み込み後に setIdAndIntersectionObserver を実行する。

ブックマークのタイミングで getTopParagraphId を実行して一番上にある要素を取得する。

以上