Skip to content

Commit 3c43ccb

Browse files
committed
perf: optimize image load and anchor hash location
1 parent e66b2ee commit 3c43ccb

File tree

9 files changed

+134
-40
lines changed

9 files changed

+134
-40
lines changed

docs/.vitepress/components/BodyScrollbar.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ onMounted(() => {
1515
});
1616
const body = useElementBounding(bodyRef);
1717
18-
const errorDistance = 5;
18+
// see https://github.com/user-attachments/assets/89796d25-b360-4486-9cf7-79a5e598022c
19+
const errorDistance = 2;
1920
2021
const yShow = computed(() => body.height.value > winH.value + errorDistance);
2122
const yHeight = computed(() => {

docs/.vitepress/components/GImg.ts

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,65 @@
1-
import { defineComponent, h } from 'vue';
1+
import { computed, defineComponent, h, shallowReactive } from 'vue';
22
import { NImage } from './naive';
3+
import { imageSizeList } from '../data/mirror.load';
34
import { convertSrc } from '../utils/img';
45

6+
const imgLoadMap = shallowReactive<Record<string, string | boolean>>({});
7+
8+
const preLoadImg = async (url: string) => {
9+
const img = new Image();
10+
img.src = url;
11+
await new Promise<void>((res, rej) => {
12+
img.onload = () => res();
13+
img.onerror = rej;
14+
});
15+
imgLoadMap[url] = true;
16+
// const r = await fetch(url);
17+
// const b = await r.blob();
18+
// imgLoadMap[url] = URL.createObjectURL(b);
19+
};
20+
21+
const getImgPlaceholderUrl = (url: string): string => {
22+
if (!import.meta.env.SSR) {
23+
preLoadImg(url);
24+
}
25+
const size = imageSizeList.find((i) => url.endsWith(i.name));
26+
if (!size) {
27+
console.error(`Image size not found for ${url}`);
28+
return url;
29+
}
30+
return (
31+
'data:image/svg+xml,' +
32+
encodeURIComponent(
33+
`<svg xmlns="http://www.w3.org/2000/svg" width="${size.width}" height="${size.height}" viewBox="0 0 ${size.width} ${size.height}"></svg>`,
34+
)
35+
);
36+
};
37+
538
const GImg = defineComponent<{
639
src: string;
7-
width?: number | string;
840
}>(
9-
(props) => {
41+
(props, ctx) => {
42+
const rawSrc = computed(() => convertSrc(props.src));
43+
const src = computed(() => {
44+
const u = imgLoadMap[rawSrc.value];
45+
if (u === true) {
46+
return rawSrc.value;
47+
}
48+
if (typeof u === 'string') {
49+
return u;
50+
}
51+
return getImgPlaceholderUrl(rawSrc.value);
52+
});
1053
return () => {
1154
return h(NImage, {
12-
src: convertSrc(props.src),
13-
width: props.width,
55+
src: src.value,
56+
'data-src': rawSrc.value,
57+
...ctx.attrs,
1458
});
1559
};
1660
},
1761
{
18-
props: ['src', 'width'],
62+
props: ['src'],
1963
},
2064
);
2165

docs/.vitepress/components/ImageTable.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { convertSrc } from '../utils/img';
3-
import { NImage, NImageGroup } from './naive';
3+
import GImg from './GImg';
4+
import { NImageGroup } from './naive';
45
56
withDefaults(
67
defineProps<{
@@ -15,7 +16,7 @@ withDefaults(
1516
<NImageGroup>
1617
<tr v-for="(imgs, i) in images" :key="i">
1718
<td v-for="src in imgs" :key="src">
18-
<NImage :src="convertSrc(src)" />
19+
<GImg :src="convertSrc(src)" />
1920
</td>
2021
</tr>
2122
</NImageGroup>

docs/.vitepress/data/mirror.load.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ const getPkgLatestVersion = async (name: string): Promise<string> => {
88
};
99

1010
const version = await getPkgLatestVersion('@gkd-kit/assets');
11-
const data = `https://registry.npmmirror.com/@gkd-kit/assets/${version}/files/assets/`;
11+
export const assetsBaseUrl = `https://registry.npmmirror.com/@gkd-kit/assets/${version}/files/assets/`;
1212

13-
export default data;
13+
interface ImageSize {
14+
width: number;
15+
height: number;
16+
name: string;
17+
}
18+
19+
export const imageSizeList: ImageSize[] = await fetch(
20+
assetsBaseUrl + 'image-size.json',
21+
).then((r) => r.json());

docs/.vitepress/theme/index.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'uno.css';
2-
import { type Router, useRouter, type Theme } from 'vitepress';
2+
import { type Router, type Theme, useRouter } from 'vitepress';
33
import DefaultTheme from 'vitepress/theme';
44
import {
55
defineComponent,
@@ -47,6 +47,34 @@ const checkAllImagesLoaded = () => {
4747
return true;
4848
};
4949

50+
const delay = (n = 0) => new Promise((r) => setTimeout(r, n));
51+
52+
let lastHashEl: HTMLElement | undefined = undefined;
53+
let newPage = true;
54+
const animateHashEl = async (hashEl?: HTMLElement) => {
55+
if (location.hash) {
56+
hashEl ??= document.querySelector<HTMLElement>(location.hash) || undefined;
57+
}
58+
if (!hashEl) return;
59+
const hintCls = 'animate-hash-hint';
60+
if (lastHashEl) {
61+
lastHashEl.classList.remove(hintCls);
62+
}
63+
if (hashEl.classList.contains(hintCls)) {
64+
hashEl.classList.remove(hintCls);
65+
await delay(25);
66+
}
67+
hashEl.classList.add(hintCls);
68+
lastHashEl = hashEl;
69+
const ms = 500 * 2 * (newPage ? 5 : 2);
70+
newPage = false;
71+
await delay(ms);
72+
hashEl.classList.remove(hintCls);
73+
if (lastHashEl === hashEl) {
74+
lastHashEl = undefined;
75+
}
76+
};
77+
5078
const handleCompatRedirect = async (router: Router) => {
5179
// 兼容旧链接/短链重定向
5280
const u = location.href.substring(location.origin.length);
@@ -95,39 +123,55 @@ const handleCompatRedirect = async (router: Router) => {
95123
const hashEl = await (async () => {
96124
const href = location.href;
97125
let i = 0;
98-
while (i < 20) {
126+
while (i < 25) {
99127
if (href !== location.href) return;
100128
const el = document.querySelector<HTMLElement>(location.hash);
101129
if (el) {
102130
return el;
103131
}
104132
i++;
105-
await new Promise((r) => setTimeout(r, 250));
133+
await delay(100);
106134
}
107135
})();
108136
if (hashEl) {
109137
if (!isInViewport(hashEl)) {
110-
// 图片加载完成会导致排版变化,此处手动判断后滚动到视口中
111-
// 也许后续可实现在构建时提前获取 size 后设置 aspect-ratio
138+
// 图片加载完成会导致排版变化, 即使提前使用相同比例的占位图也无法避免排版变化
112139
let i = 0;
113140
while (i < 25 && !checkAllImagesLoaded()) {
114141
await new Promise((r) => setTimeout(r, 100));
115142
i++;
116143
}
117-
hashEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
144+
const anchor = document.querySelector(
145+
`a.header-anchor[href="${location.hash}"]`,
146+
) as HTMLAnchorElement;
147+
if (anchor) {
148+
anchor.click();
149+
} else {
150+
hashEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
151+
}
118152
}
119-
const hintCls = 'animate-hash-hint';
120-
hashEl.classList.add(hintCls);
121-
await new Promise((r) => setTimeout(r, 500 * 2 * 5));
122-
hashEl.classList.remove(hintCls);
153+
animateHashEl(hashEl);
123154
}
124155
}
125156
};
126157

158+
if (!import.meta.env.SSR) {
159+
window.addEventListener('hashchange', async () => {
160+
animateHashEl();
161+
});
162+
}
163+
127164
const Redirect = defineComponent(() => {
128165
const router = useRouter();
129166
onMounted(() => {
130167
handleCompatRedirect(router);
168+
router.onAfterPageLoad = () => {
169+
console.log('page loaded');
170+
nextTick().then(async () => {
171+
await delay(100);
172+
animateHashEl();
173+
});
174+
};
131175
});
132176
return () => {};
133177
});

docs/.vitepress/utils/img.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import mirrorHost from '../data/mirror.load';
2-
const imgHost = 'https://a.gkd.li/';
1+
import { assetsBaseUrl } from '../data/mirror.load';
32

43
export const convertSrc = (name: string): string => {
5-
if (name && name.startsWith('https:')) {
6-
if (name.startsWith(imgHost)) {
7-
return mirrorHost + name.slice(imgHost.length);
8-
}
4+
if (name.startsWith('https:')) {
95
return name;
106
}
11-
return mirrorHost + name;
7+
return assetsBaseUrl + name;
128
};

docs/guide/selector.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,11 @@
264264

265265
| 操作符 | 名称 | 图例 | 选择器 |
266266
| :----: | :----------------: | :---------------------------------: | :-----------------------------------------------------------------------: |
267-
| + | 前置兄弟节点 | <GImg src="0020.png" width="250" /> | [\* + \[\_id=33\]](https://i.gkd.li/i/14045424?gkd=KiArIFtfaWQ9MzNd) |
268-
| - | 后置兄弟节点 | <GImg src="0021.png" width="250" /> | [\* - \[\_id=32\]](https://i.gkd.li/i/14045424?gkd=KiAtIFtfaWQ9MzJd) |
269-
| > | 祖先节点 | <GImg src="0022.png" width="250" /> | [\* > \[\_id=90\]](https://i.gkd.li/i/14045424?gkd=KiA-IFtfaWQ9OTBd) |
270-
| < | 直接子节点 | <GImg src="0023.png" width="250" /> | [\* < \[\_id=89\]](https://i.gkd.li/i/14045424?gkd=KiA8IFtfaWQ9ODld) |
271-
| << | 子孙节点(深度先序) | <GImg src="0024.png" width="250" /> | [\* <<2 \[\_id=29\]](https://i.gkd.li/i/14045424?gkd=KiA8PDIgW19pZD0yOV0) |
267+
| + | 前置兄弟节点 | <GImg src="0020.png" w-250px /> | [\* + \[\_id=33\]](https://i.gkd.li/i/14045424?gkd=KiArIFtfaWQ9MzNd) |
268+
| - | 后置兄弟节点 | <GImg src="0021.png" w-250px /> | [\* - \[\_id=32\]](https://i.gkd.li/i/14045424?gkd=KiAtIFtfaWQ9MzJd) |
269+
| > | 祖先节点 | <GImg src="0022.png" w-250px /> | [\* > \[\_id=90\]](https://i.gkd.li/i/14045424?gkd=KiA-IFtfaWQ9OTBd) |
270+
| < | 直接子节点 | <GImg src="0023.png" w-250px /> | [\* < \[\_id=89\]](https://i.gkd.li/i/14045424?gkd=KiA8IFtfaWQ9ODld) |
271+
| << | 子孙节点(深度先序) | <GImg src="0024.png" w-250px /> | [\* <<2 \[\_id=29\]](https://i.gkd.li/i/14045424?gkd=KiA8PDIgW19pZD0yOV0) |
272272

273273
</NImageGroup>
274274

docs/guide/subscription.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ https://gist.github.com/lisonge/3f5693182ad4ef5e307be760dba22bcb/raw/gkd.json5
3838

3939
位置: 首页-订阅-本地订阅-应用规则
4040

41-
<GImg src="https://e.gkd.li/76d0bc58-de43-416d-9454-f980e8075660" max-w="[min(375px,100%)]" />
41+
<GImg src="0041.jpg" max-w="[min(375px,100%)]" />
4242

4343
此处可添加 [应用规则](/api/interfaces/RawApp), 如下是一个简单的规则示例, 它由 [快照-13070251](https://i.gkd.li/i/13070251?gkd=VGV4dFZpZXdbaWQ9ImNvbS56aGlodS5hbmRyb2lkOmlkL2J0bl9za2lwIl0) 而来
4444

@@ -61,7 +61,7 @@ https://gist.github.com/lisonge/3f5693182ad4ef5e307be760dba22bcb/raw/gkd.json5
6161

6262
位置: 首页-订阅-本地订阅-应用规则-应用
6363

64-
<GImg src="https://e.gkd.li/6497fe09-b019-46b9-8157-d4b21809b4fc" max-w="[min(375px,100%)]" />
64+
<GImg src="0042.jpg" max-w="[min(375px,100%)]" />
6565

6666
此处可添加 [应用规则组](/api/interfaces/RawAppGroup), 如下是一个简单的规则示例, 它由 [快照-14310618](https://i.gkd.li/i/14310618?gkd=W3ZpZD0iaW1nX2Nsb3NlIl0) 而来
6767

@@ -85,7 +85,7 @@ https://gist.github.com/lisonge/3f5693182ad4ef5e307be760dba22bcb/raw/gkd.json5
8585

8686
位置: 首页-订阅-本地订阅-应用规则-应用-规则组右侧三个点-编辑禁用
8787

88-
<GImg src="https://e.gkd.li/8af0bc31-8a23-4342-b2f3-7a9378f9e094" max-w="[min(375px,100%)]" />
88+
<GImg src="0043.jpg" max-w="[min(375px,100%)]" />
8989

9090
此处可添加需要禁用的 [activityid](/api/interfaces/RawAppRule#activityids), 如下是一个简单的 activityid 示例
9191

@@ -98,7 +98,7 @@ com.tencent.mm.plugin.sns.ui.improve.ImproveSnsTimelineUI
9898

9999
位置: 首页-订阅-本地订阅-全局规则
100100

101-
<GImg src="https://e.gkd.li/f51545c0-d3aa-4ec7-87eb-adf927023640" max-w="[min(375px,100%)]" />
101+
<GImg src="0044.jpg" max-w="[min(375px,100%)]" />
102102

103103
此处可添加 [全局规则](/api/interfaces/RawGlobalRule), 如下是一个简单的规则示例
104104

@@ -123,7 +123,7 @@ com.tencent.mm.plugin.sns.ui.improve.ImproveSnsTimelineUI
123123

124124
位置: 首页-订阅-本地订阅-规则类别
125125

126-
<GImg src="https://e.gkd.li/6784867c-4fa9-4a06-abf7-b332ba2945de" max-w="[min(375px,100%)]" />
126+
<GImg src="0045.jpg" max-w="[min(375px,100%)]" />
127127

128128
此处可添加 [规则类别](/api/interfaces/RawCategory), 在输入框中输入类别名称点击确认即可
129129

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
"vitepress": "1.6.3"
3333
},
3434
"volta": {
35-
"node": "22.12.0",
36-
"pnpm": "10.6.5"
35+
"node": "22.15.0",
36+
"pnpm": "10.10.0"
3737
},
3838
"packageManager": "pnpm@10.6.5",
3939
"pnpm": {

0 commit comments

Comments
 (0)