This article serves as both a complete usage tutorial for matery theme interaction & visual features and an automated test arena.
Covers: code block enhancements, image lightbox, typewriter, page effects, simplified/traditional Chinese conversion, reading progress bar, back-to-top, reward popup, print styles.
── Code & Print ──
1. Code Block Enhancements
Purpose
Code blocks display a language label and provide copy/expand/collapse/fullscreen operations. Overly long code is automatically height-limited.
Configuration (theme _config.yml code block)
code:
# Code block language label (default TEXT, unrecognized languages display TEXT)
language:
enable: true
default: "TEXT"
# Copy button
copy_btn: true
# Expand/collapse button (for long code)
show_full: true
# Manual collapse button
shrink: true
# Maximum code block height (collapses above this value, string with units)
height_limit: "450px"Usage
Standard Markdown code blocks work (auto-enhanced):
```bash
# Bash example
echo "Hello Matery"
```Examples (multi-language)
# Bash: system operations
sudo apt update
docker compose up -d
systemctl status nginx# Python: data processing
def process(data):
"""Process data and return results"""
result = [x * 2 for x in data if x > 0]
return result
print(process([1, -2, 3, 4]))// JavaScript: async requests
async function fetchData(url) {
const response = await fetch(url);
const data = await response.json();
console.log("Data:", data);
}# YAML: config file
server:
host: 0.0.0.0
port: 8080
workers: 4# Python: long code example (exceeds 450px height threshold, triggers "expand" button)
# Simulates a simple blog post processing pipeline
import hashlib
import json
import re
from collections import Counter
from datetime import datetime
from pathlib import Path
def read_posts(directory):
"""Read all Markdown posts in a directory"""
posts = []
for path in Path(directory).glob("*.md"):
posts.append(path.read_text(encoding="utf-8"))
return posts
def extract_front_matter(content):
"""Extract Front-Matter metadata"""
match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL)
if not match:
return {}
meta = {}
for line in match.group(1).splitlines():
if ":" in line:
key, value = line.split(":", 1)
meta[key.strip()] = value.strip()
return meta
def analyze_posts(directory):
"""Main analysis flow: word frequency, summary generation, word count"""
all_words = Counter()
for content in read_posts(directory):
meta = extract_front_matter(content)
body = re.sub(r"^---\n.*?\n---\n", "", content, flags=re.DOTALL)
text = re.sub(r"[#*`>\[\]()]", "", body)
words = re.findall(r"[\u4e00-\u9fa5]|[a-zA-Z]+", text)
all_words.update(words)
word_count = len(words)
summary = " ".join(words[:20])
checksum = hashlib.md5(content.encode()).hexdigest()[:8]
print(f"{meta.get('title', 'untitled')} | {word_count} words | {checksum}")
print(f" Summary: {summary}...")
print(f"\nTotal unique words analyzed: {len(all_words)}")
return all_words
if __name__ == "__main__":
result = analyze_posts("content/posts")
top_words = result.most_common(10)
print("Top 10 frequent words:")
for word, count in top_words:
print(f" {word}: {count}")Implementation Effects
- Language label in the top-right corner (bash/python/js/yaml)
- Copy button (click to copy code)
- Long code shows an “expand” button
- Fullscreen button (view code on a large screen)
Test assertion: page contains
code-areamarker (at least 4 code blocks).
Expand Mechanism (code-area)
Code block height limiting and expansion are handled by the code-area component:
- Height limit:
code.height_limit(default"450px") – code blocks exceeding this height are automatically collapsed - Expand: a
scroll-down-barexpand bar appears at the bottom of the collapsed state; clicking it sets the containermaxHeighttoscrollHeightvia JS (full display, no more height limit) - Horizontal scrolling: code container uses
overflow-x: auto, overly wide lines scroll horizontally without wrapping (withcode.break: false) - Fullscreen:
code.show_expand: trueprovides fullscreen viewing
Test assertion: readmode use case (L11) verifies code is fully displayed after expansion.
Code Block Delegation Idempotency (codeWidget)
Encrypted wiki page code block buttons (collapse/copy) fail on click. Root cause: codeWidget() is called multiple times (page load + post-decryption refresh), accumulating click delegates on document; classList.toggle executes an even number of times, producing no net effect. Fix: named handler (_codeWidgetClickHandler) calls removeEventListener before add; Prism fallback callback gets _codeWidgetPrismRegistered idempotency flag.
Test assertion:
readmode-integration.test.js“codeWidget second invocation still works with single click”.
Wiki Page Line Numbers (prism line-numbers)
Wiki documentation pages (layout/wiki.ejs) render code blocks with prism and enable line-numbers, coexisting with the article page code block enhancements (toolbar).
Test assertion: TC-L15 G5 (
tools/tests/wiki-prism-integration.test.js, 4 assertions): tutorial page loads / prism-core / code blocks / line-numbers.
── UI Interaction & Motion ──
2. Image Lightbox (lightGallery)
Purpose
Click post images to enlarge for fullscreen browsing, with zoom/paging/download support.
Configuration (theme _config.yml)
# Post images are automatically wrapped as .img-item by articleInit
image_zoom:
enable: trueImage Size Injection Preprocessing (imgsize.js)
Before the lightbox opens, the theme script scripts/events/lib/imgsize.js injects real width/height into all <img> elements on the page (prevents CLS):
- Local images: synchronously reads file dimensions and injects directly
- Remote images: cache hit uses cached values; CDN→local mapping (
image_size_plugin.map_paths) reads local file; others fetched asynchronously - Persistent cache:
image_sizes_cache.json(30-day TTL, 5000-entry cap) - Regex compatibility: matches src in three forms (double-quoted / single-quoted / unquoted), compatible with hexo-minify’s quote stripping
This script runs before lightbox binding, ensuring articleInit() wrapping img → .img-item already has dimension attributes, so the lightbox opening doesn’t trigger reflow.
Usage
Regular Markdown images work (auto-enhanced):
Examples


Implementation Effects
- Image hover shadow + border
- Click opens lightbox (fullscreen + zoom + paging)
- Supports captions (alt/title display)
Test assertion: images are wrapped as
.img-item, clicking opens.lg-outer.
3. Typewriter (Typed)
Purpose
Banner subtitle/post title character-by-character typing animation.
Configuration (theme _config.yml)
subtitle:
enable: true
typed:
enable: true # Enable typewriter
loop: true # Loop rotation
showCursor: true # Show cursor
cursorChar: "_" # Cursor character
startDelay: 100 # Start delay (ms)
typeSpeed: 80 # Typing speed (ms/char)
backSpeed: 50 # Deleting speed (ms/char)
post:
typed:
enable: true # Post title typewriterImplementation Effects
- Banner subtitle types out character by character
- Post page title typing effect
- Cursor blinking + loop rotation
Test assertion: page contains
#typedor.typed-cursormarker.
4. Page Effects
Purpose
Decorative effects: page/background effects (cherry blossoms / water ripples / falling leaves / snowflakes / network / ribbons, etc.) and mouse/click effects (hearts / stars / fireworks, etc.), mutually exclusive single selection.
Configuration (theme _config.yml effects block)
effects:
enable: true # Master switch
desktop_only: true # Only inject on desktop
page: # Page/background effects (mutually exclusive, pick one)
default: off # off | random | specific id
available:
sakura: { label: Cherry Blossoms, lib: sakura }
ripples: { label: Water Ripples, lib: ripples }
leaf: { label: Falling Leaves, lib: leaf }
snowdown: { label: Snowfall, lib: snowdown }
snowflake: { label: Snowflakes, lib: snowflake }
buble: { label: Bubbles, lib: buble }
canvas_nest: { label: Network, lib: canvas_nest }
ribbon: { label: Ribbons, lib: ribbon }
ribbon_dynamic: { label: Dynamic Ribbons, lib: ribbon_dynamic }
mouse: # Mouse/click effects (mutually exclusive, pick one)
default: off
available:
clicklove: { label: Hearts, lib: clicklove }
popupText: { label: Popup Text, lib: popupText }
mouseStar: { label: Stars, lib: star }
fireworks: { label: Fireworks, lib: fireworks }At runtime, effects can be switched from the right-side floating panel (localStorage
matery.effect.page/matery.effect.mouse), no code changes needed.
Activation Conditions (desktopGate, when desktop_only is enabled)
- Desktop width >
BP.fun(1400px, falls back to 992 if BP is undefined) - localStorage
viewCount> 1 (prevents loading on first visit) - When
desktop_only: false, gate is skipped (loads on mobile too)
Implementation Effects
Page decoration animations, enhanced desktop experience, automatically disabled on mobile (performance).
Test assertion: effect scripts load according to gate (
windowWidth > BP.fun) + page/mouse mutual exclusion works.
── Internationalization & Architecture ──
Note: This partition is grouped for reading navigation. §6–§8 functionally belong to “UI Interaction”, “UI Interaction”, and “Code & Print” respectively.
5. Simplified/Traditional Chinese Conversion
Purpose
One-click switching between traditional and simplified Chinese on the page.
Configuration (theme _config.yml, migrated to preference block on 2026-08-16, unified site-wide language config on 2026-09-08)
preference: # Language preference aggregation (old footer.translate removed)
translate:
enable: true # Traditional/simplified conversion switchImplementation Effects
- Footer “Traditional/Simplified” toggle button
- Click converts all text globally (JS languageToggle, traditional/simplified character mapping)
- localStorage remembers user choice (
targetEncoding_<host>cookie)
Test assertion: page contains
#translateLinkor toggle button.
6. Reading Progress Bar / Back to Top
Purpose
Reading progress indicator + one-click back to top.
Configuration (theme _config.yml)
fun_features:
progressbar:
enable: true
height_px: 3
color: "#29d"
# Back to top button
backTop:
enable: trueProgress Bar Library Config (libs.js.scrollProgress)
The progress bar is built on the ScrollProgress library. The library path is configured in theme _config.yml under libs.js.scrollProgress (uses public CDN by default, can be changed to local /libs/scrollprogress/scrollProgress.min.js):
libs:
js:
scrollProgress: https://lib.baomitu.com/scrollprogress/3.0.2/scrollProgress.min.jsrefreshLayout integration: the progress bar instance registers as a layout callback (Matery.events.registerLayoutCallback). When content loads dynamically (decryption / pagination / pull-to-load), calling refreshLayout() destroys the old instance and recalculates (prevents listener stacking from causing incorrect progress calculations).
Test assertion: TC-L15 G6 (
tools/tests/core-events-integration.test.js, 2 assertions): layout callback registered >= 1 andrefreshLayout()does not throw.
Implementation Effects
- Progress bar at the top grows with scrolling
- Back-to-top button in the bottom-right corner (with smooth scrolling)
- Mobile button size adapts automatically
Test assertion: page contains
.progress-baror#backTop.
7. Reward Popup
Purpose
Post reward button + WeChat/Alipay QR code popup dialog.
Configuration (theme _config.yml post.reward)
post:
reward:
enable: true
title: "Thanks for reading! Consider tipping the author!"
wechat: /medias_webp/reward/wechat.webp
alipay: /medias_webp/reward/alipay.webpPost front-matter control
reward: true # Enable reward for this post (optional, follows global setting by default)Implementation Effects
- “Reward” button at the bottom of posts
- Click opens QR code dialog (Alipay/WeChat tabs)
- Close button + click overlay to dismiss
Test assertion: page contains
#reward+ reward button.
8. Print Styles
Purpose
Layout optimization when printing articles.
Configuration (theme _config.yml)
print:
enable: trueImplementation Effects
- Hides navigation/sidebar/footer during print
- Content centered with optimized spacing
- Links display full URLs
Test assertion: CSS contains
@media printrules.
── Media & Data Visualization ──
Note: This partition is grouped for reading navigation. §13–§14 belong to “UI Interaction”, §15/§18 belong to “Architecture”, §16–§17 belong to “UI Interaction”.
9. Music Player (APlayer)
Purpose
Embed a music player in posts/sidebar (MetingJS parsing + APlayer playback).
Configuration (theme _config.yml)
music:
enable: true
server: netease # netease/tencent/kugou/xiami/baidu
type: playlist # song/playlist/album/search/artist
id: 190133732 # NetEase playlist/song ID
fixed: false # true = fixed at bottom mode
autoplay: false
theme: '#42b983'Implementation Effects
- Music card (cover + playback controls + progress bar)
- Fixed bottom mode (
fixed) pinned to page bottom - Dark mode adaptation
Test assertion: page contains
aplayercontainer marker.
10. Video Player (DPlayer)
Purpose
Embed a video player in posts (supports HLS/live streams, etc.).
Configuration (theme _config.yml)
dplayer:
enable: trueExample (local video)
{% dplayer url=/medias_webp/video/demo.mp4 %}Implementation Effects
- Video player (play/pause/progress/volume)
- Supports danmaku (bullet comments)
Test assertion: page contains
dplayercontainer marker.
11. Bilibili Video Card
Purpose
Embed Bilibili videos (iframe or card).
Configuration (theme _config.yml)
bilibili:
enable: true
# iframeUrl: //player.bilibili.com/player.html?aid=xxx&bvid=xxxExample
{% bilibili BV1oa4y1L7mw %}Implementation Effects
Bilibili player embedding, adjustable dimensions.
Test assertion: page contains bilibili-related container markers.
12. ECharts
Purpose
Embed ECharts interactive charts in posts.
Configuration (theme _config.yml)
echarts:
enable: trueExample
{% echarts '100%' 400 %}
{
"title": {"text": "Sample Chart"},
"xAxis": {"type": "category", "data": ["A", "B", "C"]},
"series": [{"data": [10, 20, 30], "type": "bar"}]
}
{% endecharts %}Implementation Effects
ECharts interactive charts (bar/line/pie, etc.).
Dark/Light Theme Auto-Switching (unified on 2026-09-06 P11)
All ECharts charts within the theme (article tag / word cloud / radar / article stats 3 charts / calendar / music) use Matery.echarts.init(id, option) uniformly. They listen to the theme-changed event and automatically dispose + reinitialize (with new theme) + setOption on dark/light switching. The container CSS uses fixed height placeholders to prevent CLS.
Test assertion: page contains echarts container marker +
Matery.echartsis registered (tools/tests/wordcloud.test.jsL2g verifies dark/light switching).
13. AOS Card Enter Animation
Purpose: Enter animation for homepage waterfall cards when loading.
Implementation: New cards (from infinite scroll pull-to-load) use IntersectionObserver to watch .card elements. The .card-aos-enter animation class (fade-in-up, 500ms) is added only when the card enters the viewport. Does not apply to .article (Masonry absolute positioning, avoids overlap).
Configuration: AOS parameters are in themes/matery/source/js/boot.js (duration 700ms / delay 100ms)
Test assertion: after pull-to-load, new cards contain
.card-aos-enterand cards have no overlap (tools/tests/infinite-scroll-aos.test.js).
14. Floating Panel (right-floating)
Purpose
A “Preferences” panel opened by the gear button in the bottom-right corner: font scaling, language switching (simplified/traditional), display mode (light/auto/dark), page effects toggle, etc. Selections are saved in localStorage and persist across pages.
Configuration
The panel is a pure frontend component (layout/_partial/common/right-floating.ejs + floating-panel.styl) with no independent config toggle; each option inside the panel links to existing theme features (language via lang.translate, display mode via data-user-color-scheme, effects via the effects switch).
Usage
- Click the gear button (
.btn-floating) in the bottom-right corner to open/close the panel - Font scaling: A+ / A- step by 0.1, range 0.85-1.3, applies to the root
--font-scaleCSS variable; reset to default (1.0) with the reset button - Selections auto-save to localStorage (e.g. font
matery.font-scale, backgroundmatery.bg), persisting across refresh and page navigation
Implementation Effects
- Panel hidden by default, gear click expands it (display flex)
- Font scaling takes effect immediately and persists across pages
- One-click reset to defaults
Test assertion: TC-L14 (
tools/tests/floating-panel-integration.test.js, 8 assertions, part of release-test L11): panel hidden by default / gear opens / font scaling / reset to defaults, etc.
15. Event Bus (Matery.events)
Purpose
A unified event bus for inter-component communication within the theme, solving the problem of “components need reinitialization after dynamic content loading” – decryption, pagination, pull-to-load and other scenarios trigger corresponding refreshes. Each component registers a callback without coupling to others.
API (themes/matery/source/js/events.js)
| Method | Effect |
|---|---|
Matery.events.registerRefreshCallback(cb) | Register a full refresh callback (content re-rendering: MathJax/mermaid/Prism, etc.) |
Matery.events.registerLayoutCallback(cb) | Register a layout refresh callback (layout recalculation only: AOS/Masonry/progress bar) |
Matery.events.refresh() | Trigger all refresh callbacks |
Matery.events.refreshLayout() | Trigger all layout callbacks (lightweight, no content re-rendering) |
Usage
// After dynamic content loading, re-rendering is needed -> full refresh
Matery.events.registerRefreshCallback(function () { /* re-render */ });
// Only layout changes (resize / content height change) -> lightweight layout refresh
Matery.events.registerLayoutCallback(function () { /* re-layout */ });
Matery.events.refreshLayout();Implementation Effects
- refresh and refreshLayout are isolated: full refresh does not redundantly trigger layout; layout refresh does not re-render content
- After decryption, uniformly use refresh (see content tag article section 14 / layout article section 8)
Test assertion: TC-L15 G3 (
tools/tests/core-events-integration.test.js, 4 assertions): API exists / refresh triggers full refresh / refreshLayout triggers layout / isolation.
16. Banner Dark/Light Switching
Purpose
Banner background syncs with dark/light theme switching: after the theme-changed event dispatches, the body background switches between dark and light styles; background mode (image/video/off) is controlled by the panel preference matery.bg.
Configuration (theme _config.yml)
The background preference is stored in localStorage matery.bg, with values off | image | video (mutually exclusive, pick image or video); when unset, falls back to the config default bgDefault. Image/video background resources are configured in the corresponding sections of _config.yml.
Implementation Effects
- Switch dark/light theme ->
color-schema.jsdispatchestheme-changed-> banner/body background dark/light sync - Panel selects background mode -> persists across pages (localStorage
matery.bg)
Test assertion:
tools/tests/banner-bg.test.jsT5: body background dark/light changes (theme-changed dispatch).
17. Read Mode & Code Fullscreen (split into two independent features on 2026-09-06)
Purpose
- Read mode (read_mode): one-click fullscreen immersive reading of the post card, hiding other page elements
- Code fullscreen (code-fullscreen): fullscreen view of a single code block container, independent of read mode (the class is applied to the code container, not the card)
Usage
- Read mode: click the read mode button (
#read_mode) in the post info bar (post-info) → the post card.card-block-fullscreengoes fullscreen; click again to exit - Code fullscreen: click the fullscreen button in the code block toolbar (
.code-area .code-fullscreen) → that code container.code-area.code-block-fullscreengoes fullscreen
Implementation Effects
- Read mode enter: card
elastic 1sanimation + floating ball delayed fade-in (readmode-ball-fade .3s ease .75s both— the elastic transform creates a containing block that makes the fixed floating ball jump, the delayed fade-in solves it) - Read mode exit:
card-block-fullscreen-exitfade-out (scale .98 + opacity 0) → fullscreen class removed after 220ms (prevents the floating ball from jumping back) - Floating ball:
position: fixed; right: 1em; top: 70px; z-index: 10000when fullscreen (top 70 clears the navbar bottom 64; z above the navbar 997) - Code fullscreen:
.code-area.code-block-fullscreencontainer fullscreen,prefills it - scroll-down-bar expand:
pre.style.maxHeight = pre.scrollHeight + 'px'(auto-computes the full height, no inner scrollbar, page scrolls); collapsemaxHeight=''(restores--code-max-height450px)
Test assertion:
tools/tests/readmode-integration.test.js(L2f PASS=14): read mode enter/exit, floating ball, code fullscreen, full code display after expand.
18. JS Loading Layers & jQuery Removal (2026-09-05 P4/P5)
Purpose
Remove the site-wide jQuery dependency (rewritten with native DOM API) and load scripts in layers by first-screen priority to reduce first-screen blocking.
Configuration
libs.js.jquery/jqueryUI/jqueryBarragerare all commented out and disabled (P4/P5 site-wide jQuery removal complete;rippleseffect rewritten natively,contactpage removed)jqueryPjaxretained (standalone library, not global jQuery)- Layered loading: core scripts (materialize/masonry/imagesloaded/aos/events/plugins) unified
defer; analytics (umati/busuanzi)async; first-screen critical inline scripts injected synchronously (execute before all defer scripts)
Implementation Effect
- Page no longer requests global jQuery library
- defer scripts don’t block first-screen parsing; inline critical scripts execute before defer, ensuring dependency order
Test assertion: Page has no global jQuery requests; core scripts have
defer.
⚠️ Known Pitfalls
Pitfall 1: Double Section Numbering
Symptom: Headings render as “1. 1. Overview” (two numbers).
Cause: Theme CSS auto-adds numbers to h2–h6 by default; hand-written numbers (e.g. ## 1. Overview) stack on top.
Fix: Pages with hand-written numbering add closeAutoTocNum: true in front-matter; pages without hand-written numbering must not add this field.
This article already correctly sets
closeAutoTocNum: true.
Pitfall 5: Config Changes Require Clean Build
Symptom: Changed effects/subtitle.typed/image_zoom etc. configuration but the page didn’t update.
Cause: Hexo’s caching mechanism doesn’t auto-detect configuration changes.
Fix: After changing config, you must npm run clean && npm run build. Changing only article content (e.g. images/text) doesn’t require clean; incremental builds work normally.
Appendix: Interaction & Visual Feature Quick Reference
| Feature | Config Key | Key Parameters | Automated Assertion Marker |
|---|---|---|---|
| Code block | code | language/copy_btn/show_full | code-area |
| Lightbox | image_zoom + libs.js.lightgallery | enable | .img-item / .lg-outer |
| Typewriter | subtitle.typed / post.typed | loop/showCursor/speed | #typed |
| Effects | sakura etc. 10+ switches | desktop + viewCount > 2 gate | windowWidth > BP.fun |
| Trad/Simp | lang.translate.enable | aggregated config (old zh_default/footer.translate removed) | #translateLink |
| Progress bar | progressbar | height/color | .progress-bar |
| Back to top | backTop | enable | #backTop |
| Reward | post.reward | wechat/alipay images | #reward |
print | enable | @media print | |
| APlayer | music | server/type/id | aplayer |
| DPlayer | dplayer | url | dplayer |
| Bilibili | bilibili | iframeUrl | bilibili |
| ECharts | echarts | JSON config | echarts |
| AOS animation | aos (boot.js) | duration/delay | .card-aos-enter |
| Floating panel | right-floating (ejs) | font scaling/language/theme/effects | #floating-panel |
| Event bus | Matery.events | refresh/refreshLayout | Matery.events |
| Banner dark/light | matery.bg (localStorage) | off/image/video | body background |
| Wiki line numbers | prism line-numbers | wiki page code blocks | .line-numbers |
| Read mode | #read_mode (post-info button) | card fullscreen + floating ball | .card-block-fullscreen |
| Code fullscreen | .code-area .code-fullscreen | container fullscreen (independent of read mode) | .code-block-fullscreen |
| JS loading layers | libs.js (jquery commented out) | core defer / analytics async / first-screen inline sync | no global jQuery requests |
References
系列教程
Hexo 主题功能测试系列
- Hexo主题功能测试-布局与页面篇 —— 页面布局、导航、侧边栏、卡片等结构功能
- Hexo主题功能测试-交互视觉篇 —— 代码高亮、图片灯箱、动画、明暗切换等交互
- Hexo主题功能测试-内容tag篇 —— note / tabs / timeline / label / button 等 tag 插件
- Hexo主题功能测试-Markdown语法篇 —— Markdown 渲染与扩展语法
- Hexo主题功能测试-自动化测试篇 —— 主题功能的自动化测试方案

