import { onPage } from "./lifecycle" /** * 回到顶部按钮。 * * 显隐**默认交给 CSS**:`animation-timeline: scroll(root block)` 让按钮随滚动位置 * 淡入,不需要任何 scroll 监听(见 global.css 的 .back-to-top)。 * 这里只负责两件事: * 1. 点击后平滑回到顶部; * 2. 兜住「CSS 那条动画没生效」的所有情况 —— 既包括不支持滚动驱动动画的浏览器 * (Firefox < 144 / Safari < 26),也包括规则被构建压缩弄坏的情况(见下)。 */ /** 与 global.css 里 animation-range 的起点呼应 */ const REVEAL_AFTER_PX = 480 /** global.css 中 @keyframes 的名字,用来实测动画有没有真的挂上 */ const ANIMATION_NAME = "back-to-top-reveal" /** * 「CSS 那条滚动驱动动画到底有没有生效」——直接读计算样式,而不是做特性检测。 * * 为什么不用 CSS.supports("animation-timeline: scroll()"):它只回答「这个语法浏览器认不认」, * 回答不了「这条规则有没有在构建压缩中活下来」。实际的坑是:Lightning CSS 会把 * animation-timeline 合并进 animation 简写,而 WebKit 不认简写里的 timeline 分量, * 整条声明被丢弃 —— 此时 CSS.supports 仍然返回 true,于是降级分支永远不执行, * 按钮就永远停在 opacity:0。改读 animationName 之后,CSS 那边无论因为什么原因没生效, * 这里都能立刻发现并接管(详见 global.css 里 .back-to-top 的注释)。 */ const cssDrivesVisibility = (button: HTMLElement) => window .getComputedStyle(button) .animationName.split(",") .some((part) => part.trim() === ANIMATION_NAME) onPage("back-to-top", () => { const button = document.querySelector("[data-back-to-top]") if (!button) return const onClick = () => { const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches window.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" }) } button.addEventListener("click", onClick) if (cssDrivesVisibility(button)) { return () => button.removeEventListener("click", onClick) } // ——— 降级路径 ——— // rAF 节流,且只在跨越阈值时才写 class:滚动本身不产生布局读取(只用 scrollY) const root = document.documentElement root.classList.add("no-scroll-timeline") let frame = 0 const update = () => { frame = 0 button.classList.toggle("is-visible", window.scrollY > REVEAL_AFTER_PX) } const onScroll = () => { if (frame) return frame = window.requestAnimationFrame(update) } window.addEventListener("scroll", onScroll, { passive: true }) update() return () => { window.removeEventListener("scroll", onScroll) if (frame) window.cancelAnimationFrame(frame) button.removeEventListener("click", onClick) root.classList.remove("no-scroll-timeline") } })