mirror of
https://github.com/byoungd/up.git
synced 2026-10-02 08:04:40 +08:00
Improve the long-form reading experience
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { useRoute } from "vitepress";
|
||||
|
||||
const route = useRoute();
|
||||
const progress = ref(0);
|
||||
const active = ref(false);
|
||||
let animationFrame: number | undefined;
|
||||
let contentObserver: ResizeObserver | undefined;
|
||||
|
||||
function measureProgress() {
|
||||
animationFrame = undefined;
|
||||
const content = document.querySelector<HTMLElement>(".VPDoc .content-container");
|
||||
if (!content) {
|
||||
active.value = false;
|
||||
progress.value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const navHeight = Number.parseFloat(rootStyles.getPropertyValue("--vp-nav-height")) || 64;
|
||||
const contentTop = window.scrollY + content.getBoundingClientRect().top;
|
||||
const start = Math.max(0, contentTop - navHeight);
|
||||
const end = contentTop + content.scrollHeight - window.innerHeight;
|
||||
const distance = end - start;
|
||||
|
||||
active.value = distance > 160;
|
||||
progress.value = distance > 0 ? Math.min(1, Math.max(0, (window.scrollY - start) / distance)) : 1;
|
||||
}
|
||||
|
||||
function scheduleMeasurement() {
|
||||
if (animationFrame === undefined) animationFrame = window.requestAnimationFrame(measureProgress);
|
||||
}
|
||||
|
||||
function observeContent() {
|
||||
contentObserver?.disconnect();
|
||||
const content = document.querySelector<HTMLElement>(".VPDoc .content-container");
|
||||
if (content) contentObserver?.observe(content);
|
||||
scheduleMeasurement();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
contentObserver = new ResizeObserver(scheduleMeasurement);
|
||||
window.addEventListener("scroll", scheduleMeasurement, { passive: true });
|
||||
window.addEventListener("resize", scheduleMeasurement);
|
||||
observeContent();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
async () => {
|
||||
progress.value = 0;
|
||||
await nextTick();
|
||||
observeContent();
|
||||
},
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("scroll", scheduleMeasurement);
|
||||
window.removeEventListener("resize", scheduleMeasurement);
|
||||
contentObserver?.disconnect();
|
||||
if (animationFrame !== undefined) window.cancelAnimationFrame(animationFrame);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-show="active"
|
||||
class="reading-progress"
|
||||
aria-hidden="true"
|
||||
data-reading-progress
|
||||
:data-progress="(progress * 100).toFixed(1)"
|
||||
>
|
||||
<span class="reading-progress-value" :style="{ transform: `scaleX(${progress})` }" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,7 +1,13 @@
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import type { Theme } from "vitepress";
|
||||
import { h } from "vue";
|
||||
import ReadingProgress from "./ReadingProgress.vue";
|
||||
import "./styles.css";
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout: () =>
|
||||
h(DefaultTheme.Layout, null, {
|
||||
"layout-top": () => h(ReadingProgress),
|
||||
}),
|
||||
} satisfies Theme;
|
||||
|
||||
@@ -6,10 +6,31 @@
|
||||
--vp-c-tip-1: #176f5b;
|
||||
--vp-c-tip-soft: rgba(23, 111, 91, 0.12);
|
||||
--vp-layout-max-width: 1480px;
|
||||
--reading-progress-color: #c0523a;
|
||||
}
|
||||
|
||||
.VPDoc .content-container {
|
||||
max-width: 820px;
|
||||
max-width: 780px;
|
||||
}
|
||||
|
||||
.reading-progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.reading-progress-value {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--reading-progress-color);
|
||||
transform-origin: left center;
|
||||
transition: transform 80ms linear;
|
||||
}
|
||||
|
||||
.vp-doc h1,
|
||||
@@ -19,8 +40,36 @@
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
font-size: clamp(2rem, 5vw, 2.75rem);
|
||||
line-height: 1.2;
|
||||
max-width: 22ch;
|
||||
font-size: 2.625rem;
|
||||
line-height: 1.18;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.vp-doc h2,
|
||||
.vp-doc h3 {
|
||||
scroll-margin-top: calc(var(--vp-nav-height) + 20px);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.vp-doc p,
|
||||
.vp-doc li {
|
||||
line-height: 1.85;
|
||||
}
|
||||
|
||||
.vp-doc p {
|
||||
margin: 18px 0;
|
||||
}
|
||||
|
||||
.vp-doc blockquote {
|
||||
margin: 24px 0;
|
||||
padding: 12px 18px;
|
||||
border-left: 3px solid var(--vp-c-brand-1);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.vp-doc blockquote > p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vp-doc img {
|
||||
@@ -116,6 +165,17 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
max-width: none;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.vp-doc p,
|
||||
.vp-doc li {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.vp-doc .guide-paths {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -129,3 +189,64 @@
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reading-progress-value {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
margin: 18mm 16mm;
|
||||
}
|
||||
|
||||
.VPNav,
|
||||
.VPSidebar,
|
||||
.VPDocAside,
|
||||
.VPLocalNav,
|
||||
.VPDocFooter,
|
||||
.VPFooter,
|
||||
.reading-progress {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.VPContent,
|
||||
.VPDoc,
|
||||
.VPDoc .container,
|
||||
.VPDoc .content,
|
||||
.VPDoc .content-container {
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.vp-doc {
|
||||
color: #111;
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
max-width: none;
|
||||
font-size: 26pt;
|
||||
}
|
||||
|
||||
.vp-doc h1,
|
||||
.vp-doc h2,
|
||||
.vp-doc h3 {
|
||||
break-after: avoid-page;
|
||||
}
|
||||
|
||||
.vp-doc table,
|
||||
.vp-doc pre,
|
||||
.vp-doc blockquote,
|
||||
.vp-doc img {
|
||||
break-inside: avoid-page;
|
||||
}
|
||||
|
||||
.vp-doc a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user