feat(settings): 站点界面动画总开关与子开关

管理后台可关全站动画,并细调代码折叠、平滑滚动、装饰入场;统一 lib/motion,尊重系统减少动态效果。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 06:59:26 +08:00
parent c6df1b3b7e
commit 2b27703e6c
14 changed files with 511 additions and 46 deletions

89
frontend/lib/motion.ts Normal file
View File

@@ -0,0 +1,89 @@
/**
* 全站动画策略:系统「减少动态效果」优先;站点总开关/子开关可进一步关闭。
* 新增性能相关动画时:CSS 挂 j13-reduce-*,JS 走 allowMotion / scrollBehavior。
*/
export type MotionKind = "codeFold" | "smoothScroll" | "chrome";
export type MotionSiteConfig = {
/** 界面动画总开关 */
ui_animations: boolean;
/** 代码折叠高度与跟滚 */
anim_code_fold: boolean;
/** 平滑滚动 */
anim_smooth_scroll: boolean;
/** 装饰入场:列表/Toast/点赞/脉冲等 */
anim_chrome: boolean;
};
export const DEFAULT_MOTION_CONFIG: MotionSiteConfig = {
ui_animations: true,
anim_code_fold: true,
anim_smooth_scroll: true,
anim_chrome: true,
};
let siteConfig: MotionSiteConfig = { ...DEFAULT_MOTION_CONFIG };
export function getMotionSiteConfig(): MotionSiteConfig {
return siteConfig;
}
export function motionConfigFromSettings(s: {
ui_animations?: boolean;
anim_code_fold?: boolean;
anim_smooth_scroll?: boolean;
anim_chrome?: boolean;
}): MotionSiteConfig {
return {
ui_animations: s.ui_animations !== false,
anim_code_fold: s.anim_code_fold !== false,
anim_smooth_scroll: s.anim_smooth_scroll !== false,
anim_chrome: s.anim_chrome !== false,
};
}
export function osPrefersReducedMotion(): boolean {
return (
typeof window !== "undefined" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches
);
}
/** 是否允许指定类动画(OS reduce 或总开关关闭则一律否) */
export function allowMotion(kind: MotionKind = "chrome"): boolean {
if (osPrefersReducedMotion()) return false;
if (!siteConfig.ui_animations) return false;
switch (kind) {
case "codeFold":
return siteConfig.anim_code_fold;
case "smoothScroll":
return siteConfig.anim_smooth_scroll;
case "chrome":
return siteConfig.anim_chrome;
default:
return true;
}
}
export function scrollBehavior(): ScrollBehavior {
return allowMotion("smoothScroll") ? "smooth" : "auto";
}
/** 同步 documentElement class,供 CSS 与 SSR 初值一致 */
export function syncMotionHtmlClasses(cfg: MotionSiteConfig = siteConfig): void {
if (typeof document === "undefined") return;
const root = document.documentElement;
root.classList.toggle("j13-reduce-motion", !cfg.ui_animations);
root.classList.toggle(
"j13-reduce-chrome",
cfg.ui_animations && !cfg.anim_chrome
);
}
/** 应用站点动画配置(SSR 初值 / WS 热更新) */
export function applyMotionSiteConfig(cfg: MotionSiteConfig): MotionSiteConfig {
siteConfig = motionConfigFromSettings(cfg);
syncMotionHtmlClasses(siteConfig);
return siteConfig;
}