← 博客

排版测试:文字在这里的样子

文字、代码高亮、数理化生公式,以及表格、动画和交互画布的排版测试。

这是一篇排版测试文章。从一行文字到一张可以操作的画布,试着让不同形式的内容,都能安静、清楚地放在同一个页面里。

文字 · 代码 · 数学 · 物理 · 化学 · 生物 · 表格 · 交互表格 · 动画 · Canvas

文字与层次

一个安静的页面,应该把注意力留给内容。这里用稍长一点的段落观察行宽、行距,以及中文和 English 混排时的节奏。读到句子的末尾,再自然地转到下一行,文字之间留出一点呼吸的空间。

强调、引用与列表

这是加粗文字、斜体文字、删除文字,以及行内代码 const greeting = 'hello'。也可以打开项目页,或看看 MotionFlux。

清楚的层次、舒服的间距,就足够支撑一次阅读。

长一点的引用,也应该保留段落本身的节奏。

  • 中英文、数字 0123456789 和标点自然地排在一起。
  • 列表可以嵌套:
    • 公式、表格与代码都属于内容。
    • 不需要为每种内容重新设计一篇文章。
  1. 先看电脑上的整体阅读效果。
  2. 再看手机上的行宽和横向滚动。
  3. 最后试试下面的交互。
  • 标题、引用与列表
  • 公式与代码高亮
  • 动画、Canvas 与交互表格

代码高亮

代码块按标注的语言高亮,保留缩进,支持复制。较长的代码可以在块内横向滚动。

typescript
type Sample = { name: string; values: number[] }

function mean({ name, values }: Sample): string {
  // 让数字与文字保持同样清楚的表达。
  const average = values.reduce((sum, value) => sum + value, 0) / values.length
  return `${name}: ${average.toFixed(2)}`
}

console.log(mean({ name: 'Sylvan', values: [2, 4, 8, 16] }))
python
from math import sin, pi

def wave(t: float, amplitude: float = 1.0) -> float:
    """一条最简单的正弦波。"""
    return amplitude * sin(2 * pi * t)

samples = [wave(i / 100) for i in range(101)]
print(f"Peak: {max(samples):.3f}")

数学公式

行内公式可以自然地嵌入句子,例如 eiπ+1=0e^{i\pi}+1=0,或描述函数 f(x)=x2f(x)=x^2。独立公式居中显示,长公式在小屏幕上可以横向滚动。

∫−∞+∞e−x2 dx=π\int_{-\infty}^{+\infty} e^{-x^2}\,\mathrm{d}x = \sqrt{\pi}

分式、根号、多行推导和矩阵:

ax2+bx+c=0(a≠0)x=−b±b2−4ac2a\begin{aligned} ax^2+bx+c &= 0 \qquad (a\ne 0) \\ x &= \frac{-b\pm\sqrt{b^2-4ac}}{2a} \end{aligned} R(θ)=(cos⁡θ−sin⁡θsin⁡θcos⁡θ)\mathbf{R}(\theta)= \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}

物理公式

简谐运动的位移为 x(t)=Acos⁡(ωt+φ)x(t)=A\cos(\omega t+\varphi)。对时间求导,可以得到速度与加速度:

v(t)=−Aωsin⁡(ωt+φ)a(t)=−ω2x(t)\begin{aligned} v(t) &= -A\omega\sin(\omega t+\varphi) \\ a(t) &= -\omega^2 x(t) \end{aligned}

矢量、偏导与物理单位同样可以排版:

∇×E=−∂B∂tE=mc2,c≈3.00×108  m s−1\nabla\times\mathbf{E}=-\frac{\partial\mathbf{B}}{\partial t} \qquad\qquad E=mc^2,\quad c\approx 3.00\times10^8\;\mathrm{m\,s^{-1}}

化学公式

化学式使用 \ce{...} 语法,支持元素下标、离子电荷、反应箭头与同位素。例如水 HX2O\ce{H2O}、硫酸根 SOX4X2−\ce{SO4^{2-}} 和碳同位素 X614X26214C\ce{^{14}_{6}C}。

2 HX2+OX2→2 HX2O\ce{2H2 + O2 -> 2H2O} FeX3++SCNX−⇌[FeSCN]X2+\ce{Fe^{3+} + SCN^- <=> [FeSCN]^{2+}} CaCOX3→ΔCaO+COX2↑\ce{CaCO3 ->[\Delta] CaO + CO2 ^}

生物公式

米氏方程描述一种常用的酶动力学模型,其中 [S][S] 为底物浓度,KmK_m 为米氏常数:

v=Vmax⁡[S]Km+[S]v=\frac{V_{\max}[S]}{K_m+[S]}

相应的简化反应过程可以写成:

E+S⇌ES→E+P\ce{E + S <=> ES -> E + P}

在满足 Hardy–Weinberg 平衡假设的二等位基因模型中,等位基因频率与基因型频率的关系为:

p+q=1,p2+2pq+q2=1p+q=1,\qquad p^2+2pq+q^2=1

这些公式用于排版演示;具体模型的使用仍需要相应的实验条件与假设。

静态表格

普通 Markdown 表格支持列对齐、行内公式、链接与强调。窄屏幕上可以在表格内左右滑动。

物理量符号示例值单位
振幅AA0.8m\mathrm{m}
频率ff1.5Hz\mathrm{Hz}
角频率ω=2πf\omega=2\pi f9.42rad s−1\mathrm{rad\,s^{-1}}
周期T=1/fT=1/f0.67s\mathrm{s}

交互表格

点击列标题排序,输入关键词筛选,或切换到下一页。点击「编辑数据」可以修改数值,下面的均值会随之更新。「重置表格」恢复原始数据。

酶反应初速 · 演示数据
样本 A0.50.91
样本 B11.67
样本 C22.86
样本 D44.44
样本 E86.15
8 行 · 第 1 / 2 页
底物浓度 (mM)均值 8.4375初速 (μmol/min)均值 4.8738

修改仅用于本页演示,刷新或重置后恢复。均值随筛选和编辑实时更新。

查看表格数据
json
{
  "title": "酶反应初速 · 演示数据",
  "pageSize": 5,
  "columns": [
    { "key": "sample", "label": "样本", "type": "text" },
    { "key": "substrate", "label": "底物浓度 (mM)", "type": "number", "editable": true },
    { "key": "rate", "label": "初速 (μmol/min)", "type": "number", "editable": true }
  ],
  "rows": [
    { "sample": "样本 A", "substrate": 0.5, "rate": 0.91 },
    { "sample": "样本 B", "substrate": 1, "rate": 1.67 },
    { "sample": "样本 C", "substrate": 2, "rate": 2.86 },
    { "sample": "样本 D", "substrate": 4, "rate": 4.44 },
    { "sample": "样本 E", "substrate": 8, "rate": 6.15 },
    { "sample": "样本 F", "substrate": 12, "rate": 7.06 },
    { "sample": "样本 G", "substrate": 16, "rate": 7.62 },
    { "sample": "样本 H", "substrate": 24, "rate": 8.28 }
  ]
}

动画

一颗小球沿着轨道往返。点击「运行示例」开始,拖动滑杆改变周期;也可以随时停止。系统开启「减少动态效果」时,动画保持静止。

动画演示

点击「运行示例」查看动画。

查看示例源码
html
<style>
  .track { position:relative; height:100px; margin:30px 14px 24px; border-bottom:1px solid #bdc8b3; }
  .dot { position:absolute; bottom:-13px; width:26px; height:26px; background:#576f47; border-radius:50%; animation:travel var(--duration, 3s) ease-in-out infinite alternate; }
  .track::before,.track::after { position:absolute; bottom:-30px; color:#727d68; font-size:12px; }
  .track::before { content:'0'; left:0; }
  .track::after { content:'1'; right:0; }
  @keyframes travel { from { left:0; } to { left:calc(100% - 26px); } }
  label { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
  input { flex:1; min-width:100px; accent-color:#576f47; }
  output { min-width:3em; font-variant-numeric:tabular-nums; }
</style>
<div class="track" aria-label="往返运动的小球"><div class="dot"></div></div>
<label>单程时间 <input id="duration" aria-label="单程时间" type="range" min="1" max="6" step="0.5" value="3"><output id="duration-value">3.0 s</output></label>
<p style="color:#727d68;font-size:12px">相同的路径,不同的节奏。</p>
<script>
  const slider = document.getElementById('duration');
  slider.addEventListener('input', () => {
    document.documentElement.style.setProperty('--duration', slider.value + 's');
    document.getElementById('duration-value').textContent = Number(slider.value).toFixed(1) + ' s';
  });
</script>

Canvas 交互画布

拖动滑杆,观察振幅与频率如何改变一条波。点击画布内的「播放波形」,还可以让它随时间移动。

Canvas 交互画布
查看示例源码
html
<style>
  canvas { display:block; width:100%; height:220px; border-bottom:1px solid #cdd4c6; }
  .controls { display:grid; gap:10px; margin-top:18px; }
  label { display:grid; grid-template-columns:45px 1fr 50px; gap:12px; align-items:center; }
  input { min-width:0; width:100%; accent-color:#576f47; }
  output { font-variant-numeric:tabular-nums; }
  button { padding:5px 12px; border:1px solid #aab79e; border-radius:4px; background:transparent; color:#364b2b; }
  .caption { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:16px; font-size:12px; color:#6a7660; }
</style>
<canvas id="wave" role="img" aria-label="正弦波:振幅 0.8,频率 1.5">正弦波图像;下方可调整振幅和频率。</canvas>
<div class="controls">
  <label>振幅 <input id="amplitude" aria-label="振幅" type="range" min="0.1" max="1" step="0.1" value="0.8"><output id="amp-value">0.8</output></label>
  <label>频率 <input id="frequency" aria-label="频率" type="range" min="0.5" max="4" step="0.5" value="1.5"><output id="freq-value">1.5</output></label>
</div>
<div class="caption"><span>y = A sin(2πfx − t)</span><button id="play" type="button" aria-pressed="false">播放波形</button></div>
<script>
  const canvas = document.getElementById('wave');
  const context = canvas.getContext('2d');
  const amplitude = document.getElementById('amplitude');
  const frequency = document.getElementById('frequency');
  const play = document.getElementById('play');
  const motion = matchMedia('(prefers-reduced-motion: reduce)');
  let active = false, phase = 0, last = 0, request = 0;
  function draw() {
    const width = canvas.clientWidth, height = 220, dpr = Math.min(devicePixelRatio || 1, 2);
    canvas.width = Math.round(width * dpr); canvas.height = height * dpr;
    context.setTransform(dpr, 0, 0, dpr, 0, 0);
    context.strokeStyle = '#d8dece'; context.lineWidth = 1;
    for (let y = 30; y < height; y += 40) {
      context.beginPath(); context.moveTo(0, y); context.lineTo(width, y); context.stroke();
    }
    context.strokeStyle = '#aebba1';
    context.beginPath(); context.moveTo(0, height / 2); context.lineTo(width, height / 2); context.stroke();
    context.strokeStyle = '#526c41'; context.lineWidth = 2.5; context.beginPath();
    for (let x = 0; x <= width; x++) {
      const y = height / 2 - Number(amplitude.value) * 85 * Math.sin(2 * Math.PI * Number(frequency.value) * x / width - phase);
      x === 0 ? context.moveTo(x, y) : context.lineTo(x, y);
    }
    context.stroke();
    document.getElementById('amp-value').textContent = amplitude.value;
    document.getElementById('freq-value').textContent = frequency.value;
    canvas.setAttribute('aria-label', `正弦波:振幅 ${amplitude.value},频率 ${frequency.value}`);
  }
  function tick(time) {
    if (!active) return;
    if (last) phase += Math.min((time - last) / 1000, 0.05) * 2;
    last = time; draw(); request = requestAnimationFrame(tick);
  }
  function stop() {
    active = false; cancelAnimationFrame(request); last = 0;
    play.textContent = '播放波形'; play.setAttribute('aria-pressed', 'false');
  }
  play.addEventListener('click', () => {
    if (active) return stop();
    active = true; play.textContent = '暂停波形'; play.setAttribute('aria-pressed', 'true');
    request = requestAnimationFrame(tick);
  });
  [amplitude, frequency].forEach(input => input.addEventListener('input', draw));
  new ResizeObserver(draw).observe(canvas);
  document.addEventListener('visibilitychange', () => { if (document.hidden) stop(); });
  motion.addEventListener('change', () => { if (motion.matches) stop(); });
  draw();
</script>

写作时怎么使用

以上内容都由文章正文中的 Markdown 渲染,后续文章也可以直接使用:

  • 代码:使用带语言名的代码块,如 typescript、python、sql。
  • 数学和物理:行内用 $...$,独立公式用单独成行的 $$ 包围 LaTeX。
  • 化学和生物反应:在公式内使用 \ce{...}。
  • 静态表格:使用 Markdown 的竖线表格语法。
  • 交互表格:使用 interactive-table 代码块,内容为上面示例中的 JSON。
  • 动画、画布与其他交互:使用 animation、canvas 或 html-live 代码块,写入 HTML、CSS 和 JavaScript。

展开演示下方的「查看示例源码」即可查看和复制。交互示例在独立的沙盒中运行,使用内联代码和本地数据,不依赖外部脚本。