Skip to content

Commit a917884

Browse files
committed
feat: 博文图片预加载
1 parent aa38816 commit a917884

File tree

7 files changed

+92
-24
lines changed

7 files changed

+92
-24
lines changed

markdown/output.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
"text": "记录一下建站的第一天,也许开心到爆炸.\r\n![建站图片](https://upload-bbs.miyoushe.com/upload/2024/07/27/75276539/98580c852764d70e5a9597aa7678f131_2245446554692880272.jpg)\r\n![建站图片](https://upload-bbs.miyoushe.com/upload/2024/03/20/285532152/757fa74f8b38fdfab0bd1b653a69af4d_6553329811603610700.gif)",
1313
"count": "268",
1414
"readingTime": "1 ",
15+
"imageUrls": [
16+
"https://upload-bbs.miyoushe.com/upload/2024/07/27/75276539/98580c852764d70e5a9597aa7678f131_2245446554692880272.jpg",
17+
"https://upload-bbs.miyoushe.com/upload/2024/03/20/285532152/757fa74f8b38fdfab0bd1b653a69af4d_6553329811603610700.gif"
18+
],
1519
"createdAt": "2024-11-13T12:48:31.000Z",
16-
"updatedAt": "2024-11-13T12:52:18.000Z",
20+
"updatedAt": "2024-11-13T13:01:51.000Z",
1721
"modified": true
1822
}
1923
},
@@ -30,8 +34,12 @@
3034
"text": "记录一下建站的第一天,也许开心到爆炸.\r\n![建站图片](https://upload-bbs.miyoushe.com/upload/2024/07/27/75276539/98580c852764d70e5a9597aa7678f131_2245446554692880272.jpg)\r\n![建站图片](https://upload-bbs.miyoushe.com/upload/2024/03/20/285532152/757fa74f8b38fdfab0bd1b653a69af4d_6553329811603610700.gif)",
3135
"count": "268",
3236
"readingTime": "1 ",
37+
"imageUrls": [
38+
"https://upload-bbs.miyoushe.com/upload/2024/07/27/75276539/98580c852764d70e5a9597aa7678f131_2245446554692880272.jpg",
39+
"https://upload-bbs.miyoushe.com/upload/2024/03/20/285532152/757fa74f8b38fdfab0bd1b653a69af4d_6553329811603610700.gif"
40+
],
3341
"createdAt": "2024-11-13T12:48:31.000Z",
34-
"updatedAt": "2024-11-13T12:52:18.000Z",
42+
"updatedAt": "2024-11-13T13:01:51.000Z",
3543
"modified": true
3644
}
3745
]

src/app/(app)/projects/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
'use client';
22

33
import Image from 'next/image';
4+
import Link from 'next/link';
45

56
import { GitHubBrandIcon } from '@/components/icons/platform/GitHubBrandIcon';
67
import { cn } from '@/lib/helper';
78
import { ProjectModel, projectList } from '~/index';
8-
import { PrefetchLink } from '@/components/modules/shared/PrefetchLink';
99

1010
export default function Projects() {
1111
return (
@@ -41,7 +41,7 @@ const ProjectCardList = ({ data }: { data: ProjectModel[] }) => (
4141
);
4242
const ProjectCard = ({ project }: { project: ProjectModel }) => {
4343
return (
44-
<PrefetchLink
44+
<Link
4545
href={project.url}
4646
key={project.id}
4747
className="group flex shrink-0 grid-cols-[60px_2fr] flex-col items-center gap-4 md:grid"
@@ -59,7 +59,7 @@ const ProjectCard = ({ project }: { project: ProjectModel }) => {
5959
{project.desc}
6060
</span>
6161
</span>
62-
</PrefetchLink>
62+
</Link>
6363
);
6464
};
6565

src/components/layout/Footer/Footer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ const Footer = () => {
1313
<div className="relative mx-auto max-w-7xl lg:px-8 h-full flex flex-row items-center justify-around">
1414
<div className=" flex items-center gap-x-4 font-mono">
1515
<span className="">
16-
<PrefetchLink href={'/'}>
16+
<PrefetchLink href={'/about'}>
1717
<span className=" relative before:content-[''] before:absolute before:bottom-[-2px] before:w-[0px] hover:before:w-[100%] before:h-[1px] before:bg-[var(--accent-color)] before:transition-all before:duration-300">
1818
关于我
1919
</span>
2020
</PrefetchLink>
2121
</span>
2222
<span className="">
23-
<Link href={'/'}>
23+
<Link href={'https://github.com/coderz-w/blog'}>
2424
<span className=" relative before:content-[''] before:absolute before:bottom-[-2px] before:w-[0px] hover:before:w-[100%] before:h-[1px] before:bg-[var(--accent-color)] before:transition-all before:duration-300">
2525
此项目
2626
</span>

src/components/modules/list/PostItem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const PostItem = memo<{ data: PostItemType }>(function PostItem({ data })
1414
return (
1515
<PrefetchLink
1616
href={postLink}
17+
preFetchImages={data.imageUrls}
1718
className="relative flex flex-col py-8 focus-visible:!shadow-none gap-2"
1819
>
1920
<PostItemHoverOverlay />
Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,73 @@
11
'use client';
22

33
import Link from 'next/link';
4-
import { useState, useCallback } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { useState } from 'react';
6+
7+
const loadedImages = new Set<string>();
58

69
interface PrefetchLinkProps extends React.ComponentProps<typeof Link> {
710
href: string;
11+
preFetchImages: string[];
812
}
913

10-
export const PrefetchLink: React.FC<PrefetchLinkProps> = ({ href, children, ...props }) => {
11-
const [prefetch, setPrefetch] = useState(false);
12-
13-
const handleMouseEnter = useCallback(() => {
14-
setPrefetch(true);
15-
}, []);
14+
export const PrefetchLink: React.FC<PrefetchLinkProps> = ({
15+
href,
16+
preFetchImages,
17+
children,
18+
...props
19+
}) => {
20+
const [preloading, setPreloading] = useState<(() => void)[]>([]);
21+
const router = useRouter();
1622

1723
return (
18-
<Link href={href} prefetch={prefetch} onMouseEnter={handleMouseEnter} {...props}>
24+
<Link
25+
href={href}
26+
prefetch={false}
27+
onMouseEnter={() => {
28+
router.prefetch(href);
29+
if (!preFetchImages || preFetchImages.length < 1) return;
30+
31+
const p: (() => void)[] = [];
32+
33+
for (const image of preFetchImages) {
34+
const remove = prefetchImage(image);
35+
if (remove) p.push(remove);
36+
}
37+
38+
setPreloading(p);
39+
}}
40+
onMouseLeave={() => {
41+
for (const remove of preloading) {
42+
remove();
43+
}
44+
45+
setPreloading([]);
46+
}}
47+
{...props}
48+
>
1949
{children}
2050
</Link>
2151
);
2252
};
53+
54+
function prefetchImage(image: string) {
55+
if (loadedImages.has(image)) return;
56+
57+
const img = new Image();
58+
img.decoding = 'async';
59+
img.fetchPriority = 'low';
60+
img.src = image;
61+
62+
let done = false;
63+
64+
img.onload = img.onerror = () => {
65+
done = true;
66+
loadedImages.add(image);
67+
};
68+
69+
return () => {
70+
if (done) return;
71+
img.src = '';
72+
};
73+
}

src/components/ui/focus-cards.tsx/FocusCards.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use client';
22

33
import Image from 'next/image';
4-
import { useRouter } from 'next/navigation';
5-
import React, { useCallback, useState } from 'react';
4+
import React, { useState } from 'react';
65

76
import { cn } from '@/lib/helper';
8-
import type { PostItem as PostItemType } from '@/core';
97
import dayjs from '@/lib/dayjs';
8+
import type { PostItem as PostItemType } from '@/core';
9+
import { PrefetchLink } from '@/components/modules/shared/PrefetchLink';
1010

1111
export const Card = React.memo(
1212
({
@@ -20,14 +20,12 @@ export const Card = React.memo(
2020
hovered: number | null;
2121
setHovered: React.Dispatch<React.SetStateAction<number | null>>;
2222
}) => {
23-
const router = useRouter();
2423
const postLink = `/notes/${card.path}`;
2524

2625
return (
27-
<div
28-
onClick={useCallback(() => {
29-
router.push(postLink);
30-
}, [])}
26+
<PrefetchLink
27+
href={postLink}
28+
preFetchImages={card.imageUrls}
3129
className={cn(
3230
'rounded-lg relative bg-gray-100 dark:bg-neutral-900 overflow-hidden h-60 md:h-96 w-full transition-all duration-300 ease-out cursor-pointer',
3331
hovered !== null && hovered !== index && 'blur-sm scale-[0.98]',
@@ -70,7 +68,7 @@ export const Card = React.memo(
7068
</div>
7169
</div>
7270
</div>
73-
</div>
71+
</PrefetchLink>
7472
);
7573
},
7674
);

src/core/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type PostItem = {
1919
modified: boolean;
2020
coverImage: string;
2121
summary?: string;
22+
imageUrls: string[];
2223
};
2324

2425
export type PostJsonType = {
@@ -32,6 +33,8 @@ export type PostJsonType = {
3233

3334
export type PostMap = Record<string, PostItem>;
3435

36+
const imgRegex = /!\[.*?\]\((http[s]?:\/\/[^\s\)]+\.(?:jpg|jpeg|png|gif|bmp|svg|webp|tiff|ico))\)/g;
37+
3538
export function importMarkdownFile(path: string) {
3639
const markdownContext = (require as any).context('../../markdown', true, /\.md$/);
3740

@@ -64,6 +67,12 @@ export function buildPostData() {
6467
const file = importMarkdownFile(`./${path}`);
6568
const createdAt = getFirstGitCommitTime(join('markdown/', path));
6669
const updatedAt = getLastGitUpdateTime(join('markdown/', path));
70+
let match;
71+
const imageUrls = [];
72+
73+
while ((match = imgRegex.exec(file)) !== null) {
74+
imageUrls.push(match[1]);
75+
}
6776

6877
const postItem: PostItem = {
6978
authors,
@@ -76,6 +85,7 @@ export function buildPostData() {
7685
text: file,
7786
count: symbolsCount(file),
7887
readingTime: symbolsTime(file, 0, 200),
88+
imageUrls: imageUrls,
7989
createdAt,
8090
updatedAt,
8191
modified: updatedAt && createdAt ? updatedAt.getTime() !== createdAt.getTime() : false,

0 commit comments

Comments
 (0)