feat(v1.2.5): 优化首页滚动顺滑性、恢复整屏吸附并提升 Halo 要求至 2.27
- 首页固定模糊半径只改 opacity;Hero 离屏停绘;Navbar 滚动状态去重 - 恢复首屏整屏吸附:仅接管 0 ↔ 内容区顶部边界,scrollend + 600ms 兜底解锁 - CSS scroll-snap proximity 实际不吸附(缺页面顶部吸附点),已移除避免与 scrollTo 冲突 - 版本 1.2.5,requires >=2.27.0;源码仓库与问题反馈改为 git.nxxy335.top - 更新 README / FAQ / 开发指南 / 更新日志,补充性能与发版说明
This commit is contained in:
1 parent
6c612911b3
commit
c0ad7331d4
19 files changed
+264
-120
No files matched your search
@@ -6,7 +6,7 @@
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Halo **2.26.0 及以上**(页面布局契约、微信读书页面均依赖 2.26 的新能力)
|
||||
- Halo **2.27.0 及以上**(页面布局契约、微信读书页面均依赖 2.27 的新能力)
|
||||
- v1.2.0 起内置主题配色系统(纯 CSS 变量实现);更低版本的 Halo 请使用主题 v1.1.x
|
||||
|
||||
## 特点
|
||||
@@ -15,7 +15,7 @@
|
||||
2. **插件兼容性** — 适配 11 款常用 Halo 插件,安装即用;提供页面布局契约,插件前台页面自动复用主题外观
|
||||
3. **内置灯箱** — 主题内置 LightGallery 灯箱,支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用
|
||||
4. **可配置性** — 17 个设置分组、122 个配置项,几乎所有模块可独立开关
|
||||
5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、滚动吸附、CSS/HTML 压缩
|
||||
5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、首屏整屏吸附(仅首屏边界接管,内容区保留原生滚动)、Hero 离屏停绘、CSS/HTML 压缩;详见[开发指南 - 性能与滚动顺滑](docs/customize/development.md)
|
||||
6. **页面布局契约** — 提供 `templates/layout.html`,插件页面不再回退到 Halo 内置布局
|
||||
|
||||
## 预览
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
- Hero 首屏:明暗双背景图、大标题/副标题独立配色、一言(支持自动刷新与淡入淡出过渡)、下滑箭头平滑滚动
|
||||
- 欢迎弹窗:基于访客 IP 地理定位的欢迎提示,支持 5 种位置、自定义 API 与会话去重开关
|
||||
- 滚动吸附:首屏与内容区之间滚轮/触摸自动吸附
|
||||
- 滚动吸附:首屏与内容区之间滚轮/触摸自动吸附,进入内容区后恢复原生自由滚动
|
||||
- 文章卡片:可配置显示字数(依赖 API 扩展包)、阅读量、分类、标签、摘要行数
|
||||
|
||||
### 文章
|
||||
@@ -177,7 +177,7 @@ pnpm smoke
|
||||
|
||||
## 技术栈
|
||||
|
||||
- [Halo](https://halo.run) 2.26+
|
||||
- [Halo](https://halo.run) 2.27+
|
||||
- [Astro](https://astro.build) 6(构建输出 Thymeleaf 模板)
|
||||
- [Thymeleaf](https://www.thymeleaf.org) 模板引擎
|
||||
- [Sass](https://sass-lang.com/) 样式
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@charset "UTF-8";:root{--bg: #faf7f2;--bg-raised: #f3ede5;--bg-overlay: color-mix(in srgb, var(--bg) 85%, transparent);--ink: #2c2420;--ink-2: #7a6e64;--ink-3: #b5a99e;--rule: #e8e0d6;--accent: #d4764e;--accent-hover: #c4613a;--accent-bg: #fdf0e8;--mist-pink: #f0e4de;--sea-salt: #e8e0d6;--caramel: #8b6f5e;--glass-bg: color-mix(in srgb, var(--bg) 72%, transparent);--glass-border: color-mix(in srgb, var(--rule) 50%, transparent);--shadow-sm: 0 1px 3px color-mix(in srgb, var(--ink) 6%, transparent);--shadow-md: 0 4px 16px color-mix(in srgb, var(--ink) 8%, transparent);--shadow-lg: 0 8px 32px color-mix(in srgb, var(--ink) 12%, transparent);--shadow-glow: 0 0 40px color-mix(in srgb, var(--accent) 15%, transparent)}html.dark{--bg: #1e1c1a;--bg-raised: #292623;--bg-overlay: color-mix(in srgb, var(--bg) 85%, transparent);--ink: #e9e4dd;--ink-2: #aaa096;--ink-3: #7c7268;--rule: #3b362f;--accent: #e89a64;--accent-hover: #f2ad79;--accent-bg: #2c231b;--mist-pink: #2e2823;--sea-salt: #302b26;--caramel: #a38b7b;--glass-bg: color-mix(in srgb, var(--bg) 72%, transparent);--glass-border: color-mix(in srgb, var(--rule) 50%, transparent);--shadow-sm: 0 1px 3px rgba(0, 0, 0, .24);--shadow-md: 0 4px 16px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 32px rgba(0, 0, 0, .4);--shadow-glow: 0 0 40px rgba(232, 154, 100, .12)}html[data-palette=matcha]{--bg: #f6f8f1;--bg-raised: #ecf1e3;--ink: #26302a;--ink-2: #6d7a6e;--ink-3: #a8b3a4;--rule: #dde5d4;--accent: #6f9d52;--accent-hover: #5d8943;--accent-bg: #ecf3e2;--mist-pink: #e7eddc;--sea-salt: #dfe8d3;--caramel: #7d8a68}html[data-palette=matcha].dark{--bg: #1b1f1a;--bg-raised: #252b22;--ink: #e6e9e0;--ink-2: #a3ab9c;--ink-3: #767e70;--rule: #363d33;--accent: #9cc47f;--accent-hover: #aed493;--accent-bg: #242c1f;--mist-pink: #262d22;--sea-salt: #282f24;--caramel: #98a387}html[data-palette=indigo]{--bg: #f5f7fa;--bg-raised: #eaeff5;--ink: #232a33;--ink-2: #66707d;--ink-3: #a3adb8;--rule: #dce2ea;--accent: #4a6fa5;--accent-hover: #3d5d8c;--accent-bg: #e9eff8;--mist-pink: #e2e8f0;--sea-salt: #dbe4ee;--caramel: #707c8e}html[data-palette=indigo].dark{--bg: #191d23;--bg-raised: #222831;--ink: #e4e7ec;--ink-2: #9aa4b1;--ink-3: #6d7683;--rule: #323a45;--accent: #7fa3d1;--accent-hover: #93b4dc;--accent-bg: #1f2632;--mist-pink: #232830;--sea-salt: #252b34;--caramel: #8b95a6}html[data-palette=sakura]{--bg: #faf5f6;--bg-raised: #f3eaec;--ink: #322a2c;--ink-2: #7d6e72;--ink-3: #b4a6aa;--rule: #ecdfe2;--accent: #d97387;--accent-hover: #c85d72;--accent-bg: #fbeef1;--mist-pink: #f2e2e6;--sea-salt: #eddfe3;--caramel: #96757e}html[data-palette=sakura].dark{--bg: #201b1c;--bg-raised: #2a2425;--ink: #ece5e6;--ink-2: #b0a4a7;--ink-3: #7f7276;--rule: #3c3436;--accent: #e89aab;--accent-hover: #f0aec0;--accent-bg: #2d2225;--mist-pink: #2f2729;--sea-salt: #31282a;--caramel: #a88d94}html[data-palette=violet]{--bg: #f7f5fa;--bg-raised: #eeeaf5;--ink: #2b2733;--ink-2: #6f6a7c;--ink-3: #aaa5b6;--rule: #e2ddeb;--accent: #8a6fc0;--accent-hover: #7759ae;--accent-bg: #f0eaf9;--mist-pink: #e8e2f0;--sea-salt: #e3dcee;--caramel: #7f7590}html[data-palette=violet].dark{--bg: #1c1a21;--bg-raised: #26222f;--ink: #e8e5ee;--ink-2: #a49eaf;--ink-3: #746e80;--rule: #373241;--accent: #b49ae0;--accent-hover: #c3ace8;--accent-bg: #262033;--mist-pink: #292434;--sea-salt: #2b2536;--caramel: #948caa}html[data-palette=sea]{--bg: #f3f8f7;--bg-raised: #e8f0ee;--ink: #21302d;--ink-2: #647872;--ink-3: #9fb1ac;--rule: #d9e6e3;--accent: #3d9c8f;--accent-hover: #2f8578;--accent-bg: #e5f3f0;--mist-pink: #dee9e6;--sea-salt: #d6e5e1;--caramel: #6c8680}html[data-palette=sea].dark{--bg: #181f1e;--bg-raised: #212a28;--ink: #e3eae8;--ink-2: #9aaba6;--ink-3: #6d7d79;--rule: #2f3b39;--accent: #6cc3b4;--accent-hover: #82d0c2;--accent-bg: #1d2a27;--mist-pink: #222c2a;--sea-salt: #242e2c;--caramel: #869894}html[data-palette=amber]{--bg: #faf7ef;--bg-raised: #f3eddc;--ink: #322b1e;--ink-2: #7d7360;--ink-3: #b5ab94;--rule: #eae1cb;--accent: #b97f1c;--accent-hover: #9f6a0e;--accent-bg: #f9efd8;--mist-pink: #f0e8d2;--sea-salt: #ebe2ca;--caramel: #96835c}html[data-palette=amber].dark{--bg: #201d16;--bg-raised: #2a261d;--ink: #ebe6d9;--ink-2: #b0a893;--ink-3: #7f7866;--rule: #3c3729;--accent: #e0a83e;--accent-hover: #ebba5c;--accent-bg: #2c251a;--mist-pink: #2f2a1e;--sea-salt: #312c20;--caramel: #a89a76}html[data-palette=plum]{--bg: #f9f4f5;--bg-raised: #f1e8ea;--ink: #2f2427;--ink-2: #796a6f;--ink-3: #b1a2a7;--rule: #e9dde0;--accent: #a4506b;--accent-hover: #8d3f59;--accent-bg: #f7e9ee;--mist-pink: #ecdee2;--sea-salt: #e5d8db;--caramel: #8d6f77}html[data-palette=plum].dark{--bg: #1f1a1b;--bg-raised: #292223;--ink: #eae2e4;--ink-2: #ada0a4;--ink-3: #7c6e73;--rule: #3a3133;--accent: #d98aa3;--accent-hover: #e39fb5;--accent-bg: #2b2023;--mist-pink: #2d2527;--sea-salt: #2f2729;--caramel: #a08890}html[data-palette=graphite]{--bg: #f7f7f6;--bg-raised: #eeedeb;--ink: #262626;--ink-2: #6e6e6c;--ink-3: #ababa8;--rule: #e0e0de;--accent: #3f3f3e;--accent-hover: #262626;--accentLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
@charset "UTF-8";:root{--bg: #faf7f2;--bg-raised: #f3ede5;--bg-overlay: color-mix(in srgb, var(--bg) 85%, transparent);--ink: #2c2420;--ink-2: #7a6e64;--ink-3: #b5a99e;--rule: #e8e0d6;--accent: #d4764e;--accent-hover: #c4613a;--accent-bg: #fdf0e8;--mist-pink: #f0e4de;--sea-salt: #e8e0d6;--caramel: #8b6f5e;--glass-bg: color-mix(in srgb, var(--bg) 72%, transparent);--glass-border: color-mix(in srgb, var(--rule) 50%, transparent);--shadow-sm: 0 1px 3px color-mix(in srgb, var(--ink) 6%, transparent);--shadow-md: 0 4px 16px color-mix(in srgb, var(--ink) 8%, transparent);--shadow-lg: 0 8px 32px color-mix(in srgb, var(--ink) 12%, transparent);--shadow-glow: 0 0 40px color-mix(in srgb, var(--accent) 15%, transparent)}html.dark{--bg: #1e1c1a;--bg-raised: #292623;--bg-overlay: color-mix(in srgb, var(--bg) 85%, transparent);--ink: #e9e4dd;--ink-2: #aaa096;--ink-3: #7c7268;--rule: #3b362f;--accent: #e89a64;--accent-hover: #f2ad79;--accent-bg: #2c231b;--mist-pink: #2e2823;--sea-salt: #302b26;--caramel: #a38b7b;--glass-bg: color-mix(in srgb, var(--bg) 72%, transparent);--glass-border: color-mix(in srgb, var(--rule) 50%, transparent);--shadow-sm: 0 1px 3px rgba(0, 0, 0, .24);--shadow-md: 0 4px 16px rgba(0, 0, 0, .3);--shadow-lg: 0 8px 32px rgba(0, 0, 0, .4);--shadow-glow: 0 0 40px rgba(232, 154, 100, .12)}html[data-palette=matcha]{--bg: #f6f8f1;--bg-raised: #ecf1e3;--ink: #26302a;--ink-2: #6d7a6e;--ink-3: #a8b3a4;--rule: #dde5d4;--accent: #6f9d52;--accent-hover: #5d8943;--accent-bg: #ecf3e2;--mist-pink: #e7eddc;--sea-salt: #dfe8d3;--caramel: #7d8a68}html[data-palette=matcha].dark{--bg: #1b1f1a;--bg-raised: #252b22;--ink: #e6e9e0;--ink-2: #a3ab9c;--ink-3: #767e70;--rule: #363d33;--accent: #9cc47f;--accent-hover: #aed493;--accent-bg: #242c1f;--mist-pink: #262d22;--sea-salt: #282f24;--caramel: #98a387}html[data-palette=indigo]{--bg: #f5f7fa;--bg-raised: #eaeff5;--ink: #232a33;--ink-2: #66707d;--ink-3: #a3adb8;--rule: #dce2ea;--accent: #4a6fa5;--accent-hover: #3d5d8c;--accent-bg: #e9eff8;--mist-pink: #e2e8f0;--sea-salt: #dbe4ee;--caramel: #707c8e}html[data-palette=indigo].dark{--bg: #191d23;--bg-raised: #222831;--ink: #e4e7ec;--ink-2: #9aa4b1;--ink-3: #6d7683;--rule: #323a45;--accent: #7fa3d1;--accent-hover: #93b4dc;--accent-bg: #1f2632;--mist-pink: #232830;--sea-salt: #252b34;--caramel: #8b95a6}html[data-palette=sakura]{--bg: #faf5f6;--bg-raised: #f3eaec;--ink: #322a2c;--ink-2: #7d6e72;--ink-3: #b4a6aa;--rule: #ecdfe2;--accent: #d97387;--accent-hover: #c85d72;--accent-bg: #fbeef1;--mist-pink: #f2e2e6;--sea-salt: #eddfe3;--caramel: #96757e}html[data-palette=sakura].dark{--bg: #201b1c;--bg-raised: #2a2425;--ink: #ece5e6;--ink-2: #b0a4a7;--ink-3: #7f7276;--rule: #3c3436;--accent: #e89aab;--accent-hover: #f0aec0;--accent-bg: #2d2225;--mist-pink: #2f2729;--sea-salt: #31282a;--caramel: #a88d94}html[data-palette=violet]{--bg: #f7f5fa;--bg-raised: #eeeaf5;--ink: #2b2733;--ink-2: #6f6a7c;--ink-3: #aaa5b6;--rule: #e2ddeb;--accent: #8a6fc0;--accent-hover: #7759ae;--accent-bg: #f0eaf9;--mist-pink: #e8e2f0;--sea-salt: #e3dcee;--caramel: #7f7590}html[data-palette=violet].dark{--bg: #1c1a21;--bg-raised: #26222f;--ink: #e8e5ee;--ink-2: #a49eaf;--ink-3: #746e80;--rule: #373241;--accent: #b49ae0;--accent-hover: #c3ace8;--accent-bg: #262033;--mist-pink: #292434;--sea-salt: #2b2536;--caramel: #948caa}html[data-palette=sea]{--bg: #f3f8f7;--bg-raised: #e8f0ee;--ink: #21302d;--ink-2: #647872;--ink-3: #9fb1ac;--rule: #d9e6e3;--accent: #3d9c8f;--accent-hover: #2f8578;--accent-bg: #e5f3f0;--mist-pink: #dee9e6;--sea-salt: #d6e5e1;--caramel: #6c8680}html[data-palette=sea].dark{--bg: #181f1e;--bg-raised: #212a28;--ink: #e3eae8;--ink-2: #9aaba6;--ink-3: #6d7d79;--rule: #2f3b39;--accent: #6cc3b4;--accent-hover: #82d0c2;--accent-bg: #1d2a27;--mist-pink: #222c2a;--sea-salt: #242e2c;--caramel: #869894}html[data-palette=amber]{--bg: #faf7ef;--bg-raised: #f3eddc;--ink: #322b1e;--ink-2: #7d7360;--ink-3: #b5ab94;--rule: #eae1cb;--accent: #b97f1c;--accent-hover: #9f6a0e;--accent-bg: #f9efd8;--mist-pink: #f0e8d2;--sea-salt: #ebe2ca;--caramel: #96835c}html[data-palette=amber].dark{--bg: #201d16;--bg-raised: #2a261d;--ink: #ebe6d9;--ink-2: #b0a893;--ink-3: #7f7866;--rule: #3c3729;--accent: #e0a83e;--accent-hover: #ebba5c;--accent-bg: #2c251a;--mist-pink: #2f2a1e;--sea-salt: #312c20;--caramel: #a89a76}html[data-palette=plum]{--bg: #f9f4f5;--bg-raised: #f1e8ea;--ink: #2f2427;--ink-2: #796a6f;--ink-3: #b1a2a7;--rule: #e9dde0;--accent: #a4506b;--accent-hover: #8d3f59;--accent-bg: #f7e9ee;--mist-pink: #ecdee2;--sea-salt: #e5d8db;--caramel: #8d6f77}html[data-palette=plum].dark{--bg: #1f1a1b;--bg-raised: #292223;--ink: #eae2e4;--ink-2: #ada0a4;--ink-3: #7c6e73;--rule: #3a3133;--accent: #d98aa3;--accent-hover: #e39fb5;--accent-bg: #2b2023;--mist-pink: #2d2527;--sea-salt: #2f2729;--caramel: #a08890}html[data-palette=graphite]{--bg: #f7f7f6;--bg-raised: #eeedeb;--ink: #262626;--ink-2: #6e6e6c;--ink-3: #ababa8;--rule: #e0e0de;--accent: #3f3f3e;--accent-hover: #262626;--accentLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
.hero[data-astro-cid-nlow4r3u]{position:fixed;inset:0;z-index:-1;display:flex;align-items:center;justify-content:center;overflow:hidden;background:linear-gradient(160deg,var(--bg) 0%,color-mix(in srgb,var(--bg) 85%,var(--accent) 15%) 40%,color-mix(in srgb,var(--bg) 90%,var(--mist-pink) 10%) 70%,var(--bg) 100%);width:100%}.hero__bg[data-astro-cid-nlow4r3u]{position:absolute;inset:0;pointer-events:none}.hero__blur-overlay[data-astro-cid-nlow4r3u]{position:absolute;inset:0;z-index:1;background:color-mix(in srgb,var(--bg) 26%,transparent);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);opacity:0;will-change:opacity;pointer-events:none}.hero[data-astro-cid-nlow4r3u].wi-hero--offscreen{visibility:hidden}.hero[data-astro-cid-nlow4r3u].wi-hero--offscreen .hero__orb[data-astro-cid-nlow4r3u],.hero[data-astro-cid-nlow4r3u].wi-hero--offscreen .hero__scroll-indicator[data-astro-cid-nlow4r3u]{animation-play-state:paused}.hero__orb[data-astro-cid-nlow4r3u]{position:absolute;border-radius:50%;filter:blur(80px);animation:breathe 5s ease-in-out infinite}.hero__orb--1[data-astro-cid-nlow4r3u]{width:420px;height:420px;top:-10%;right:-5%;background:radial-gradient(circle,rgba(212,118,78,.4) 0%,transparent 70%);animation-delay:0s;animation-duration:5s}.hero__orb--2[data-astro-cid-nlow4r3u]{width:350px;height:350px;bottom:-8%;left:-5%;background:radial-gradient(circle,rgba(220,140,160,.3) 0%,transparent 70%);animation-delay:-1.8s;animation-duration:6s}.hero__orb--3[data-astro-cid-nlow4r3u]{width:300px;height:300px;top:40%;left:50%;transform:translate(-50%);background:radial-gradient(circle,rgba(180,150,200,.2) 0%,transparent 70%);animation-delay:-3.2s;animation-duration:4.5s}.hero__bg-image[data-astro-cid-nlow4r3u]{position:absolute;inset:0;background-size:cover;background-position:center;opacity:1}.hero__bg-image--dark[data-astro-cid-nlow4r3u],html.dark .hero__bg-image--light[data-astro-cid-nlow4r3u]{display:none}html.dark .hero__bg-image--dark[data-astro-cid-nlow4r3u]{display:block}.hero__content[data-astro-cid-nlow4r3u]{position:relative;z-index:2;display:flex;flex-direction:column;align-items:center;text-align:center;padding:var(--space-xl);gap:var(--space-lg);transition:opacity .4s ease,transform .4s ease}.hero__title[data-astro-cid-nlow4r3u]{font-family:var(--font-sans);font-size:clamp(3rem,8vw,6rem);font-weight:700;letter-spacing:-.03em;color:var(--hero-title-text, var(--ink));line-height:var(--leading-tight)}.hero__subtitle[data-astro-cid-nlow4r3u]{font-family:var(--font-body);font-size:var(--text-xl);color:var(--hero-subtitle-text, var(--ink-2));letter-spacing:.05em;line-height:var(--leading-normal)}.hero__description[data-astro-cid-nlow4r3u]{font-size:var(--text-lg);color:var(--hero-quote-text, var(--ink-2));line-height:var(--leading-relaxed);max-width:560px;text-align:center;transition:opacity .3s ease}.hero--fx-shadow[data-astro-cid-nlow4r3u] .hero__title[data-astro-cid-nlow4r3u],.hero--fx-shadow[data-astro-cid-nlow4r3u] .hero__subtitle[data-astro-cid-nlow4r3u],.hero--fx-shadow[data-astro-cid-nlow4r3u] .hero__description[data-astro-cid-nlow4r3u]{text-shadow:0 1px 2px rgba(0,0,0,.22),0 2px 10px rgba(0,0,0,.16)}html.dark .hero--fx-shadow[data-astro-cid-nlow4r3u] .hero__title[data-astro-cid-nlow4r3u],html.dark .hero--fx-shadow[data-astro-cid-nlow4r3u] .hero__subtitle[data-astro-cid-nlow4r3u],html.dark .hero--fx-shadow[data-astro-cid-nlow4r3u] .hero__description[data-astro-cid-nlow4r3u]{text-shadow:0 1px 3px rgba(0,0,0,.6),0 2px 12px rgba(0,0,0,.4)}.hero--fx-glow[data-astro-cid-nlow4r3u] .hero__title[data-astro-cid-nlow4r3u],.hero--fx-glow[data-astro-cid-nlow4r3u] .hero__subtitle[data-astro-cid-nlow4r3u],.hero--fx-glow[data-astro-cid-nlow4r3u] .hero__description[data-astro-cid-nlow4r3u]{text-shadow:0 0 10px rgba(255,255,255,.85),0 0 26px rgba(255,255,255,.5)}html.dark .hero--fx-glow[data-astro-cid-nlow4r3u] .hero__title[data-astro-cid-nlow4r3u],html.dark .hero--fx-glow[data-astro-cid-nlow4r3u] .hero__subtitle[data-astro-cid-nlow4r3u],html.dark .hero--fx-glow[data-astro-cid-nlow4r3u] .hero__description[data-astro-cid-nlow4r3u]{text-shadow:0 0 8px rgba(255,255,255,.45),0 0 24px rgba(255,255,255,.22)}.hero--fx-scrim[data-astro-cid-nlow4r3u] .hero__content[data-astro-cid-nlow4r3u]{background:#ffffff73;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border-radius:24px;padding:var(--space-2xl)}html.dark .hero--fx-scrim[data-astro-cid-nlow4r3u] .hero__content[data-astro-cid-nlow4r3u]{background:#0006}.hero--fx-gradient[data-astro-cid-nlow4r3u] .hero__content[data-astro-cid-nlow4r3u]{background:linear-gradient(135deg,#ffffff8c,#ffffff2e);border-radius:24px;padding:var(--space-2xl)}html.dark .hero--fx-gradient[data-astro-cid-nlow4r3u] .hero__content[data-astro-cid-nlow4r3u]{background:linear-gradient(135deg,#00000080,#00000029)}.hero--fx-frame[data-astro-cid-nlow4r3u] .hero__content[data-astro-cid-nlow4r3u]{border:1px solid color-mix(in srgb,var(--ink) 22%,transparent);background:color-mix(in srgb,var(Line truncated
|
||||
@@ -80,7 +80,7 @@ export default defineConfig({
|
||||
],
|
||||
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/nxxy335top/halo-theme-WarmIsland' }
|
||||
{ icon: 'github', link: 'https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland', ariaLabel: '源码仓库' }
|
||||
],
|
||||
|
||||
search: {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# 更新日志
|
||||
|
||||
## v1.2.5
|
||||
|
||||
### 兼容性与元数据
|
||||
|
||||
- ⬆️ Halo 版本要求提升至 **2.27.0+**(页面布局契约、微信读书页面依赖 2.27 的能力)
|
||||
- 🔗 源码仓库与问题反馈地址统一为 `https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland`
|
||||
- 📚 完善安装、开发、FAQ 文档,修正过时的技术栈与构建打包说明
|
||||
|
||||
### 性能优化
|
||||
|
||||
- 🚀 **首页滚动顺滑性**:移除逐帧改写全屏 `backdrop-filter` 模糊半径的逻辑。
|
||||
旧实现让 Chromium 每个滚动帧都重做一次全屏背景采样 + 高斯模糊,是首页掉帧主因;
|
||||
现改为固定模糊半径 + 只调整 `opacity`(合成层操作),并缓存进度值避免重复写 DOM
|
||||
- 🚀 **首屏离屏即停**:滚过首屏后 Hero 已被不透明内容区完全盖住,此时隐藏 Hero
|
||||
并暂停光球动画,长页面后半段不再有额外绘制
|
||||
- 🚀 **首屏整屏吸附回归(顺滑版)**:v1.2.5 初版改用 CSS `scroll-snap: y proximity` 后吸附
|
||||
实际失效——内容区顶部是唯一吸附点、页面顶部 `0` 没有吸附点,`proximity` 几乎不触发;
|
||||
换成 `mandatory` 又会把长内容反复拉回内容区顶部。现改为**边界吸附**:只在
|
||||
`0 ↔ 内容区顶部` 接管一次滚轮 / 触摸,进入内容区后完全交给原生滚动惯性;解锁用
|
||||
`scrollend` + 600ms 兜底(原为固定 800ms 输入锁),已在目标位置时不再接管
|
||||
- 🚀 **导航栏滚动状态去重**:只在状态真正变化时写 class,不再每帧触发样式重算
|
||||
- ♿ 补充 `prefers-reduced-motion` 支持:减少动态效果时关闭平滑滚动与光球 / 指示箭头动画,
|
||||
吸附改为即时定位(不再关闭吸附)
|
||||
- 🖼️ 首页文章列表继续使用 `content-visibility: auto` 跳过视口外渲染,图片保持懒加载
|
||||
|
||||
## v1.2.4
|
||||
|
||||
### 问题修复
|
||||
|
||||
@@ -80,7 +80,7 @@ ICP 备案号通常需要链接到工信部的备案查询页面。主题会自
|
||||
footer_socials:
|
||||
- platform: GitHub
|
||||
icon: fa-brands fa-github
|
||||
url: https://github.com/your-username
|
||||
url: https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland
|
||||
- platform: 微博
|
||||
icon: fa-brands fa-weibo
|
||||
url: https://weibo.com/your-username
|
||||
|
||||
@@ -27,8 +27,8 @@ git --version # git version 2.x.x
|
||||
## 克隆与安装
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/WarmIsland.git
|
||||
cd WarmIsland
|
||||
git clone https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland.git
|
||||
cd halo-theme-WarmIsland
|
||||
pnpm install
|
||||
```
|
||||
|
||||
@@ -66,6 +66,36 @@ ZIP 根目录直接放 `theme.yaml` / `settings.yaml` / `templates/`,不要额
|
||||
> `Template index was not found.` 并让首页 404。现在打包脚本会先校验
|
||||
> `index.html` 等模板、资源目录与版本号,不满足即报错退出。
|
||||
|
||||
## 性能与滚动顺滑
|
||||
|
||||
首页滚动曾经卡顿,根因不是 DOM 数量,而是「每帧高成本绘制 + 劫持滚动输入」:
|
||||
|
||||
| 问题 | 原因 | 现在的做法 |
|
||||
|------|------|------------|
|
||||
| 全屏模糊逐帧重算 | 滚动回调每帧改写 `backdrop-filter: blur(Npx)`,Chromium 每帧重做一次全屏背景采样 + 高斯模糊 | 模糊半径固定,滚动只改整层 `opacity`(合成层操作) |
|
||||
| 吸附吞掉滚动惯性 | 旧实现在 `wheel` / `touchend` 上 `preventDefault()` 并固定锁 800ms | 保留整屏吸附,但只在 `0 ↔ 内容区顶部` 边界接管一次;进入内容区后完全交给原生滚动。解锁用 `scrollend` + 600ms 兜底,已在目标位置时不再接管 |
|
||||
| Hero 一直绘制 | 固定全屏 Hero 的光球动画与模糊层在滚过首屏后仍在跑 | 内容区盖满视口后加 `wi-hero--offscreen` 隐藏并暂停动画 |
|
||||
| 状态反复写入 | 导航栏每个滚动帧都 `classList.add/remove` | 缓存状态,只在变化时写 |
|
||||
|
||||
**为什么吸附不用 CSS `scroll-snap`**:v1.2.5 初版试过 `html { scroll-snap-type: y proximity }`
|
||||
加内容区 `scroll-snap-align: start`,实际几乎不吸附——只有内容区顶部一个吸附点,页面顶部
|
||||
`0` 没有吸附点,`proximity` 只在滚动结束位置接近吸附点时才生效;换成 `mandatory` 则会在
|
||||
长内容的每次滚动后把页面拉回内容区顶部,正文无法阅读。`prefers-reduced-motion` 下 CSS 方案
|
||||
还会直接关闭吸附。因此改为脚本的边界吸附,CSS 侧不再设置 `scroll-snap`,避免与 `scrollTo`
|
||||
平滑动画抢夺滚动位置。
|
||||
|
||||
涉及文件:`src/pages/index.astro`(滚动渐隐 + 边界吸附脚本)、`src/components/HeroSection.astro`(模糊层与离屏规则)、
|
||||
`src/components/Navbar.astro`(状态去重)、`src/styles/main.scss`(平滑滚动与 reduced-motion)。
|
||||
|
||||
**可调项**:后台「总体样式 → 启用动效」可一键关闭滚动渐入,「Hero 首屏」可关闭整个 Hero;
|
||||
系统开启「减少动态效果」(`prefers-reduced-motion: reduce`)时,平滑滚动与光球 / 箭头动画停用,
|
||||
吸附改为即时定位(仍然可用)。长列表使用 `content-visibility: auto` 跳过视口外渲染,
|
||||
图片统一 `loading="lazy" decoding="async"`。
|
||||
|
||||
> 改动滚动逻辑后建议用浏览器 Performance 面板录制一次首页滚动:滚动帧里不应出现大面积
|
||||
> `Paint` / `Update Layer Tree`;也不要在 `scroll` 回调里读取 `getBoundingClientRect()` 之类
|
||||
> 会强制同步布局的属性(当前实现只在初始化 / resize 时测量内容区位置)。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
@@ -130,26 +160,26 @@ WarmIsland/
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| [Astro](https://astro.build) | 5.x | 静态站点生成,组件开发 |
|
||||
| [vite-plugin-halo-theme](https://github.com/halo-sigs/vite-plugin-halo-theme) | - | Halo 主题开发集成 |
|
||||
| [Thymeleaf](https://www.thymeleaf.org/) | - | Halo 服务端模板引擎 |
|
||||
| [Halo](https://www.halo.run/) | 2.27+ | 博客运行时,提供 Finder API 与主题渲染 |
|
||||
| [Astro](https://astro.build) | 6.x | 组件开发,构建输出静态 `.html` 模板 |
|
||||
| [Thymeleaf](https://www.thymeleaf.org/) | - | Halo 服务端模板引擎(源码里直接写 `th:*`) |
|
||||
|
||||
### 前端技术
|
||||
|
||||
| 技术 | 用途 |
|
||||
|------|------|
|
||||
| [Vue 3](https://vuejs.org/) | 交互组件(目录、点赞、无限滚动等) |
|
||||
| [SCSS](https://sass-lang.com/) | 样式预处理 |
|
||||
| [Prism.js](https://prismjs.com/) | 代码高亮 |
|
||||
| [LightGallery](https://www.lightgalleryjs.com/) | 图片灯箱 |
|
||||
| [SCSS](https://sass-lang.com/) | 样式预处理,CSS 变量驱动的配色系统 |
|
||||
| 原生 JavaScript | 滚动、菜单、主题切换、无限滚动等交互,无前端框架运行时 |
|
||||
| [LightGallery](https://www.lightgalleryjs.com/) | 图片灯箱(可选) |
|
||||
| Halo 插件生态 | 评论、代码高亮、服务端字数统计等由 Halo 与可选插件提供 |
|
||||
|
||||
### 构建工具
|
||||
|
||||
| 技术 | 用途 |
|
||||
|------|------|
|
||||
| [Vite](https://vitejs.dev/) | 构建打包 |
|
||||
| [Vite](https://vitejs.dev/)(Astro 内置) | 资源打包与内容哈希 |
|
||||
| [pnpm](https://pnpm.io/) | 包管理 |
|
||||
| [TypeScript](https://www.typescriptlang.org/) | 类型安全 |
|
||||
| [TypeScript](https://www.typescriptlang.org/) | 类型定义(`env.d.ts` / `pages.ts`) |
|
||||
|
||||
## 模板语法
|
||||
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ ZIP 里缺少 `templates/index.html` 时,渲染首页找不到 `index` 模板
|
||||
|
||||
检查以下几点:
|
||||
|
||||
1. 确认 Halo 版本 >= 2.26.0
|
||||
1. 确认 Halo 版本 >= 2.27.0
|
||||
2. 确认主题已启用
|
||||
3. 清除浏览器缓存后重试(Ctrl+Shift+R 强制刷新)
|
||||
4. 检查 Halo 后台日志是否有报错
|
||||
@@ -170,7 +170,7 @@ v1.2.1 起安装包会保留各版本的哈希资源(v1.1.7 之后全部覆盖
|
||||
|
||||
### 如何报告 Bug?
|
||||
|
||||
请在 [GitHub Issues](https://github.com/sunny-335/halo-theme-WarmIsland/issues) 中提交,包含以下信息:
|
||||
请在 [源码仓库 Issues](https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland/issues) 中提交,包含以下信息:
|
||||
|
||||
- Halo 版本
|
||||
- 主题版本
|
||||
|
||||
@@ -27,7 +27,7 @@ WarmIsland(暖屿)是一款为 [Halo](https://halo.run) 打造的博客主
|
||||
|
||||
### 方式二:本地上传安装
|
||||
|
||||
1. 从 [GitHub Releases](https://github.com/nxxy335top/halo-theme-WarmIsland/releases) 下载最新版本的 `.zip` 安装包
|
||||
1. 从 [源码仓库](https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland) 的 Releases / 发行版页面下载最新版本的 `.zip` 安装包
|
||||
2. 登录 Halo 后台管理控制台
|
||||
3. 进入 **主题** → **安装** → 选择 **本地上传**
|
||||
4. 上传下载的 `.zip` 文件
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ hero:
|
||||
text: 配置项
|
||||
link: /config/basic
|
||||
- theme: alt
|
||||
text: GitHub
|
||||
link: https://github.com/nxxy335top/halo-theme-WarmIsland
|
||||
text: 源码仓库
|
||||
link: https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland
|
||||
|
||||
features:
|
||||
- title: 🎨 精致视觉
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.5",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
|
||||
@@ -229,12 +229,28 @@
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
backdrop-filter: blur(0px);
|
||||
-webkit-backdrop-filter: blur(0px);
|
||||
will-change: backdrop-filter;
|
||||
/* 模糊半径固定,滚动时只改这层的 opacity(合成层操作)。
|
||||
逐帧改写 backdrop-filter 的 px 值会让浏览器每帧重做全屏背景采样,
|
||||
是首页滚动掉帧的主因;固定值 + opacity 可复用已合成的结果。 */
|
||||
background: color-mix(in srgb, var(--bg) 26%, transparent);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 滚过首屏后 Hero 已被不透明内容区完全盖住,隐藏它可停掉
|
||||
光球动画与模糊层的持续绘制(由首页滚动脚本切换 wi-hero--offscreen) */
|
||||
.hero.wi-hero--offscreen {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.hero.wi-hero--offscreen .hero__orb,
|
||||
.hero.wi-hero--offscreen .hero__scroll-indicator {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.hero__orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
@@ -454,6 +470,17 @@
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero__orb,
|
||||
.hero__scroll-indicator {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.hero__content {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
html.dark .hero {
|
||||
background: linear-gradient(
|
||||
160deg,
|
||||
|
||||
+24
-19
@@ -97,29 +97,34 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
||||
const navbar = document.getElementById("wi-navbar");
|
||||
let lastScrollY = window.scrollY;
|
||||
let ticking = false;
|
||||
/* 缓存状态:只在状态真正变化时才写 class,避免每个滚动帧都触发样式重算 */
|
||||
let scrolled = null;
|
||||
let hidden = null;
|
||||
|
||||
function onScroll() {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(() => {
|
||||
const currentScrollY = window.scrollY;
|
||||
|
||||
if (currentScrollY > 20) {
|
||||
navbar?.classList.add("wi-navbar--scrolled");
|
||||
} else {
|
||||
navbar?.classList.remove("wi-navbar--scrolled");
|
||||
}
|
||||
|
||||
if (currentScrollY > lastScrollY && currentScrollY > 80) {
|
||||
navbar?.classList.add("wi-navbar--hidden");
|
||||
} else if (currentScrollY < lastScrollY) {
|
||||
navbar?.classList.remove("wi-navbar--hidden");
|
||||
}
|
||||
|
||||
lastScrollY = currentScrollY;
|
||||
ticking = false;
|
||||
});
|
||||
if (ticking) return;
|
||||
ticking = true;
|
||||
requestAnimationFrame(() => {
|
||||
ticking = false;
|
||||
const currentScrollY = window.scrollY;
|
||||
const nextScrolled = currentScrollY > 20;
|
||||
const nextHidden =
|
||||
currentScrollY > lastScrollY && currentScrollY > 80
|
||||
? true
|
||||
: currentScrollY < lastScrollY
|
||||
? false
|
||||
: hidden;
|
||||
|
||||
if (nextScrolled !== scrolled) {
|
||||
scrolled = nextScrolled;
|
||||
navbar?.classList.toggle("wi-navbar--scrolled", scrolled);
|
||||
}
|
||||
if (nextHidden !== hidden) {
|
||||
hidden = nextHidden;
|
||||
navbar?.classList.toggle("wi-navbar--hidden", !!hidden);
|
||||
}
|
||||
lastScrollY = currentScrollY;
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
|
||||
@@ -21,7 +21,6 @@ const fragmentAttr = layoutFragment ? "html (head, content)" : undefined;
|
||||
<!doctype html>
|
||||
<html
|
||||
lang="zh-CN"
|
||||
class={home ? 'wi-home-snap' : ''}
|
||||
style="overflow-x:hidden"
|
||||
th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'"
|
||||
th:attr="data-palette=${theme.config?.style?.color_palette ?: 'warm-island'}"
|
||||
|
||||
+115
-69
@@ -277,106 +277,152 @@ import WelcomePopup from "../components/WelcomePopup.astro";
|
||||
|
||||
if (!hero || !blurOverlay) return;
|
||||
|
||||
var maxBlur = 20;
|
||||
var triggerDistance = 300;
|
||||
/* 首屏滚动渐隐只写 opacity / transform 这类合成属性。
|
||||
旧实现每帧改写全屏 backdrop-filter 的模糊半径,Chromium 每帧都要
|
||||
重做一次全屏背景采样 + 高斯模糊,是首页滚动掉帧的主因。 */
|
||||
var fadeStart = 200;
|
||||
var fadeEnd = 500;
|
||||
var ticking = false;
|
||||
var lastProgress = -1;
|
||||
var lastHidden = null;
|
||||
var content = document.querySelector(".wi-home-content--with-hero");
|
||||
var contentTop = window.innerHeight;
|
||||
var reduceMotion =
|
||||
window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
/* 内容区顶部位置只在初始化 / resize 时测量,避免滚动帧里读布局 */
|
||||
function measure() {
|
||||
if (content) {
|
||||
contentTop = content.getBoundingClientRect().top + (window.scrollY || window.pageYOffset);
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
ticking = false;
|
||||
var scrollY = window.scrollY || window.pageYOffset;
|
||||
|
||||
/* 滚过首屏后 Hero 已被不透明内容区完全盖住,直接隐藏:
|
||||
光球动画、模糊层都不再绘制,长页面后半段零额外开销。 */
|
||||
var hidden = scrollY >= contentTop - 1;
|
||||
if (hidden !== lastHidden) {
|
||||
lastHidden = hidden;
|
||||
hero.classList.toggle("wi-hero--offscreen", hidden);
|
||||
}
|
||||
if (hidden) return;
|
||||
|
||||
var progress = Math.max(0, Math.min(1, (scrollY - fadeStart) / (fadeEnd - fadeStart)));
|
||||
if (progress === lastProgress) return;
|
||||
lastProgress = progress;
|
||||
|
||||
/* 模糊半径固定,只调整整层不透明度:GPU 合成,无需重新采样背景 */
|
||||
blurOverlay.style.opacity = String(Math.min(1, scrollY / 300));
|
||||
if (heroContent) {
|
||||
heroContent.style.opacity = String(1 - progress);
|
||||
if (!reduceMotion) {
|
||||
heroContent.style.transform = "translateY(" + (-20 * progress) + "px)";
|
||||
}
|
||||
}
|
||||
if (scrollIndicator) {
|
||||
scrollIndicator.style.opacity = String(1 - progress);
|
||||
}
|
||||
}
|
||||
|
||||
function onScroll() {
|
||||
if (ticking) return;
|
||||
ticking = true;
|
||||
requestAnimationFrame(function () {
|
||||
ticking = false;
|
||||
var scrollY = window.scrollY || window.pageYOffset;
|
||||
|
||||
var currentBlur = Math.min(scrollY / triggerDistance * maxBlur, maxBlur);
|
||||
blurOverlay.style.backdropFilter = "blur(" + currentBlur + "px)";
|
||||
blurOverlay.style.webkitBackdropFilter = "blur(" + currentBlur + "px)";
|
||||
|
||||
if (heroContent) {
|
||||
var progress = Math.max(0, Math.min(1, (scrollY - fadeStart) / (fadeEnd - fadeStart)));
|
||||
heroContent.style.opacity = String(1 - progress);
|
||||
heroContent.style.transform = "translateY(" + (-20 * progress) + "px)";
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
if (scrollIndicator) {
|
||||
var indicatorProgress = Math.max(0, Math.min(1, (scrollY - fadeStart) / (fadeEnd - fadeStart)));
|
||||
scrollIndicator.style.opacity = String(1 - indicatorProgress);
|
||||
}
|
||||
});
|
||||
function onResize() {
|
||||
measure();
|
||||
onScroll();
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
onScroll();
|
||||
})();
|
||||
</script>
|
||||
window.addEventListener("resize", onResize, { passive: true });
|
||||
measure();
|
||||
update();
|
||||
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var hero = document.getElementById("wi-hero");
|
||||
var content = document.querySelector(".wi-home-content--with-hero");
|
||||
if (!hero || !content) return;
|
||||
/* ---- 首屏整屏吸附:只接管 0 ↔ 内容区顶部这一个边界 ----
|
||||
纯 CSS scroll-snap 表达不了这个行为:内容区顶部是唯一吸附点,页面
|
||||
顶部 0 没有吸附点,proximity 几乎不会触发;改成 mandatory 又会在
|
||||
长内容的每一次滚动后把页面拉回内容区顶部,导致正文无法阅读。
|
||||
因此只在首屏边界接管一次滚轮 / 触摸,进入内容区后完全交给原生滚动。
|
||||
掉帧主因(逐帧改写 backdrop-filter)已在上面的 update() 中修复,
|
||||
这里只做一次 scrollTo,不参与滚动帧绘制。 */
|
||||
var snapping = false;
|
||||
var snapTimer = null;
|
||||
var SNAP_EDGE = 8;
|
||||
|
||||
var isSnapping = false;
|
||||
var heroHeight = window.innerHeight;
|
||||
function endSnap() {
|
||||
snapping = false;
|
||||
if (snapTimer) {
|
||||
clearTimeout(snapTimer);
|
||||
snapTimer = null;
|
||||
}
|
||||
window.removeEventListener("scrollend", endSnap);
|
||||
}
|
||||
|
||||
window.addEventListener("resize", function () {
|
||||
heroHeight = window.innerHeight;
|
||||
}, { passive: true });
|
||||
|
||||
function snapTo(targetY) {
|
||||
if (isSnapping) return;
|
||||
isSnapping = true;
|
||||
window.scrollTo({ top: targetY, behavior: "smooth" });
|
||||
var timer = setTimeout(function () {
|
||||
isSnapping = false;
|
||||
}, 800);
|
||||
function snapTo(top) {
|
||||
if (snapping) return;
|
||||
snapping = true;
|
||||
window.scrollTo({ top: top, behavior: reduceMotion ? "auto" : "smooth" });
|
||||
/* scrollend 精确解锁;短定时器兜底,避免不支持 scrollend 时锁死输入 */
|
||||
if ("onscrollend" in window) {
|
||||
window.addEventListener(
|
||||
"scrollend",
|
||||
function () {
|
||||
clearTimeout(timer);
|
||||
isSnapping = false;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
window.addEventListener("scrollend", endSnap, { once: true });
|
||||
}
|
||||
snapTimer = setTimeout(endSnap, reduceMotion ? 100 : 600);
|
||||
}
|
||||
|
||||
window.addEventListener("wheel", function (e) {
|
||||
if (isSnapping) {
|
||||
window.addEventListener(
|
||||
"wheel",
|
||||
function (e) {
|
||||
if (!content || e.ctrlKey || e.defaultPrevented) return;
|
||||
var y = window.scrollY || window.pageYOffset;
|
||||
/* 内容区内部不再干预,保留原生惯性与长文阅读体验 */
|
||||
if (y > contentTop + SNAP_EDGE) return;
|
||||
if (snapping) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
var y = window.scrollY;
|
||||
if (y < heroHeight && e.deltaY > 0) {
|
||||
if (Math.abs(e.deltaY) < 2) return;
|
||||
var target = e.deltaY > 0 ? contentTop : 0;
|
||||
if (Math.abs(target - y) < 2) return;
|
||||
e.preventDefault();
|
||||
snapTo(heroHeight);
|
||||
} else if (y > 0 && y <= heroHeight && e.deltaY < 0) {
|
||||
e.preventDefault();
|
||||
snapTo(0);
|
||||
}
|
||||
}, { passive: false });
|
||||
snapTo(target);
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
|
||||
var touchStartY = 0;
|
||||
window.addEventListener("touchstart", function (e) {
|
||||
window.addEventListener(
|
||||
"touchstart",
|
||||
function (e) {
|
||||
touchStartY = e.touches[0].clientY;
|
||||
}, { passive: true });
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
window.addEventListener("touchend", function (e) {
|
||||
if (isSnapping) return;
|
||||
window.addEventListener(
|
||||
"touchend",
|
||||
function (e) {
|
||||
if (!content || snapping) return;
|
||||
var y = window.scrollY || window.pageYOffset;
|
||||
if (y > contentTop + SNAP_EDGE) return;
|
||||
var diff = touchStartY - e.changedTouches[0].clientY;
|
||||
var y = window.scrollY;
|
||||
if (y < heroHeight && diff > 30) {
|
||||
snapTo(heroHeight);
|
||||
} else if (y > 0 && y <= heroHeight && diff < -30) {
|
||||
snapTo(0);
|
||||
}
|
||||
}, { passive: true });
|
||||
if (Math.abs(diff) < 30) return;
|
||||
var target = diff > 0 ? contentTop : 0;
|
||||
if (Math.abs(target - y) < 2) return;
|
||||
snapTo(target);
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- 首屏整屏吸附由上面的脚本在 0 ↔ 内容区顶部边界接管;
|
||||
CSS 侧不再设置 scroll-snap,避免与 scrollTo 平滑动画互相抢夺滚动位置。 -->
|
||||
|
||||
<style>
|
||||
.wi-home-content {
|
||||
position: relative;
|
||||
|
||||
@@ -3,7 +3,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<!--
|
||||
页面布局契约(Halo 2.26+)
|
||||
页面布局契约(Halo 2.27+)
|
||||
供插件前台页面通过 ~{layout :: html(head = ..., content = ...)} 复用主题外观,
|
||||
避免这些页面回退到 Halo 内置的 fallback 布局导致风格不一致。
|
||||
|
||||
|
||||
@@ -27,6 +27,15 @@ html {
|
||||
hanging-punctuation: first last;
|
||||
}
|
||||
|
||||
/* 首页整屏吸附由 src/pages/index.astro 的边界吸附脚本处理:CSS scroll-snap
|
||||
无法只吸附 0 ↔ 内容区顶部这一个边界(见脚本注释)。这里只保留全局平滑滚动。 */
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
+4
-4
@@ -10,8 +10,8 @@ spec:
|
||||
description: 暖如灯火,静如留白。暖屿以柔和的色调和舒缓的节奏回归内容本身,为记录与阅读留出一方安静从容的角落。
|
||||
logo: /themes/warm-island/assets/logo.png
|
||||
homepage: https://nxxy335.top/WarmIsland/
|
||||
repo: https://github.com/sunny-335/halo-theme-WarmIsland
|
||||
issues: https://github.com/sunny-335/halo-theme-WarmIsland/issues
|
||||
repo: https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland
|
||||
issues: https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland/issues
|
||||
settingName: "warm-island-setting"
|
||||
configMapName: "warm-island-configMap"
|
||||
customTemplates:
|
||||
@@ -28,8 +28,8 @@ spec:
|
||||
description: 微信读书书架(依赖微信读书插件)
|
||||
screenshot:
|
||||
file: weread.html
|
||||
version: "1.2.4"
|
||||
requires: ">=2.26.0"
|
||||
version: "1.2.5"
|
||||
requires: ">=2.27.0"
|
||||
license:
|
||||
- name: "GPL-3.0"
|
||||
url: "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||
Reference in new issue
Block a user