完整差異
diff --git a/static/team-logo.js b/static/team-logo.js\nindex ebca80a0a..ec27bfe95 100644\n--- a/static/team-logo.js\n+++ b/static/team-logo.js\n@@ -8,7 +8,14 @@\n var reduced = window.matchMedia("(prefers-reduced-motion: reduce)");\n var timer = 0;\n var entryTimer = 0;\n- var observer = null;\n+ var visibilityListener = null;\n+\n+ function stopWaiting() {\n+ if (!visibilityListener) return;\n+ window.removeEventListener("scroll", visibilityListener);\n+ window.removeEventListener("resize", visibilityListener);\n+ visibilityListener = null;\n+ }\n \n function play(restart) {\n if (reduced.matches || document.documentElement.dataset.theme === "8bit") return;\n@@ -34,23 +41,19 @@\n if (image) image.addEventListener("load", function () { startOnEntry(event); }, { once: true });\n return;\n }\n- if (observer) observer.disconnect();\n+ stopWaiting();\n window.clearTimeout(entryTimer);\n entryTimer = window.setTimeout(function () {\n- var box = trigger.getBoundingClientRect();\n- var inView = box.bottom > 0 && box.right > 0 &&\n- box.top < window.innerHeight && box.left < window.innerWidth;\n- if (inView) { play(Boolean(event.persisted)); return; }\n- if ("IntersectionObserver" in window) {\n- observer = new IntersectionObserver(function (entries) {\n- if (!entries.some(function (entry) { return entry.isIntersecting; })) return;\n- observer.disconnect();\n- play(Boolean(event.persisted));\n- }, { threshold: .6 });\n- observer.observe(trigger);\n- } else {\n+ visibilityListener = function () {\n+ var box = trigger.getBoundingClientRect();\n+ if (box.bottom <= 0 || box.right <= 0 ||\n+ box.top >= window.innerHeight || box.left >= window.innerWidth) return;\n+ stopWaiting();\n play(Boolean(event.persisted));\n- }\n+ };\n+ window.addEventListener("scroll", visibilityListener, { passive: true });\n+ window.addEventListener("resize", visibilityListener);\n+ visibilityListener();\n }, 350);\n }\n \ndiff --git a/tests/test_team_logo.js b/tests/test_team_logo.js\nindex f920c3258..c2445cb8c 100644\n--- a/tests/test_team_logo.js\n+++ b/tests/test_team_logo.js\n@@ -11,12 +11,12 @@ function setup(theme = "light", prefersReducedMotion = false, initiallyVisible =\n const timers = new Map();\n const listeners = {};\n let nextTimer = 0;\n- let observer;\n+ let visible = initiallyVisible;\n const image = { complete: true, naturalWidth: 250 };\n const trigger = {\n offsetWidth: 88,\n getBoundingClientRect() {\n- return initiallyVisible\n+ return visible\n ? { top: 718, bottom: 800, left: 24, right: 107 }\n : { top: 900, bottom: 982, left: 24, right: 107 };\n },\n@@ -28,17 +28,14 @@ function setup(theme = "light", prefersReducedMotion = false, initiallyVisible =\n addEventListener(name, handler) { listeners[name] = handler; },\n querySelector() { return image; },\n };\n- class IntersectionObserver {\n- constructor(callback) { this.callback = callback; observer = this; }\n- observe() {}\n- disconnect() {}\n- }\n const window = {\n- IntersectionObserver,\n innerWidth: 390,\n innerHeight: 844,\n matchMedia() { return { matches: prefersReducedMotion }; },\n addEventListener(name, handler) { listeners[name] = handler; },\n+ removeEventListener(name, handler) {\n+ if (listeners[name] === handler) delete listeners[name];\n+ },\n setTimeout(handler) { const id = ++nextTimer; timers.set(id, handler); return id; },\n clearTimeout(id) { timers.delete(id); },\n };\n@@ -46,7 +43,7 @@ function setup(theme = "light", prefersReducedMotion = false, initiallyVisible =\n documentElement: { dataset: { theme } },\n querySelector() { return trigger; },\n };\n- vm.runInNewContext(source, { window, document, IntersectionObserver });\n+ vm.runInNewContext(source, { window, document });\n return {\n classes, listeners,\n enter(persisted = false) {\n@@ -56,7 +53,7 @@ function setup(theme = "light", prefersReducedMotion = false, initiallyVisible =\n timers.delete(first);\n handler();\n },\n- reveal() { observer.callback([{ isIntersecting: true }]); },\n+ reveal() { visible = true; listeners.scroll(); },\n finish() {\n for (const handler of timers.values()) handler();\n timers.clear();