Loading...

Loading...

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-area marker (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-bar expand bar appears at the bottom of the collapsed state; clicking it sets the container maxHeight to scrollHeight via JS (full display, no more height limit)
  • Horizontal scrolling: code container uses overflow-x: auto, overly wide lines scroll horizontally without wrapping (with code.break: false)
  • Fullscreen: code.show_expand: true provides 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: true

Image 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):

![Image description](/medias_webp/featureimages/1.webp)

Examples

Lightbox test image 1

Lightbox test image 2

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 typewriter

Implementation Effects

  • Banner subtitle types out character by character
  • Post page title typing effect
  • Cursor blinking + loop rotation

Test assertion: page contains #typed or .typed-cursor marker.


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 switch

Implementation 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 #translateLink or 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: true

Progress 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.js

refreshLayout 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 and refreshLayout() 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-bar or #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.webp

Post 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: true

Implementation Effects

  • Hides navigation/sidebar/footer during print
  • Content centered with optimized spacing
  • Links display full URLs

Test assertion: CSS contains @media print rules.


── 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 aplayer container marker.


10. Video Player (DPlayer)

Purpose

Embed a video player in posts (supports HLS/live streams, etc.).

Configuration (theme _config.yml)

dplayer:
  enable: true

Example (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 dplayer container 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=xxx

Example

{% 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: true

Example

{% 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.echarts is registered (tools/tests/wordcloud.test.js L2g 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-enter and 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-scale CSS variable; reset to default (1.0) with the reset button
  • Selections auto-save to localStorage (e.g. font matery.font-scale, background matery.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)

MethodEffect
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.js dispatches theme-changed -> banner/body background dark/light sync
  • Panel selects background mode -> persists across pages (localStorage matery.bg)

Test assertion: tools/tests/banner-bg.test.js T5: 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-fullscreen goes 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-fullscreen goes fullscreen

Implementation Effects

  • Read mode enter: card elastic 1s animation + 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-exit fade-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: 10000 when fullscreen (top 70 clears the navbar bottom 64; z above the navbar 997)
  • Code fullscreen: .code-area.code-block-fullscreen container fullscreen, pre fills it
  • scroll-down-bar expand: pre.style.maxHeight = pre.scrollHeight + 'px' (auto-computes the full height, no inner scrollbar, page scrolls); collapse maxHeight='' (restores --code-max-height 450px)

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 / jqueryBarrager are all commented out and disabled (P4/P5 site-wide jQuery removal complete; ripples effect rewritten natively, contact page removed)
  • jqueryPjax retained (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

FeatureConfig KeyKey ParametersAutomated Assertion Marker
Code blockcodelanguage/copy_btn/show_fullcode-area
Lightboximage_zoom + libs.js.lightgalleryenable.img-item / .lg-outer
Typewritersubtitle.typed / post.typedloop/showCursor/speed#typed
Effectssakura etc. 10+ switchesdesktop + viewCount > 2 gatewindowWidth > BP.fun
Trad/Simplang.translate.enableaggregated config (old zh_default/footer.translate removed)#translateLink
Progress barprogressbarheight/color.progress-bar
Back to topbackTopenable#backTop
Rewardpost.rewardwechat/alipay images#reward
Printprintenable@media print
APlayermusicserver/type/idaplayer
DPlayerdplayerurldplayer
BilibilibilibiliiframeUrlbilibili
EChartsechartsJSON configecharts
AOS animationaos (boot.js)duration/delay.card-aos-enter
Floating panelright-floating (ejs)font scaling/language/theme/effects#floating-panel
Event busMatery.eventsrefresh/refreshLayoutMatery.events
Banner dark/lightmatery.bg (localStorage)off/image/videobody background
Wiki line numbersprism line-numberswiki page code blocks.line-numbers
Read mode#read_mode (post-info button)card fullscreen + floating ball.card-block-fullscreen
Code fullscreen.code-area .code-fullscreencontainer fullscreen (independent of read mode).code-block-fullscreen
JS loading layerslibs.js (jquery commented out)core defer / analytics async / first-screen inline syncno global jQuery requests

References

系列教程

全部文章RSS订阅

Hexo 主题功能测试系列


Hexo Theme Feature Test - Interaction & Visual
Posted on
August 7, 2026
reprint policy. If reproduced, please indicate source
Support
Comments
Data Loading...
 Previous

Read More

Hexo Theme Feature Test - Markdown Syntax
Hexo Theme Feature Test - Markdown Syntax Hexo Theme Feature Test - Markdown Syntax
A complete Markdown syntax tutorial and test for the theme: full coverage of 16 markdown-it plugins (emoji/abbr/footnote
2026-08-08
Next 

Read More

Hexo Theme Feature Test - Content Tags
Hexo Theme Feature Test - Content Tags Hexo Theme Feature Test - Content Tags
Complete tutorial and test for matery theme content tag plugins: note callouts, timeline, tabs, labels, buttons, githubC
2026-08-07