feat(settings): 站点界面动画总开关与子开关
管理后台可关全站动画,并细调代码折叠、平滑滚动、装饰入场;统一 lib/motion,尊重系统减少动态效果。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ArrowUp, MessageSquare } from "lucide-react";
|
||||
import LikeButton from "./LikeButton";
|
||||
import { scrollBehavior } from "@/lib/motion";
|
||||
|
||||
/**
|
||||
* 帖子详情移动端浮动操作条(lg 以下显示):
|
||||
@@ -28,7 +29,7 @@ export default function MobilePostBar({
|
||||
|
||||
const scrollToComments = () => {
|
||||
document.getElementById("comment-input")?.scrollIntoView({
|
||||
behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth",
|
||||
behavior: scrollBehavior(),
|
||||
block: "center",
|
||||
});
|
||||
};
|
||||
@@ -36,7 +37,7 @@ export default function MobilePostBar({
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth",
|
||||
behavior: scrollBehavior(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
52
frontend/components/MotionProvider.tsx
Normal file
52
frontend/components/MotionProvider.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, type ReactNode } from "react";
|
||||
import {
|
||||
applyMotionSiteConfig,
|
||||
getMotionSiteConfig,
|
||||
motionConfigFromSettings,
|
||||
type MotionSiteConfig,
|
||||
} from "@/lib/motion";
|
||||
import { realtime, RT_SETTINGS_CHANGED, type RtSettingsData } from "@/lib/realtime";
|
||||
|
||||
/**
|
||||
* 根布局注入:SSR 初值写入 motion 模块 + html class;settings:changed 热更新。
|
||||
*/
|
||||
export default function MotionProvider({
|
||||
initial,
|
||||
children,
|
||||
}: {
|
||||
initial: MotionSiteConfig;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
applyMotionSiteConfig(motionConfigFromSettings(initial));
|
||||
}, [initial]);
|
||||
|
||||
useEffect(() => {
|
||||
return realtime.on<RtSettingsData>(RT_SETTINGS_CHANGED, (data) => {
|
||||
if (!data) return;
|
||||
const cur = getMotionSiteConfig();
|
||||
applyMotionSiteConfig(
|
||||
motionConfigFromSettings({
|
||||
ui_animations:
|
||||
typeof data.ui_animations === "boolean"
|
||||
? data.ui_animations
|
||||
: cur.ui_animations,
|
||||
anim_code_fold:
|
||||
typeof data.anim_code_fold === "boolean"
|
||||
? data.anim_code_fold
|
||||
: cur.anim_code_fold,
|
||||
anim_smooth_scroll:
|
||||
typeof data.anim_smooth_scroll === "boolean"
|
||||
? data.anim_smooth_scroll
|
||||
: cur.anim_smooth_scroll,
|
||||
anim_chrome:
|
||||
typeof data.anim_chrome === "boolean" ? data.anim_chrome : cur.anim_chrome,
|
||||
})
|
||||
);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return children;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { scrollBehavior } from "@/lib/motion";
|
||||
|
||||
export interface TocItem {
|
||||
level: 2 | 3;
|
||||
@@ -44,13 +45,12 @@ export default function PostToc({ items }: { items: TocItem[] }) {
|
||||
}
|
||||
const el = navRef.current?.querySelector<HTMLElement>(`[data-toc-id="${active}"]`);
|
||||
if (!el) return;
|
||||
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
el.scrollIntoView({ block: "nearest", behavior: reduce ? "auto" : "smooth" });
|
||||
el.scrollIntoView({ block: "nearest", behavior: scrollBehavior() });
|
||||
}, [active]);
|
||||
|
||||
const jump = (id: string) => (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
document.getElementById(id)?.scrollIntoView({ behavior: scrollBehavior(), block: "start" });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type RtFeedChangedData,
|
||||
} from "@/lib/realtime";
|
||||
import type { FeedSort } from "@/components/home/FeedTabs";
|
||||
import { scrollBehavior } from "@/lib/motion";
|
||||
|
||||
interface FeedNewTipProps {
|
||||
sort: FeedSort;
|
||||
@@ -52,7 +53,7 @@ export default function FeedNewTip({
|
||||
const handleClick = () => {
|
||||
setVisible(false);
|
||||
router.refresh();
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
window.scrollTo({ top: 0, behavior: scrollBehavior() });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "react";
|
||||
import { Check, Copy, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { allowMotion } from "@/lib/motion";
|
||||
import { useCodeFoldConfig } from "./CodeFoldProvider";
|
||||
|
||||
const FOLD_ANIM_MS = 360;
|
||||
@@ -34,10 +35,7 @@ function measureCollapsedPx(host: HTMLElement, foldLines: number): number {
|
||||
}
|
||||
|
||||
function prefersReducedMotion(): boolean {
|
||||
return (
|
||||
typeof window !== "undefined" &&
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
);
|
||||
return !allowMotion("codeFold");
|
||||
}
|
||||
|
||||
type MdCodeBlockProps = HTMLAttributes<HTMLPreElement> & {
|
||||
|
||||
Reference in New Issue
Block a user