返回顶部按钮迁移为 PaperMod 风格

- button "↑" → <a href="#top"> + 内联 SVG 双上箭头图标
- 滚动超过一屏才显示,添加 opacity/translate-y 过渡动画
- 平滑滚动到顶部并清除 URL hash(history.replaceState)
- 无障碍属性:aria-label、title、accesskey="g"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-26 13:20:25 +08:00
parent 61c1ec7282
commit a15394c935
3 changed files with 102 additions and 51 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="m296-224-56-56 240-240 240 240-56 56-184-183-184 183Zm0-240-56-56 240-240 240 240-56 56-184-183-184 183Z"/></svg>

After

Width:  |  Height:  |  Size: 229 B

View File

@ -21,7 +21,6 @@
--color-blue-600: oklch(54.6% 0.245 262.881);
--color-blue-700: oklch(48.8% 0.243 264.376);
--color-gray-50: oklch(98.5% 0.002 247.839);
--color-gray-100: oklch(96.7% 0.003 264.542);
--color-gray-200: oklch(92.8% 0.006 264.531);
--color-gray-300: oklch(87.2% 0.01 258.338);
--color-gray-400: oklch(70.7% 0.022 261.325);
@ -205,6 +204,12 @@
}
}
@layer utilities {
.pointer-events-none {
pointer-events: none;
}
.visible {
visibility: visible;
}
.relative {
position: relative;
}
@ -301,6 +306,14 @@
.flex-1 {
flex: 1;
}
.translate-y-0 {
--tw-translate-y: calc(var(--spacing) * 0);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-y-2 {
--tw-translate-y: calc(var(--spacing) * 2);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.cursor-pointer {
cursor: pointer;
}
@ -368,9 +381,6 @@
.bg-gray-50 {
background-color: var(--color-gray-50);
}
.bg-gray-100 {
background-color: var(--color-gray-100);
}
.bg-gray-900 {
background-color: var(--color-gray-900);
}
@ -511,6 +521,12 @@
.underline-offset-\[0\.3rem\] {
text-underline-offset: 0.3rem;
}
.opacity-0 {
opacity: 0%;
}
.opacity-100 {
opacity: 100%;
}
.shadow {
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@ -576,13 +592,6 @@
}
}
}
.hover\:bg-gray-200 {
&:hover {
@media (hover: hover) {
background-color: var(--color-gray-200);
}
}
}
.hover\:bg-red-700 {
&:hover {
@media (hover: hover) {
@ -665,11 +674,6 @@
background-color: #2e2e33;
}
}
.dark\:bg-\[\#333\] {
&:where(.dark, .dark *) {
background-color: #333;
}
}
.dark\:bg-\[\#dadadb\] {
&:where(.dark, .dark *) {
background-color: #dadadb;
@ -759,15 +763,6 @@
}
}
}
.dark\:hover\:bg-\[\#444\] {
&:where(.dark, .dark *) {
&:hover {
@media (hover: hover) {
background-color: #444;
}
}
}
}
.dark\:hover\:text-\[\#dadadb\] {
&:where(.dark, .dark *) {
&:hover {
@ -787,6 +782,21 @@
}
}
}
@property --tw-translate-x {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-translate-y {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-translate-z {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-space-y-reverse {
syntax: "*";
inherits: false;
@ -910,24 +920,12 @@
syntax: "*";
inherits: false;
}
@property --tw-translate-x {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-translate-y {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-translate-z {
syntax: "*";
inherits: false;
initial-value: 0;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-translate-z: 0;
--tw-space-y-reverse: 0;
--tw-border-style: solid;
--tw-leading: initial;
@ -956,9 +954,6 @@
--tw-backdrop-saturate: initial;
--tw-backdrop-sepia: initial;
--tw-duration: initial;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-translate-z: 0;
}
}
}

View File

@ -172,14 +172,69 @@ fn Pagination() -> Element {
#[component]
fn Footer() -> Element {
let mut visible = use_signal(|| false);
use_effect(move || {
#[cfg(target_arch = "wasm32")]
{
if let Some(window) = web_sys::window() {
let closure = wasm_bindgen::prelude::Closure::wrap(Box::new(move || {
if let Some(w) = web_sys::window() {
let threshold = w.inner_height().ok()
.and_then(|h| h.as_f64())
.unwrap_or(0.0);
let scroll_y = w.scroll_y().unwrap_or(0.0);
let new_visible = scroll_y > threshold;
visible.set(new_visible);
}
}) as Box<dyn FnMut()>);
let _ = window.add_event_listener_with_callback("scroll", wasm_bindgen::JsCast::unchecked_ref(closure.as_ref()));
let threshold = window.inner_height().ok()
.and_then(|h| h.as_f64())
.unwrap_or(0.0);
let scroll_y = window.scroll_y().unwrap_or(0.0);
visible.set(scroll_y > threshold);
closure.forget();
}
}
});
let link_class = use_memo(move || {
let base = "p-2 rounded-full cursor-pointer hover:opacity-80 transition-all duration-300 text-gray-600 dark:text-gray-300";
if visible() {
format!("{} opacity-100 translate-y-0", base)
} else {
format!("{} opacity-0 translate-y-2 pointer-events-none", base)
}
});
rsx! {
footer { class: "w-full border-t border-gray-200 dark:border-[#333] mt-auto",
div { class: "max-w-3xl mx-auto px-6 py-5 flex items-center justify-between text-sm text-gray-400 dark:text-[#9b9c9d]",
span { "© 2026 Yggdrasil Blog" }
button {
class: "p-2 rounded-full bg-gray-100 dark:bg-[#333] hover:bg-gray-200 dark:hover:bg-[#444] transition-colors",
onclick: move |_| scroll_to_top(),
""
a {
class: "{link_class}",
href: "#top",
aria_label: "go to top",
title: "Go to Top (Alt + G)",
accesskey: "g",
onclick: move |evt| {
evt.prevent_default();
scroll_to_top();
},
svg {
xmlns: "http://www.w3.org/2000/svg",
height: "24px",
view_box: "0 -960 960 960",
width: "24px",
fill: "currentColor",
path {
d: "m296-224-56-56 240-240 240 240-56 56-184-183-184 183Zm0-240-56-56 240-240 240 240-56 56-184-183-184 183Z",
}
}
}
}
}
@ -194,10 +249,10 @@ fn scroll_to_top() {
options.top(0.0);
options.behavior(web_sys::ScrollBehavior::Smooth);
let _ = window.scroll_to_with_scroll_to_options(&options);
if let Ok(history) = window.history() {
let _ = history.replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(" "));
}
}
}
#[cfg(not(target_arch = "wasm32"))]
{
let _ = ();
}
}