From de5564b19f6364a36e5de46476409b408125e17b Mon Sep 17 00:00:00 2001 From: rohow Date: Fri, 30 Jan 2026 18:17:12 +0800 Subject: [PATCH] feat(dividing): sticky --- src/alpine/header-dividing.ts | 35 +++++++++++++++++++ src/alpine/{menu.ts => header-menu.ts} | 2 +- src/main.ts | 6 ++-- src/styles/header.scss | 48 +++++++++++++++++++++++--- templates/modules/header.html | 6 ++-- 5 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/alpine/header-dividing.ts rename src/alpine/{menu.ts => header-menu.ts} (76%) diff --git a/src/alpine/header-dividing.ts b/src/alpine/header-dividing.ts new file mode 100644 index 0000000..a164afa --- /dev/null +++ b/src/alpine/header-dividing.ts @@ -0,0 +1,35 @@ +export const headerDividing = () => ({ + isSticky: false, + $el: null as HTMLElement | null, + _observer: null as IntersectionObserver | null, + + init() { + const centerEl = this.$el as HTMLElement; + if (!centerEl) return; + + const header = centerEl.closest('.header') as HTMLElement; + + if (!header) return; + + // 直接观察 header 元素本身 + this._observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + this.isSticky = entry.boundingClientRect.top < 0 && !entry.isIntersecting; + }, + { + threshold: 0, // 只要有任何部分离开就触发 + rootMargin: '0px' // 无偏移 + } + ); + + this._observer.observe(header); + }, + + destroy() { + if (this._observer) { + this._observer.disconnect(); + this._observer = null; + } + } +}); diff --git a/src/alpine/menu.ts b/src/alpine/header-menu.ts similarity index 76% rename from src/alpine/menu.ts rename to src/alpine/header-menu.ts index c6167df..6b79901 100644 --- a/src/alpine/menu.ts +++ b/src/alpine/header-menu.ts @@ -3,7 +3,7 @@ interface MenuState { handleToggleMenu(): void; } -export const menu = (): MenuState => ({ +export const headerMenu = (): MenuState => ({ isOpen: false, handleToggleMenu() { diff --git a/src/main.ts b/src/main.ts index f71aaf3..d1d0ffe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,18 +5,20 @@ import "./styles/font-pixel.scss"; import Alpine from 'alpinejs' import {upvote} from './alpine/upvote' import {themeMode} from './alpine/theme-mode' -import {menu} from './alpine/menu' import {postLineNum} from './alpine/post-line-num' import {postToc} from './alpine/post-toc' +import {headerDividing} from './alpine/header-dividing' +import {headerMenu} from './alpine/header-menu' import {typewriterEffect} from './utils' window.Alpine = Alpine Alpine.data('upvote', upvote) Alpine.data('themeMode', themeMode) -Alpine.data('menu', menu) Alpine.data('postLineNum', postLineNum) Alpine.data('postToc', postToc) +Alpine.data('headerMenu', headerMenu) +Alpine.data('headerDividing', headerDividing) Alpine.start() diff --git a/src/styles/header.scss b/src/styles/header.scss index 52850fc..85b8168 100644 --- a/src/styles/header.scss +++ b/src/styles/header.scss @@ -11,12 +11,21 @@ justify-content: space-between; } + &__center { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + } + .dividing { flex: 1; display: block; width: 100%; height: 25px; position: relative; + --dash-spacing: 10px; + --dash-width: 2px; // 虚线背景层 - 带遮罩 &::before { @@ -29,11 +38,11 @@ background: repeating-linear-gradient( 90deg, var(--foreground), - var(--foreground) 2px, + var(--foreground) var(--dash-width), transparent 0, - transparent 10px + transparent var(--dash-spacing) ); - background-size: 10px 100%; + background-size: var(--dash-spacing) 100%; animation: line-flow 3s linear infinite; // 遮罩层实现虚线淡入淡出效果 @@ -73,6 +82,37 @@ animation: scan-line 5s ease-in-out infinite; pointer-events: none; z-index: 10; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + } + + &.sticky { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 5px; + z-index: 100; + --dash-spacing: 6px; + --dash-width: 2px; + background: var(--background); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + + &::before { + animation: line-flow 1.5s linear infinite; + mask-image: none; + -webkit-mask-image: none; + top: calc(50% - 1px); + height: 2px; + } + + &::after { + animation: scan-line 2.5s ease-in-out infinite; + opacity: 0.6; + filter: blur(3px); + // 调整扫描线高度以适应 5px 容器 + height: 200%; + width: 25%; + } } } @@ -82,7 +122,7 @@ background-position: 0 0; } 100% { - background-position: 10px 0; + background-position: var(--dash-spacing) 0; } } diff --git a/templates/modules/header.html b/templates/modules/header.html index 31c398c..5d674ec 100644 --- a/templates/modules/header.html +++ b/templates/modules/header.html @@ -5,7 +5,9 @@ Logo
-
+
+
+