-
Notifications
You must be signed in to change notification settings - Fork 7
Module Render
niryuu edited this page May 25, 2026
·
1 revision
src/lib/render.ts (122 行)。embed.ts から起動される宣言的レンダリングの中核。
export const renderGeoloniaMap: () => void;
export const registerPlugin: (plugin: (map: GeoloniaMap, target: HTMLElement, atts) => void) => void;const plugins: Array<(map: GeoloniaMap, target: HTMLElement, atts) => void> = [];registerPlugin で push される唯一のグローバル配列。 renderGeoloniaMap 内のクロージャスコープに以下の state も:
-
isDOMContentLoaded: boolean—DOMContentLoadedイベント受領フラグ -
alreadyRenderedMaps: Array<{ map, target, atts }>— DOMContentLoaded 前に render されたマップのキュー -
isRemoved: Symbol('map-is-removed')—map[isRemoved] = trueで破棄済みフラグを立てる
-
PMTiles プロトコル登録:
maplibregl.addProtocol('pmtiles', protocol.tile)(render.ts:13-14) -
iframe 許可チェック:
checkPermission()(render.ts:16)。false なら何もせずconsole.error('[Geolonia] We are very sorry, but we cannot display our map in iframe.')で終了 -
state 初期化:
isDOMContentLoaded = false、alreadyRenderedMaps = []、isRemoved = Symbol(...) -
keyring parse:
keyring.parse()(render.ts:21) — script tag から API key と stage を抽出 - renderSingleMap 関数定義 (詳細は次節)
-
DOMContentLoaded リスナー (
render.ts:56-65)isDOMContentLoaded = true-
alreadyRenderedMapsの各 entry に対し、!map[isRemoved]なら全 plugin を実行 -
alreadyRenderedMaps.splice(0, length)で配列クリア
-
IntersectionObserver 設定 (
render.ts:67-80) -
API key 未指定警告:
keyring.isGeoloniaStyle && !keyring.apiKeyならconsole.error('[Geolonia] Missing API key.')(render.ts:90-92) -
即時 render:
document.querySelectorAll('.geolonia[data-lazy-loading="off"]')をループ →renderSingleMap(container)(render.ts:95-102)- try/catch でラップ、エラー時は
console.error(...)のみで次に進む
- try/catch でラップ、エラー時は
-
lazy render:
document.querySelectorAll('.geolonia:not([data-lazy-loading="off"])')を IntersectionObserver で監視開始 (render.ts:105-107)
各 .geolonia 要素に対して呼ばれる:
-
const map = new GeoloniaMap(target)(render.ts:28) -
map.on('remove', () => { map[isRemoved] = true })(render.ts:31-33) -
MutationObserver で container 削除を監視 (
render.ts:37-45):const observer = new MutationObserver((mutationRecords) => { const removed = mutationRecords.some((record) => [...record.removedNodes].some((node) => node === target), ); if (removed && !map[isRemoved]) { map.remove(); } }); observer.observe(target.parentNode, { childList: true });
-
target.parentNodeを観察するため、target 自身ではなく親の childList 変化で削除を検知する -
targetが削除されるとremovedNodesに含まれる - 親が消えた場合は検出不可 (Suspicious-Logic 参照)
-
-
プラグインディスパッチ (
render.ts:48-53):-
atts = parseAtts(target)を取得 -
isDOMContentLoaded && !map[isRemoved]ならplugins.forEach(plugin => plugin(map, target, atts))で即実行 - そうでなければ
alreadyRenderedMaps.push({ map, target, atts })でキューに退避
-
const observer = new IntersectionObserver((entries) => {
entries.forEach((item) => {
if (!item.isIntersecting) return;
try {
renderSingleMap(item.target);
} catch (e) {
console.error('[Geolonia] Failed to initialize map', e);
}
observer.unobserve(item.target);
});
});- 監視対象は
.geolonia:not([data-lazy-loading="off"]) - 初回 isIntersecting=true で render →
unobserveで即座に監視解除 - 再度 viewport に入っても再 render しない (once-only 設計)
- maplibre 自身は
display: noneでもcontainer.clientHeightが 0 になるので、container がある瞬間に lazy 解除すれば render 自体は走る (見えなくても初期化される) -
IntersectionObserverの options 引数なしなので、threshold = 0, root = null (= viewport)
plugins.push(plugin);
return void 0;単純 push。同じ plugin を複数回登録すると複数回呼ばれる (重複チェック無し)。
| マップ初期化 ↔ DOMContentLoaded | 実行タイミング |
|---|---|
| マップ初期化が DOMContentLoaded 前 |
alreadyRenderedMaps にキュー → DOMContentLoaded ハンドラで一括実行 |
| マップ初期化が DOMContentLoaded 後 | 初期化直後に即実行 |
つまり全プラグインは「DOMContentLoaded 以降」かつ「new GeoloniaMap() 完了後」に呼ばれることが保証される。
注: プラグインは map.on('load') の前に呼ばれる可能性がある (GeoloniaMap constructor は同期で完了するが、load イベントはタイル取得後)。プラグインから marker 追加等するなら map.on('load', ...) でラップ必須。
document.querySelectorAll('.geolonia[data-lazy-loading="off"]') は renderGeoloniaMap() 実行時点の DOM スナップショット。embed.js を head で読み込む or DOMContentLoaded 前に動的に .geolonia を増やす場合、後から追加された要素は拾われない。
書き直し時はこのへんの「いつ走るか」の保証を明確化したい。
| フラグ | 型 | 場所 | 意味 / 遷移 |
|---|---|---|---|
isDOMContentLoaded |
boolean |
render.ts:17 (closure) | 初期 false、DOMContentLoaded で true
|
alreadyRenderedMaps |
Array<{map, target, atts}> |
render.ts:18 | DOMContentLoaded 前のマップを保持。DOMContentLoaded 時に空配列に |
isRemoved |
unique symbol |
render.ts:19 |
map[isRemoved] が true なら削除済 |
keyring.#isGeoloniaStyle |
boolean |
keyring.ts:4 |
parseAtts 内で isGeoloniaStyleCheck の結果で更新 |
plugins |
Array<EmbedPlugin> |
render.ts:10 (module) |
registerPlugin で push のみ。pop / クリアなし |
- 各
renderSingleMap呼び出しは try/catch でラップ → エラーが起きても次の map に進む (render.ts:72-77, 95-101) - ただし
console.errorのみ。ユーザに見える表示は無し。handleErrorMode(util.ts:376-392) を呼ぶのは GeoloniaMap constructor 内部の super() 失敗時のみ (geolonia-map.ts:188-194)
export type EmbedPlugin<
PluginAttributes extends { [otherKey: string]: string } = {
[otherKey: string]: string;
},
> = (
map: GeoloniaMap,
target: HTMLElement,
atts: EmbedAttributes & PluginAttributes,
) => void;- ジェネリクスで属性型を拡張可能
- 戻り値
void(非同期は async 関数を渡せるが await されない)
- IntersectionObserver の once-only 解除
- MutationObserver の parentNode 監視
- プラグイン Queue のメモリ解放
- DOMContentLoaded 前の早期 render に対するプラグイン遅延実行
-
renderGeoloniaMapは wrapper 側に残す (DOM ベースの自動初期化) -
registerPluginも wrapper 側 (lib 側ではGeoloniaMapを直接 new するため不要) - ただし
pluginsmodule global の所有権は議論余地 - PMTiles プロトコル登録は lib 側 (embed-core 側) で行う(
embed-core.tsの現状を踏襲)