返回顶部按钮迁移为 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:
parent
61c1ec7282
commit
a15394c935
@ -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 |
@ -21,7 +21,6 @@
|
|||||||
--color-blue-600: oklch(54.6% 0.245 262.881);
|
--color-blue-600: oklch(54.6% 0.245 262.881);
|
||||||
--color-blue-700: oklch(48.8% 0.243 264.376);
|
--color-blue-700: oklch(48.8% 0.243 264.376);
|
||||||
--color-gray-50: oklch(98.5% 0.002 247.839);
|
--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-200: oklch(92.8% 0.006 264.531);
|
||||||
--color-gray-300: oklch(87.2% 0.01 258.338);
|
--color-gray-300: oklch(87.2% 0.01 258.338);
|
||||||
--color-gray-400: oklch(70.7% 0.022 261.325);
|
--color-gray-400: oklch(70.7% 0.022 261.325);
|
||||||
@ -205,6 +204,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
.pointer-events-none {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.visible {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
.relative {
|
.relative {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@ -301,6 +306,14 @@
|
|||||||
.flex-1 {
|
.flex-1 {
|
||||||
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 {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -368,9 +381,6 @@
|
|||||||
.bg-gray-50 {
|
.bg-gray-50 {
|
||||||
background-color: var(--color-gray-50);
|
background-color: var(--color-gray-50);
|
||||||
}
|
}
|
||||||
.bg-gray-100 {
|
|
||||||
background-color: var(--color-gray-100);
|
|
||||||
}
|
|
||||||
.bg-gray-900 {
|
.bg-gray-900 {
|
||||||
background-color: var(--color-gray-900);
|
background-color: var(--color-gray-900);
|
||||||
}
|
}
|
||||||
@ -511,6 +521,12 @@
|
|||||||
.underline-offset-\[0\.3rem\] {
|
.underline-offset-\[0\.3rem\] {
|
||||||
text-underline-offset: 0.3rem;
|
text-underline-offset: 0.3rem;
|
||||||
}
|
}
|
||||||
|
.opacity-0 {
|
||||||
|
opacity: 0%;
|
||||||
|
}
|
||||||
|
.opacity-100 {
|
||||||
|
opacity: 100%;
|
||||||
|
}
|
||||||
.shadow {
|
.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));
|
--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);
|
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\:bg-red-700 {
|
||||||
&:hover {
|
&:hover {
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@ -665,11 +674,6 @@
|
|||||||
background-color: #2e2e33;
|
background-color: #2e2e33;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.dark\:bg-\[\#333\] {
|
|
||||||
&:where(.dark, .dark *) {
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dark\:bg-\[\#dadadb\] {
|
.dark\:bg-\[\#dadadb\] {
|
||||||
&:where(.dark, .dark *) {
|
&:where(.dark, .dark *) {
|
||||||
background-color: #dadadb;
|
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\] {
|
.dark\:hover\:text-\[\#dadadb\] {
|
||||||
&:where(.dark, .dark *) {
|
&:where(.dark, .dark *) {
|
||||||
&:hover {
|
&: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 {
|
@property --tw-space-y-reverse {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@ -910,24 +920,12 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
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 {
|
@layer properties {
|
||||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
@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 {
|
*, ::before, ::after, ::backdrop {
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-translate-z: 0;
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
--tw-border-style: solid;
|
--tw-border-style: solid;
|
||||||
--tw-leading: initial;
|
--tw-leading: initial;
|
||||||
@ -956,9 +954,6 @@
|
|||||||
--tw-backdrop-saturate: initial;
|
--tw-backdrop-saturate: initial;
|
||||||
--tw-backdrop-sepia: initial;
|
--tw-backdrop-sepia: initial;
|
||||||
--tw-duration: initial;
|
--tw-duration: initial;
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-translate-z: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -172,14 +172,69 @@ fn Pagination() -> Element {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Footer() -> Element {
|
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! {
|
rsx! {
|
||||||
footer { class: "w-full border-t border-gray-200 dark:border-[#333] mt-auto",
|
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]",
|
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" }
|
span { "© 2026 Yggdrasil Blog" }
|
||||||
button {
|
a {
|
||||||
class: "p-2 rounded-full bg-gray-100 dark:bg-[#333] hover:bg-gray-200 dark:hover:bg-[#444] transition-colors",
|
class: "{link_class}",
|
||||||
onclick: move |_| scroll_to_top(),
|
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.top(0.0);
|
||||||
options.behavior(web_sys::ScrollBehavior::Smooth);
|
options.behavior(web_sys::ScrollBehavior::Smooth);
|
||||||
let _ = window.scroll_to_with_scroll_to_options(&options);
|
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 _ = ();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user