当前个人网站经过了多次迭代, 是典型的静态网页, 依靠本地编译生成, 并基于 GitHub Pages ↗ 进行部署. 这类网站完全免费且不需要任何数据库支持, 但具有如下几个缺点,
- 占用空间限制: space should not be greater than 1 G.
- 流量限制: monthly flow should not be greater than 100 G.
- 更新次数限制: cannot be updated 10 times per hour.
- 自定义功能限制: cannot be equipped with comment function (this can be complemented by third-party services).
尽管如此, 静态网页带来了更多好处, 成为个人网站项目的首选.
注意, 静态网页只由 plain HTML 文件构成, 不同于由 PHP 生成的动态网页, 静态网页对于每个用户都展示相同的界面. 这也帮助没有数据库编程经验的人仅依靠 HTML/CSS 知识完成建站.
此类网站一般加载得更快, 因为服务器不需要解析任何内容, 只是读取 plain HTML 文件. 其与动态网页的优劣区别需结合实际应用权衡.
网站选取 Astro ↗ 高效能网络框架作为静态站点生成器 (static site generator, SSG), 它是首个普及群岛架构 (islands architecture) 的框架, 大幅减少了用户端负担从而提升网页的初始加载性能, 开发者可在 Astro 中对同一专案同时使用如 React ↗ 和 Vue ↗ 等多种 UI 框架.
个人网页平台利用 markdown 以及 Markdown for the component era ↗ (MDX) 编写的文章生成对应网页, 并支持通过 Vite 使用如 Sass 等 CSS 预处理器, 将脚本解析成 CSS. Astro 可以看作一类内容管理系统 (content management system, CMS, 可作 client 使用), 能够加载自定义 layout 文件以及对应 .md 与 .mdx 文件到每一网页中, 从而节约重写次数.
Waiting for api.github.com...
具体地, 本项目基于 Astro 构建, 支持 Markdown, MDX, 组件化开发, 并集成搜索, 标签, 评论等功能, 以下为项目文件结构.
-
src/源代码主目录. 用户的主要工作区, 包含页面, 组件, 内容和样式.src/pages/页面路由. 每个.astro、.md、.mdx文件会自动生成对应的路由 (如pages/about/index.astro/about).src/content/内容集合. 包含博客 (blog/, 每篇文章可包含标题, 日期, 标签等 Frontmatter), 其它文档等.src/components/可复用 UI 组件. 按功能模块划分为about/,home/,links/,projects/,waline/评论组件 (文件类型.astro/.jsx/.tsx).src/layouts/页面布局. 定义全局页面结构 (头部, 底部, 侧边栏).src/assets/静态资源. 图片, 字体, 图标, 样式文件等.
-
public/公共静态资源. 直接映射到网站根目录的/favicon/,/images/,/scripts/等资源. -
dist/构建输出目录. 为运行npm run build后生成的用于部署的静态文件 (包含所有页面, 资源, 搜索索引等). -
packages/pure/本地核心模块, 与主项目解耦. -
node_modules/astro-pure主项目核心模块. -
astro.config.tsAstro 根目录配置文件. 用于配置站点 URL, 构建选项, 集成, Vite 设置等.
① 云端分支部署#
手动将项目编译得到的静态网页 dist 上传到 GitHub 中, 进入仓库 Settings-GitHub Pages-Build and deployment-Branch , 选择 jinzhao_release 作为当前站点的 source.
首先运行编译命令, 同时本地预览.
npm run build
npx astro previewbash实时更改的本地预览命令为.
npm run devbash同样于链接 http://localhost:4321/Era_Pass/ 检测生成网页. 检查后变更网络至代理 (如 Clash 常用端口 7890), 并将生成静态网页推送至 Github 云端分支 jinzhao_release (生成 dist 静态网站目录剥离为本地临时分支 deploy-branch, 再覆盖到云端分支).
git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890
git add dist -f
git commit -m "Deploy: static site content update"
git subtree split --prefix dist -b deploy-branch
git push origin deploy-branch:jinzhao_release --forcebash网页正确运行则将在仓库 Action 页面出现蓝色 check mark, 否则显示红色 error mark, 成功托管的静态网页可在如下网址 erapass ↗ 访问.
② 网站基本设置#
字体 | Satoshi 本地加载#
在全局设置加入本地字体 (不刻意设置 weights, 不采用 fontProviders.fontshare() 以避免依靠网络加载).
fonts: [
{
provider: fontProviders.local(),
name: 'Satoshi',
cssVariable: '--font-satoshi',
options: {
variants: [{
src: ['./src/assets/fonts/Satoshi-Variable.ttf'],
style: 'normal'
}]}
}
],css题头 | Bezier 旋转#
在全局样式表设置加入个人头像的主题旋转, 以适配当前小狗灵的时频世界主题标头. 创建样式表.
img[alt=Avatar] {
-webkit-transition: -webkit-transform 1s cubic-bezier(0.65, 0.05, 0.36, 1);
-moz-transition: -moz-transform 1s cubic-bezier(0.65, 0.05, 0.36, 1);
-o-transition: -o-transform 1s cubic-bezier(0.65, 0.05, 0.36, 1);
-ms-transition: -ms-transform 1s cubic-bezier(0.65, 0.05, 0.36, 1);
transition: transform 1s cubic-bezier(0.65, 0.05, 0.36, 1);
}
img[alt=Avatar]:hover {
-webkit-transform: rotateZ(-360deg);
-moz-transform: rotateZ(-360deg);
-o-transform: rotateZ(-360deg);
-ms-transform: rotateZ(-360deg);
transform: rotateZ(-360deg);
}css导航 | BlurFilter 毛玻璃#
在全局样式表设置加入网页标头的低通模糊. 创建样式表 (.not-top 筛选顶部元素非置顶情形, 即滚动页面后, dark 筛选白 / 黑色界面切换).
header-component[class*="astro-"].not-top {
--un-border-opacity: 1 !important;
background: rgba(255, 255, 255, 0.75) !important;
-webkit-backdrop-filter: blur(2px) !important;
backdrop-filter: blur(2px) !important;
}
html[class*="dark"] header-component[class*="astro-"].not-top {
background: rgba(28, 28, 29, 0.8) !important;
-webkit-backdrop-filter: blur(2px) !important;
backdrop-filter: blur(2px) !important;
}css文摘 | Quote 随机格言#
在文段加入 HTML 标签.
<Quote />htmlLoading...
③ 网页部件设计#
简历 | PDFjs 阅读容器#
在网页中置入嵌入式 PDF 阅读器, 承装个人简历的 .pdf 文件. 包含如下功能.
采用 <iframe> 配合 PDF.js 网页部件实现 PDF 阅读容器, 首先安装 pdfjs-viewer.
npm install pdfjs-viewer-elementbash而后创建样式表.
<script>
import 'pdfjs-viewer-element';
</script>
<style>
.pdf-container {
width: 100%;
max-width: 850px;
height: 50vh;
min-height: 400px;
margin: 20px auto;
border-radius: 10px;
overflow: hidden;
}
pdfjs-viewer-element {
display: block;
width: 100%;
height: 100%;
}
</style>css并加入 pdf 阅读-编辑器模块.
<div class="pdf-container">
<pdfjs-viewer-element
src="/Era_Pass/resume/Jinzhao_Li_CV_Sp_.pdf",
viewer-css-theme="AUTOMATIC",
zoom="page-width",>
</pdfjs-viewer-element>
</div>html经历 | Card 背景框格#
含背景的个人经历, 其包含如下功能.
利用 Astro 的 ImageMetadata 加载功能, 加上 HTMLTag 及多态 Polymorphic 特性实现 Card 背景窗格. 首先为 Card 设置类型并加载网页 \asset 中的图像 (Tag='a' 将 Card 设置为可点击链接).
type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
heading?: string
subheading?: string
imagePath?: string
altText?: string
href?: string
date?: string
class?: string
}
const { as: Tag = 'a', class: className, href, heading, subheading, date, imagePath, altText } = Astro.props
const images = import.meta.glob<{ default: ImageMetadata }>(
['/src/assets/users/*.{jpeg,jpg,png,gif,avif,webp}',
'/src/assets/experiences/*.{jpeg,jpg,png,gif,avif,webp}',]
)
if (!images[imagePath])
throw new Error(
`"${imagePath}" does not exist in glob: "src/assets/*.{jpeg,jpg,png,gif,avif,webp}"`
)css而后创建样式表 (--un-gradient-from-position 和 --un-gradient-to-position 调整图像的渐变透出度最小和最大值).
<style>
.card-fg {
--un-gradient-shape: to bottom in oklch;
--un-gradient: var(--un-gradient-shape), var(--un-gradient-stops);
--un-gradient-from-position: 20%;
--un-gradient-from: transparent var(--un-gradient-from-position);
--un-gradient-to-position: 80%;
--un-gradient-to: hsl(var(--card) / var(--un-to-opacity, 1)) var(--un-gradient-to-position);
--un-gradient-stops: var(--un-gradient-from), var(--un-gradient-to);
background-image: linear-gradient(var(--un-gradient));
}
.card:hover .card-fg {
--un-gradient-to-position: 100%;
}
</style>css并加入带背景及链接的窗格模块 (rounded-md 较小圆角, -lg 中圆角, -xl 大圆角, -2xl 二倍大圆角, font-size 和 weight, margin-top 等调整 subhead 内容以及段落间距).
<Tag class={cn(
className, 'card group overflow-hidden relative flex flex-col justify-end rounded-md border bg-muted',
href && 'transition-all hover:border-foreground/25 hover:shadow-sm'
)} href={href} target='_blank'>
<Image
src={images[imagePath]()}
alt={altText ?? heading}
class='absolute w-full object-contain opacity-60 transition-opacity group-hover:opacity-100 dark:opacity-40'
class='absolute w-full h-full object-cover opacity-60 transition-opacity group-hover:opacity-100 dark:opacity-40'
loading='lazy'
/>
<div class='card-fg z-10 flex flex-col gap-y-0.5 px-5 pb-4 pt-10 transition-all'>
<h2 class='text-lg font-medium' style='line-height:1.2em;'>{heading}</h2>
<p class='text-muted-foreground' style='font-size: 0.8em; font-weight: 700; line-height:1.2em; margin-bottom: 0.1em; margin-top: -1.2em;'>{subheading}</p>
<p class='text-muted-foreground' style='font-size: 0.75em; font-weight: 400; line-height:1.2em; margin-bottom: -0.2em; margin-top: 0.1em;'>{date}</p>
</div>
<slot />
</Tag>html类似地, 窗格模块也适用于科研项目简介的 ProjectCard 类型.
<Tag class={cn(
className, 'project-card group overflow-hidden relative flex flex-col justify-end rounded-2xl border bg-muted ',
href && 'transition-all hover:border-foreground/25 hover:shadow-sm'
)} href={href} target='_blank'>
<Image
src={images[imagePath]()}
alt={altText ?? heading}
class='absolute w-full object-contain opacity-60 transition-opacity group-hover:opacity-100 dark:opacity-40'
class='absolute w-full h-full object-cover opacity-60 transition-opacity group-hover:opacity-100 dark:opacity-40'
loading='lazy'
/>
<div class='project-card-fg z-10 flex flex-col gap-y-0.5 px-5 pb-4 pt-10 transition-all'>
<h2 class='text-lg font-medium' style='line-height:1.2em;'>{ heading}</h2>
<p class='text-muted-foreground' style='font-size: 0.8em; line-height:1.2em; margin-top: 0.2em;'>{subheading}</p>
</div>
<slot />
</Tag>html④ 文章部件设计#
隐藏 | Spoiler 块#
以 HTML 标签形式加入覆盖块.
<Spoiler>隐藏文字</Spoiler>html代码 | Collapsed 块#
首先基于 Shiki 渲染器配置进一步增添代码触发折叠的逻辑.
export const addCollapse = (): ShikiTransformer => {
const line = 5
return {
name: 'shiki-transformer-add-collapse',javascript而后创建样式表 (max-height 只包含对应行数).
&.collapsed {
& pre {
max-height: 5rem;
overflow: hidden;
}css在全局设置中调用上面代码折叠块的函数.
export default defineConfig({
shikiConfig: {
...
transformers: [
addCollapse()
]
}
) as anyjavascript强调 | Aside 块#
加入文章的注意, 危险等强调色块 (type 若不指定则对应采用 note 类型).
<Aside>...</Aside>
<Aside type='danger'>contents to be noted</Aside>html媒体 | Icon 常量#
介绍 email, scholar, GitHub 等社交媒体账号的对应图标.
设置全局常量 Icon (viewBox 前两数字控制图标平移位置, transform=“scale()” 控制图标缩放大小). 应同时在 \packages\pure\libs\icons.ts 以及 \node_modules\astro-pure\components\user\Icon.astro 两路径中完成更改.
export const BuiltInIcons = {
google_scholar:
'<svg viewBox="12 16 32 32" width="100%" height="100%"><g transform="scale(0.07)" transform-origin="center"><path fill="currentColor" d="M343.759 106.662V79.43L363.524 64h-213.89L20.476 176.274h85.656a82 82 0 0 0-.219 6.225c0 20.845 7.22 38.087 21.672 51.861c14.453 13.797 32.252 20.648 53.327 20.648c4.923 0 9.75-.368 14.438-1.024c-2.907 6.5-4.374 12.523-4.374 18.142c0 9.875 4.499 20.43 13.467 31.642c-39.234 2.67-68.061 9.732-86.437 21.163c-10.531 6.5-19 14.704-25.39 24.531c-6.391 9.9-9.578 20.515-9.578 31.962c0 9.648 2.062 18.336 6.219 26.062c4.156 7.726 9.578 14.07 16.312 18.984c6.718 4.968 14.469 9.101 23.219 12.469c8.734 3.344 17.406 5.718 26.061 7.062A167 167 0 0 0 180.555 448c13.469 0 26.953-1.734 40.547-5.187c13.562-3.485 26.28-8.642 38.171-15.493c11.86-6.805 21.515-16.086 28.922-27.718c7.39-11.68 11.094-24.805 11.094-39.336c0-11.016-2.25-21.039-6.75-30.14c-4.468-9.073-9.938-16.542-16.452-22.345c-6.501-5.813-13-11.155-19.516-15.968c-6.5-4.845-12-9.75-16.468-14.813c-4.485-5.046-6.735-10.054-6.735-14.984c0-4.921 1.734-9.672 5.216-14.265c3.455-4.61 7.674-9.048 12.61-13.306c4.937-4.25 9.875-8.968 14.796-14.133c4.922-5.147 9.141-11.827 12.61-20.008c3.485-8.18 5.203-17.445 5.203-27.757c0-13.453-2.547-24.46-7.547-33.314c-.594-1.022-1.218-1.803-1.875-3.022l56.907-46.672v17.119c-7.393.93-6.624 5.345-6.624 10.635V245.96c0 5.958 4.875 10.834 10.834 10.834h3.989c5.958 0 10.833-4.875 10.833-10.834V117.293c0-5.277.778-9.688-6.561-10.63m-107.36 222.48c1.14.75 3.704 2.78 7.718 6.038c4.05 3.243 6.797 5.695 8.266 7.414a444 444 0 0 1 6.376 7.547c2.813 3.375 4.718 6.304 5.718 8.734q1.5 3.717 3.047 8.946a38.3 38.3 0 0 1 1.485 10.562c0 17.048-6.564 29.68-19.656 37.859c-13.125 8.18-28.767 12.274-46.938 12.274c-9.187 0-18.203-1.093-27.063-3.196c-8.843-2.116-17.311-5.336-25.39-9.601c-8.078-4.258-14.577-10.204-19.5-17.797c-4.938-7.64-7.407-16.415-7.407-26.25c0-10.32 2.797-19.29 8.422-26.906c5.594-7.625 12.938-13.391 22.032-17.315c9.063-3.946 18.25-6.742 27.562-8.398a158 158 0 0 1 28.438-2.555c4.47 0 7.936.25 10.405.696c.455.219 3.032 2.07 7.735 5.563c4.704 3.462 7.625 5.595 8.75 6.384zm-3.359-100.579c-7.406 8.86-17.734 13.288-30.953 13.288c-11.86 0-22.298-4.764-31.266-14.312c-9-9.523-15.422-20.328-19.344-32.43c-3.937-12.109-5.906-23.984-5.906-35.648q.002-20.54 10.781-34.976c7.187-9.65 17.5-14.485 30.938-14.485c11.875 0 22.374 5.038 31.437 15.157c9.094 10.085 15.61 21.413 19.517 33.968c3.922 12.54 5.873 24.53 5.873 35.984c0 13.446-3.702 24.61-11.076 33.454z" /></g></svg>',
gmail:
'<svg viewBox="20 20 32 32"><g transform="scale(0.04)" transform-origin="center"><path fill="currentColor" d="M696.022905 317.226952h-37.625501v48.180187h37.625501c76.228288 0 138.285932 62.057645 138.285933 138.285932v226.143921c0 17.297958-14.072915 31.468601-31.468601 31.468601H513.3682c0.6841-3.518229 0.977286-7.036457 0.977286-10.652415V503.7908c0-54.82573-23.943501-104.080931-61.764459-138.285933h124.310746v222.332507c0 0.586371-0.195457 1.172743-0.195457 1.759114 0 25.507158 20.718458 46.225616 46.225616 46.225615s46.225616-20.718458 46.225615-46.225615-19.545715-44.759687-43.977858-45.93243v-323.48158c0-4.202329 3.4205-7.5251 7.5251-7.525101h70.266845v-48.180187h-70.266845c-30.784501 0-55.803016 25.018515-55.803016 55.803016v97.142203H327.879366c-102.81046 0-186.466119 83.655659-186.466119 186.466119v246.862379c0 32.445887 26.386715 58.832602 58.832601 58.832601h602.398932c43.88013 0 79.648788-35.67093 79.648787-79.648788V503.7908c0-102.81046-83.655659-186.466119-186.466119-186.46612zM200.441306 761.207864c-5.863714 0-10.652415-4.7887-10.652415-10.652414V503.693071c0-76.228288 62.057645-138.285932 138.285932-138.285932s138.285932 62.057645 138.285933 138.285932V750.55545c0 5.863714-4.7887 10.652415-10.652415 10.652414H200.539034z" p-id="3369" fill="#000000"></path><path d="M245.591907 487.470128h139.067761v48.180187H245.591907z" p-id="3370" fill="#000000"></path></g></svg>',
}
export const Icons = {
...BuiltInIcons
}
export type IconsType = keyof typeof Iconsjavascript并在页脚加入对应的图标模块.
footer: {
social: [
{ icon: 'email', label: 'Email', href: 'mailto:opticscss@gmail.com' },
{ icon: 'google_scholar', label: 'Google Scholar', href: 'https://scholar.google.com/citations?user=6sFE0UQAAAAJ' },
{ icon: 'github', label: 'GitHub', href: 'https://github.com/Opticcss' },
{ icon: 'rss', label: 'RSS', href: '/Era_Pass/rss.xml' }
]
},css图标可由如下两类形式引入.
<Svg src={import('@/assets/icons/key.svg?raw')} />
<Icon name='rss' class='inline' />html如下为当前网页支持图标常量.
目录 | TOC 列表#
加入列表头名及加粗.
<toc-heading class={className} {...props}>
<h2 class='font-medium' style='font-size: 1.05em; font-weight: 700;'>Contents</h2>
<ul class='mt-4' style='font-size: 0.95em;'>
{toc.map((heading) => <TOCHeading heading={heading} />)}
</ul>
</toc-heading>html预览 | PostPreview 表#
加入最大预览文章篇数.
const MAX_POSTS = 5
const allPosts = await getBlogCollection()
const allPostsByDate = sortMDByDate(allPosts).slice(0, MAX_POSTS)
{allPostsByDate.length > 0 && (
<Section title='Recent Art'>
<ul class='flex flex-col gap-y-1.5 sm:gap-y-2'>
{allPostsByDate.map((p) => (
<li class='flex flex-col gap-x-2 sm:flex-row'>
<PostPreview post={p} />
</li>
))}
</ul>
<Button title='More Articles' class='w-fit self-end' href='/Era_Pass/blog' variant='ahead' />
</Section>
)}javascript编号 | Steps 表#
加入文章有序列表的分点项目.
<Steps>
1. content in the first row.
subcontent follows the first row.
2. content in the second row.
</Steps>html- content in the first row. subcontent follows the first row.
- content in the second row.
选栏 | TabItem 表#
加入文章可选列表的选项卡内容.
<Tabs>
<TabItem label="Stars">Sirius, Vega, Betelgeuse</TabItem>
<TabItem label="Moons">Io, Europa, Ganymede</TabItem>
</Tabs>html标签 | Tag 按钮#
加入文章 Frontmatter 的日期, 阅读时间以及标签内容.
<div class='flex items-center gap-1'>
<Icon name='calendar' class='size-5' />
<FormattedDate class='font-sans' date={publishDate} dateTimeOptions={dateTimeOptions} />
...
{!!tags?.length && (
<span class='flex items-center gap-1'>
<Icon name='hashtag' class='size-5' />
{tags.map((tag: string, i: number) => (
<div>
<a aria-label={`View more blogs with the tag ${tag}`}
class='hover:underline hover:underline-offset-4'
data-pagefind-filter='tag'
href={`/Era_Pass/tags/${tag}`}
>{tag}
</a>
{i < tags.length - 1 && '/'}
</div>
))}
</span>
)}
</div>html
