feat(dividing): sticky
这个提交包含在:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@ interface MenuState {
|
|||||||
handleToggleMenu(): void;
|
handleToggleMenu(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const menu = (): MenuState => ({
|
export const headerMenu = (): MenuState => ({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
|
||||||
handleToggleMenu() {
|
handleToggleMenu() {
|
||||||
+4
-2
@@ -5,18 +5,20 @@ import "./styles/font-pixel.scss";
|
|||||||
import Alpine from 'alpinejs'
|
import Alpine from 'alpinejs'
|
||||||
import {upvote} from './alpine/upvote'
|
import {upvote} from './alpine/upvote'
|
||||||
import {themeMode} from './alpine/theme-mode'
|
import {themeMode} from './alpine/theme-mode'
|
||||||
import {menu} from './alpine/menu'
|
|
||||||
import {postLineNum} from './alpine/post-line-num'
|
import {postLineNum} from './alpine/post-line-num'
|
||||||
import {postToc} from './alpine/post-toc'
|
import {postToc} from './alpine/post-toc'
|
||||||
|
import {headerDividing} from './alpine/header-dividing'
|
||||||
|
import {headerMenu} from './alpine/header-menu'
|
||||||
import {typewriterEffect} from './utils'
|
import {typewriterEffect} from './utils'
|
||||||
|
|
||||||
window.Alpine = Alpine
|
window.Alpine = Alpine
|
||||||
|
|
||||||
Alpine.data('upvote', upvote)
|
Alpine.data('upvote', upvote)
|
||||||
Alpine.data('themeMode', themeMode)
|
Alpine.data('themeMode', themeMode)
|
||||||
Alpine.data('menu', menu)
|
|
||||||
Alpine.data('postLineNum', postLineNum)
|
Alpine.data('postLineNum', postLineNum)
|
||||||
Alpine.data('postToc', postToc)
|
Alpine.data('postToc', postToc)
|
||||||
|
Alpine.data('headerMenu', headerMenu)
|
||||||
|
Alpine.data('headerDividing', headerDividing)
|
||||||
|
|
||||||
Alpine.start()
|
Alpine.start()
|
||||||
|
|
||||||
|
|||||||
+44
-4
@@ -11,12 +11,21 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.dividing {
|
.dividing {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
--dash-spacing: 10px;
|
||||||
|
--dash-width: 2px;
|
||||||
|
|
||||||
// 虚线背景层 - 带遮罩
|
// 虚线背景层 - 带遮罩
|
||||||
&::before {
|
&::before {
|
||||||
@@ -29,11 +38,11 @@
|
|||||||
background: repeating-linear-gradient(
|
background: repeating-linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
var(--foreground),
|
var(--foreground),
|
||||||
var(--foreground) 2px,
|
var(--foreground) var(--dash-width),
|
||||||
transparent 0,
|
transparent 0,
|
||||||
transparent 10px
|
transparent var(--dash-spacing)
|
||||||
);
|
);
|
||||||
background-size: 10px 100%;
|
background-size: var(--dash-spacing) 100%;
|
||||||
animation: line-flow 3s linear infinite;
|
animation: line-flow 3s linear infinite;
|
||||||
|
|
||||||
// 遮罩层实现虚线淡入淡出效果
|
// 遮罩层实现虚线淡入淡出效果
|
||||||
@@ -73,6 +82,37 @@
|
|||||||
animation: scan-line 5s ease-in-out infinite;
|
animation: scan-line 5s ease-in-out infinite;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 10;
|
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;
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
background-position: 10px 0;
|
background-position: var(--dash-spacing) 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
<img class="icon" th:if="${not #strings.isEmpty(theme.config.basic.logo)}" th:src="${theme.config.basic.logo}" alt="Logo" />
|
<img class="icon" th:if="${not #strings.isEmpty(theme.config.basic.logo)}" th:src="${theme.config.basic.logo}" alt="Logo" />
|
||||||
<div class="text" th:if="${not #strings.isEmpty(theme.config.basic.title)}" th:text="${theme.config.basic.title}"></div>
|
<div class="text" th:if="${not #strings.isEmpty(theme.config.basic.title)}" th:text="${theme.config.basic.title}"></div>
|
||||||
</a>
|
</a>
|
||||||
<div class="dividing"></div>
|
<div class="header__center" x-data="headerDividing()">
|
||||||
|
<div class="dividing" :class="{ 'sticky': isSticky }"></div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="button"
|
class="button"
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<nav class="menu">
|
<nav class="menu">
|
||||||
<ul th:if="${menu != null} and ${not #lists.isEmpty(menu.menuItems)}" class="menu__inner">
|
<ul th:if="${menu != null} and ${not #lists.isEmpty(menu.menuItems)}" class="menu__inner">
|
||||||
<li th:each="menuItem : ${menu.menuItems}" x-data="menu()" @mouseenter="handleToggleMenu()" @mouseleave="handleToggleMenu()">
|
<li th:each="menuItem : ${menu.menuItems}" x-data="headerMenu()" @mouseenter="handleToggleMenu()" @mouseleave="handleToggleMenu()">
|
||||||
<a
|
<a
|
||||||
class="text-gray-600 hover:text-blue-600"
|
class="text-gray-600 hover:text-blue-600"
|
||||||
th:href="${menuItem.status.href}"
|
th:href="${menuItem.status.href}"
|
||||||
|
|||||||
在新议题中引用
屏蔽一个用户