mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 02:56:22 +00:00
feat: 添加移动端检测功能,优化组件动画效果
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 9s
All checks were successful
Push to Helm Chart Repository / build (push) Successful in 9s
This commit is contained in:
@ -15,6 +15,7 @@ import { listLabels } from "@/api/label";
|
|||||||
import { POST_SORT_TYPE } from "@/localstore";
|
import { POST_SORT_TYPE } from "@/localstore";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useDevice } from "@/hooks/use-device";
|
import { useDevice } from "@/hooks/use-device";
|
||||||
|
import { checkIsMobile } from "@/utils/client/device";
|
||||||
|
|
||||||
// 定义排序类型
|
// 定义排序类型
|
||||||
type SortType = 'latest' | 'popular';
|
type SortType = 'latest' | 'popular';
|
||||||
@ -23,7 +24,6 @@ export default function BlogHome() {
|
|||||||
const [labels, setLabels] = useState<Label[]>([]);
|
const [labels, setLabels] = useState<Label[]>([]);
|
||||||
const [posts, setPosts] = useState<Post[]>([]);
|
const [posts, setPosts] = useState<Post[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { isMobile } = useDevice();
|
|
||||||
const [sortType, setSortType, sortTypeLoaded] = useStoredState<SortType>(POST_SORT_TYPE, 'latest');
|
const [sortType, setSortType, sortTypeLoaded] = useStoredState<SortType>(POST_SORT_TYPE, 'latest');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sortTypeLoaded) return;
|
if (!sortTypeLoaded) return;
|
||||||
@ -90,7 +90,7 @@ export default function BlogHome() {
|
|||||||
{/* 主要内容区域 */}
|
{/* 主要内容区域 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
className="lg:col-span-3 self-start"
|
className="lg:col-span-3 self-start"
|
||||||
initial={{ y: isMobile ? 30 : 60, opacity: 0 }}
|
initial={{ y: checkIsMobile() ? 30 : 60, opacity: 0 }}
|
||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
transition={{ duration: config.animationDurationSecond, ease: "easeOut" }}>
|
transition={{ duration: config.animationDurationSecond, ease: "easeOut" }}>
|
||||||
{/* 文章列表标题 */}
|
{/* 文章列表标题 */}
|
||||||
@ -155,9 +155,9 @@ export default function BlogHome() {
|
|||||||
|
|
||||||
{/* 侧边栏 */}
|
{/* 侧边栏 */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={isMobile ? { y: 30, opacity: 0 } : { x: 80, opacity: 0 }}
|
initial={checkIsMobile() ? { y: 30, opacity: 0 } : { x: 80, opacity: 0 }}
|
||||||
animate={{ x: 0, y: 0, opacity: 1 }}
|
animate={{ x: 0, y: 0, opacity: 1 }}
|
||||||
transition={{ duration: config.animationDurationSecond , ease: "easeOut" }}
|
transition={{ duration: config.animationDurationSecond, ease: "easeOut" }}
|
||||||
>
|
>
|
||||||
<Sidebar
|
<Sidebar
|
||||||
cards={[
|
cards={[
|
||||||
|
@ -73,11 +73,8 @@ async function PostHeader({ post }: { post: Post }) {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={{ zIndex: -1 }}
|
style={{ zIndex: -1 }}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<div
|
||||||
initial={{ opacity: 0, x: -50 }}
|
className={`container mx-auto px-4`}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ duration: config.animationDurationSecond, ease: "easeOut" }}
|
|
||||||
className="container mx-auto px-4"
|
|
||||||
>
|
>
|
||||||
{(post.labels || post.isOriginal) && (
|
{(post.labels || post.isOriginal) && (
|
||||||
<div className="flex flex-wrap gap-2 mb-4">
|
<div className="flex flex-wrap gap-2 mb-4">
|
||||||
@ -98,7 +95,7 @@ async function PostHeader({ post }: { post: Post }) {
|
|||||||
<div>
|
<div>
|
||||||
<PostMeta post={post} />
|
<PostMeta post={post} />
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -145,10 +142,23 @@ async function PostContent({ post }: { post: Post }) {
|
|||||||
|
|
||||||
async function BlogPost({ post }: { post: Post }) {
|
async function BlogPost({ post }: { post: Post }) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full"
|
||||||
|
>
|
||||||
{/* <ScrollToTop /> */}
|
{/* <ScrollToTop /> */}
|
||||||
<PostHeader post={post} />
|
<motion.div
|
||||||
<PostContent post={post} />
|
initial={{ opacity: 0, y: -30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: config.animationDurationSecond, ease: "easeOut" }}>
|
||||||
|
<PostHeader post={post} />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 30 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: config.animationDurationSecond, ease: "easeOut" }}>
|
||||||
|
<PostContent post={post} />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
<CommentSection targetType={TargetType.Post} targetId={post.id} totalCount={post.commentCount} />
|
<CommentSection targetType={TargetType.Post} targetId={post.id} totalCount={post.commentCount} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -6,67 +6,43 @@ import "highlight.js/styles/github.css"; // 你可以换成喜欢的主题
|
|||||||
import "highlight.js/styles/github-dark.css"; // 适用于暗黑模式
|
import "highlight.js/styles/github-dark.css"; // 适用于暗黑模式
|
||||||
import "highlight.js/styles/github-dark-dimmed.css"; // 适用于暗黑模式
|
import "highlight.js/styles/github-dark-dimmed.css"; // 适用于暗黑模式
|
||||||
import CodeBlock from "@/components/common/markdown-codeblock";
|
import CodeBlock from "@/components/common/markdown-codeblock";
|
||||||
import * as motion from "motion/react-client"
|
|
||||||
import config from "@/config";
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
|
|
||||||
export function MotionDiv(props: React.ComponentPropsWithoutRef<"div">) {
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0.3, y: 50 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: config.animationDurationSecond, ease: "easeOut" }}
|
|
||||||
>{props.children}
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const markdownComponents = {
|
export const markdownComponents = {
|
||||||
h1: (props: React.ComponentPropsWithoutRef<"h1">) => (
|
h1: (props: React.ComponentPropsWithoutRef<"h1">) => (
|
||||||
<MotionDiv>
|
|
||||||
<h1
|
<h1
|
||||||
className="scroll-m-20 text-4xl font-extrabold tracking-tight text-balance mt-10 mb-6"
|
className="scroll-m-20 text-4xl font-extrabold tracking-tight text-balance mt-10 mb-6"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</MotionDiv>
|
|
||||||
|
|
||||||
),
|
),
|
||||||
h2: (props: React.ComponentPropsWithoutRef<"h2">) => (
|
h2: (props: React.ComponentPropsWithoutRef<"h2">) => (
|
||||||
<MotionDiv>
|
|
||||||
<h2
|
<h2
|
||||||
className="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 mt-8 mb-4"
|
className="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 mt-8 mb-4"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</MotionDiv>
|
|
||||||
),
|
),
|
||||||
h3: (props: React.ComponentPropsWithoutRef<"h3">) => (
|
h3: (props: React.ComponentPropsWithoutRef<"h3">) => (
|
||||||
<MotionDiv>
|
|
||||||
<h3
|
<h3
|
||||||
className="scroll-m-20 text-2xl font-semibold tracking-tight mt-6 mb-3"
|
className="scroll-m-20 text-2xl font-semibold tracking-tight mt-6 mb-3"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</MotionDiv>
|
|
||||||
),
|
),
|
||||||
h4: (props: React.ComponentPropsWithoutRef<"h4">) => (
|
h4: (props: React.ComponentPropsWithoutRef<"h4">) => (
|
||||||
<MotionDiv>
|
|
||||||
<h4
|
<h4
|
||||||
className="scroll-m-20 text-xl font-semibold tracking-tight mt-5 mb-2"
|
className="scroll-m-20 text-xl font-semibold tracking-tight mt-5 mb-2"
|
||||||
{...props}
|
{...props}
|
||||||
/></MotionDiv>
|
/>
|
||||||
),
|
),
|
||||||
p: (props: React.ComponentPropsWithoutRef<"p">) => (
|
p: (props: React.ComponentPropsWithoutRef<"p">) => (
|
||||||
<MotionDiv>
|
|
||||||
<div className="leading-7 mt-4 mb-4">{props.children}</div>
|
<div className="leading-7 mt-4 mb-4">{props.children}</div>
|
||||||
</MotionDiv>
|
|
||||||
),
|
),
|
||||||
blockquote: (props: React.ComponentPropsWithoutRef<"blockquote">) => (
|
blockquote: (props: React.ComponentPropsWithoutRef<"blockquote">) => (
|
||||||
<MotionDiv>
|
|
||||||
<blockquote
|
<blockquote
|
||||||
className="border-l-4 border-blue-400 pl-4 italic my-6 py-2"
|
className="border-l-4 border-blue-400 pl-4 italic my-6 py-2"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</MotionDiv>
|
|
||||||
),
|
),
|
||||||
code: (props: React.ComponentPropsWithoutRef<"code">) => (
|
code: (props: React.ComponentPropsWithoutRef<"code">) => (
|
||||||
<code
|
<code
|
||||||
@ -75,7 +51,7 @@ export const markdownComponents = {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
pre: ({ children, ...props }: React.ComponentPropsWithoutRef<"pre">) => (
|
pre: ({ children, ...props }: React.ComponentPropsWithoutRef<"pre">) => (
|
||||||
<MotionDiv><CodeBlock {...props}>{children}</CodeBlock></MotionDiv>
|
<CodeBlock {...props}>{children}</CodeBlock>
|
||||||
),
|
),
|
||||||
a: (props: React.ComponentPropsWithoutRef<"a">) => (
|
a: (props: React.ComponentPropsWithoutRef<"a">) => (
|
||||||
<a
|
<a
|
||||||
|
3
web/src/utils/client/device.ts
Normal file
3
web/src/utils/client/device.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function checkIsMobile() {
|
||||||
|
return typeof window !== "undefined" && window.innerWidth <= 768;
|
||||||
|
}
|
Reference in New Issue
Block a user