EchoLens

READING

第 003 篇 · 关于网站 · 10 分钟

关于“EchoLens”

关于“EchoLens”,我想告诉你的有关它背后的一切。

一切我想告诉你的事。

“EchoLens”到底是什么

“EchoLens”是一个专注于打造优质阅读体验,记录站长所思所闻所感所想的博客网站,全名为“EchoLens观响”。通过透镜观察我大脑中不断凝聚成思想和文字的回声——这就是观响。

做这个网站的原因

我原本喜欢在微信小号朋友圈发一些自己想分享的东西,不过近期感到越来越不好意思在朋友圈发这些东西了,一是考虑到不是所有人都愿意在朋友圈看到这些内容,二是自身原因,早有开一个个人博客的想法,所以就立马着手干了起来。

“EchoLens”的特点以及整体设计思路

开门见山,我应该很自豪地列出我为EchoLens的优质体验打磨的优点:

  • 拥有科学,美观,进行过深度优化的中文排版。
  • 设计了许多提高阅读专注力的设计。
  • 对横竖屏甚至自由比例窗口进行优秀适配。
  • 不设用户名的“回声评论区”,让你的每一次“回声”传递给每个人。
  • 东方典雅设计美学,优雅且统一的动画。
  • Markdown编辑,排版相当统一。

“EchoLens”阅读体验层面的优化在开发过程中占了很大比重,我查阅了许多资料,反复更换方案测试,最终敲定了这一套我钦定的最适合中文阅读的设计。我的开发路径严格遵守最初为网站前端定的设计优先度:

  1. 阅读界面文字字体,字号,字体颜色,字距行距,一行该显示多少文字应该受到最高级别重视。
  2. 好看,好用,稳定且不影响阅读的侧边栏。
  3. 增强观感体验和不费眼的优雅动画。
  4. 好看,能降低视觉疲劳,增强阅读欲望的背景。
  5. 良好的横竖屏适配。

一个专注于中文阅读的网站一定要做好阅读体验和页面交互,我浏览了大量个人博客,我发现其中大多页面做得都很现代化,很美观,但是阅读方面确实不敢恭维,横竖屏适配做的也不多。既然要做,那就一下把要做的东西都做好吧。

下面我把整个设计过程按那个优先度顺序复述一遍,顺便把一些关键的代码片段贴出来——不是想炫技,而是想让你看到"我为什么这么决定"。


一、第一优先级:把“中文阅读”做对

中文在网页上的呈现,长期以来是被低估的话题。大部分博客只关心“字够不够大”,不关心字面、行距、字距、标点位置、孤字断行——而这些恰恰决定了一篇长文读起来累不累。

我为正文专门写了一个 .prose-elegant 样式,把所有“看似无关紧要、其实决定阅读体验”的细节一次性都加进来:

.prose-elegant {
  /* 字体:思源宋体 Regular。宋体本身有完整 hinting,1080p 屏不糊 */
  font-family: var(--font-serif);
  font-weight: 400;

  /* 字号 17.2px:研究显示屏幕中文正文的“清晰甜点”在 17~18px */
  font-size: 1.075rem;

  /* 行高 1.95:17.2px × 1.95 ≈ 33.5px,宋体的中宫加这个行距,
     呼吸感舒展但不松散 */
  line-height: 1.95;

  /* 字间距 0.01em:CJK 字形本身有留白,
     太多 tracking 会让“字”散成“笔画” */
  letter-spacing: 0.01em;

  /* OpenType 特性:palt 让中文标点压成半角,节奏更紧凑、更“印刷感” */
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1, "palt" 1;
}

光把字调好还不够,真正让中文“像印的”而不是“像打的”,是这四条规则

.prose-elegant {
  /* 1. 行首避头:句号、逗号不允许出现在行首(GB/T 15834 强制规定) */
  line-break: strict;

  /* 2. 行末标点悬挂:句末标点挂到右边距外,让版心右齐
        —— 现代杂志 / 书籍的中文排版美学 */
  hanging-punctuation: allow-end;

  /* 3. 标点间距修剪:自动去掉行首标点前的“半字”空间 */
  text-spacing-trim: trim-start;

  /* 4. CJK 智能断词:浏览器按“语义短语”换行,不把词组拆散 */
  word-break: auto-phrase;
}

这四条规则里,有的需要 Chrome 119+ 才支持。我没有为了照顾老浏览器把这些功能砍掉——因为这套优化是网站的核心。老浏览器降级到普通排版,能用;新浏览器拿到完整体验,赢麻

为了让首段第一眼就有“读印刷品”的感觉,我做了真·首字下沉:

.prose-elegant p:first-of-type::first-letter {
  float: left;
  margin: 0.1em 0.12em 0 0;
  color: var(--accent);   /* 朱红 */
  font-weight: 700;
  font-size: 3.4em;       /* 约占 3 行高 */
  line-height: 0.88;
}

引用块我特地换成楷体(霞鹜文楷),和正文的宋体形成对比——一个是骨,一个是肉:

.prose-elegant blockquote {
  border-left: 2px solid var(--accent);
  padding-left: 1.4rem;
  font-family: var(--font-kai);  /* 楷体,书卷气 */
  font-size: 1.05rem;
  line-height: 1.9;
  /* 引用也要继承标点四件套,不然节奏断了 */
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1, "palt" 1;
  line-break: strict;
  hanging-punctuation: allow-end;
}

光是这一部分的样式,我前后改过不下二十次。每一次都是“读着读着觉得哪里不对”,然后回去微调一两个像素或几个百分点。


二、第二优先级:侧栏要好用,但不能抢戏

文章页的侧栏要承担三件事:看到自己读到哪了想跳到哪一节想回去。三件事都要做到,又都不能抢正文的注意力。

阅读进度的计算我没用现成的库,自己写了一段——**用“视口底端当前覆盖到的文章位置 / 文章总高”**来算,比单纯用 "scrollY" 要准:

function updateProgress() {
  const article = document.getElementById("article-content");
  const rect = article.getBoundingClientRect();
  const articleTop = window.scrollY + rect.top;
  const articleBottom = articleTop + article.offsetHeight;

  // 视口底端“已经覆盖到”的文章内部位置
  const covered = window.scrollY + window.innerHeight - articleTop;
  const total = articleBottom - articleTop;
  const value = (Math.min(covered, total) / total) * 100;
  setProgress(Math.max(0, Math.min(100, value)));
}

目录高亮我也想了很久。一开始用“标题进入视口就高亮”,结果你来回滚一下,整篇目录像在抖。后来改成屏幕中心线规则——只有屏幕中线“压”到某一段时,那一段才高亮:

const centerY = window.scrollY + window.innerHeight / 2;
let current = headings[0];

for (const heading of headings) {
  const top = window.scrollY + heading.getBoundingClientRect().top;
  if (top <= centerY) current = heading;
  else break;
}
setActiveHeading(current.id);

这样不管你怎么滚,高亮都是稳的,不会乱跳。

视觉上我没用现代风的“卡片化侧栏”,而是仿了线装书的栏线——侧栏左侧一条贯穿到底的细线,模块之间用更细的横线隔开:

/* 侧栏左侧的细线:用半透明深色,背景越深越可见、越浅越淡,
   天然“随页面变色”,不需要 JS */
.article-side::before {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  width: 1px;
  background: color-mix(in srgb, var(--muted-ink) 40%, transparent);
}

这里有一个我踩了好久的坑:"sticky" 侧栏想跟着滚,得让它的外层容器拉伸到正文那么高,否则 "sticky" 一下就滑出去了。解法是把外层定成 "absolute" 并显式给 "top: 0; bottom: 0"

.article-side-wrap {
  position: absolute;
  top: 0;
  bottom: 0;   /* 关键:拉伸到容器底,sticky 才能跨整篇文章保持可见 */
  left: 0;
  width: 200px;
}
.article-side {
  position: sticky;
  top: 2.5rem;
}

这一步调试用了我一个晚上。


三、第三优先级:动画要优雅,但不能“哇塞”

动画是这个站很多人留意到的部分,但我自己在写的时候提醒自己最多的一句话是:动画的存在感越低越好。它应该像呼吸,而不是像烟花。

路由切换:模糊过渡的取舍

我给路由切换加了模糊淡入的过渡(使用 "framer-motion"),但在文章页这种长内容上一跑,如果过度使用 "filter: blur" 很容易掉帧甚至在 PC 端失效。经过无数次测试,我将模糊半径和过渡曲线调校到了最平衡的状态,确保在手机浏览器和电脑 "Edge" 端都能获得优雅的景深过渡体验。

// app/template.tsx
<motion.div
  initial={{ opacity: 0, filter: "blur(8px)" }}
  animate={{ opacity: 1, filter: "blur(0px)" }}
  transition={{ duration: 0.35, ease: "easeOut" }}
>
  {children}
</motion.div>

音乐播放器:克制的交互

首页有一个常驻的音乐播放器。如果是常规设计,它可能会占据屏幕底部的整条空间,但在移动端,这会严重干扰阅读。因此我采用了自适应折叠形态——在屏幕较窄时,它会默认缩成一个极其小巧的旋转唱片图标;只有当你点击或者鼠标悬停时,才会丝滑展开完整的信息和控件。这也是“不抢戏”哲学的体现。

标题飞行与正文展开

点首页文章卡片时,标题会从卡片位置“飞”到文章的 H1 位置。这个效果我全部通过 "transform" + "opacity" 完成,完全跑在 GPU 合成层上,不触发布局也不触发重绘。正文展开也是按“自然行”计算后依次淡入,视觉上像是在纸面上渐渐浮现。


四、第四优先级:背景要暖、要安静,且高度统一

我不喜欢纯白底色——长时间盯着会刺眼。也不喜欢纯灰底——显得冷淡。

最后选了一个很淡的米黄渐变 + 一层不可见的纸张噪点,让屏幕看起来像一张被灯光照着的宣纸:

body {
  background:
    /* 顶部一团暖色光晕,像灯光从上方落下 */
    radial-gradient(circle at 50% -10%,
      rgba(217, 185, 141, 0.18), transparent 32rem),
    /* 主体米黄到深黄的渐变 */
    linear-gradient(180deg, var(--paper) 0%, #efe4c5 100%);
}

在诸如“关于”、“联系”弹窗以及全局“歌单抽屉”的设计上,我高度统一了毛玻璃美学。全部统一使用 80% 透明度底色搭配 "backdrop-blur-md",让不同模块间的交互保持绝对一致的质感。全局页首(Header)的高度和边距也在各个页面中保持了严格的对齐。


五、第五优先级:多形态内容呈现与横竖屏适配

我自己平时会把笔记本竖过来读长文。市面上极少有博客考虑到这个场景——它们要么写死 1024px 断点,要么干脆只为手机 / 桌面两种宽度做。我用了一条复合媒体查询,实现了屏幕窄 或 屏幕竖,二选一就走抽屉模式,宁可激进一点切到抽屉,也不能让侧栏撞到正文。

后来我不再局限于单一的“长文章”,而是引入了 "Portal"(门面)和 "Daily"(日常)版块。

  • Portal 主页:像一张名片,用简洁的排版融合了关于、联系、近期文章与个人歌单。
  • Daily 日常:用纯 CSS 原生瀑布流("columns-1 md:columns-2 lg:columns-3")实现了图文混排,不需要借助繁重的第三方 "masonry" 库就能完美自适应不同屏幕尺寸。
/* 纯 CSS 瀑布流,利用 columns 实现,避免 JS 计算 */
.daily-grid {
  columns: 3;
  column-gap: 2rem;
}
.daily-card {
  break-inside: avoid; /* 防止卡片被硬生生切断 */
}

六、音乐管理:“拿来主义”与绝对控制权

对于“首页音乐”功能,我实现了一套“半自动化”的后台管理逻辑。 我可以输入一串网易云音乐的链接,后端会通过爬虫接口直接抓取封面、歌曲名、专辑信息以及音频链接。但这还不够

即便抓取了外部数据,我依然为后台开发了拖拽排序与内联编辑的能力:

  • 我可以在后台直接给歌曲更换自定义名称或修改歌手信息,而不是死板地照搬网易云的数据。
  • 我加了排序功能,可以自由调整歌曲播放列表的上下顺序。
  • 如果外部音频防盗链失效,我甚至可以点击上传按钮,把一首本地的 ".mp3"".flac" 覆盖上去,做到随时备份与托管。这是我想要的“拿来主义”和“绝对控制权”的结合。

七、回声评论区:不留名字,慢一点

普通博客评论区是“快速、自由、所有人能看”。我反过来做:不让填昵称、必须经审核、文案叫“回声”

// 提交评论的表单——只有内容字段,没有昵称、邮箱
<textarea
  value={content}
  maxLength={500}
  placeholder="说点什么..."
/>

服务端收到后默认 "approved: false",访客看不到,需要我手动放行。提交成功后弹一个温柔的小窗告知“正在审核”——而不是一句冷冰冰的 "Submitted"。这种节奏不快,但避免了垃圾评论、避免了情绪化对线、保留了留言文化最珍贵的部分


八、Markdown 写作:让排版不操心

我自己写文章时,最不想做的事就是“调格式”。所以管理后台只接受一种输入:纯 Markdown

渲染端用 "react-markdown" 把它变成 HTML,并给每个 "h2" / "h3" 打 ID 用作目录锚点。排版样式全靠 ".prose-elegant" 那一套——意思就是不管哪篇文章、谁写的、写多长,最后呈现出来的视觉永远是一致的。我只需要专注内容。


九、写在最后

整个站的工程量并不大,但每一个细节我都打磨过不止一遍。 它不会被很多人看见。
但如果你恰好打开了它,能在某一篇文章里安静地读完一段话,没有被弹窗、广告、动画、横竖屏的不适感打断——

那这个站就值了。

把生活里的微光,慢慢安放。
纸张摊开,让回响落定。

Mini Cover

多次元宇宙融合論

TAKIO, つぐ