深夜提醒

现在是深夜,建议您注意休息,不要熬夜哦~

🏮 🏮 🏮

新年快乐

祝君万事如意心想事成!

share-image
中
ESC

How the Stats Overlay Visualization Feature Works

English 阅读中文版

While developing the blog theme, to boost the homepage’s visual impact and interactivity, we added a “Stats Overlay” to the Hero Splash (the hero-screen animation). This overlay displays the key data from the marathon track animation in real time (such as time, distance, pace and heart rate).

This article takes a deep dive into how this feature works, covering the HTML structure, the CSS styling and the JavaScript interaction logic.

1. Core Feature Overview

The Stats Overlay’s main job is to show the current workout data in sync while the map track animation plays. It has these characteristics:

  • Dynamic show/hide: it fades in automatically in step with the track animation’s progress.
  • Real-time updates: as each animation frame renders, it computes and shows the data for the current position in real time.
  • Interactivity: it supports collapse/expand and adapts to mobile display.

2. HTML Structure Design

The feature is embedded in layout.ejs as part of #hero-splash. Structurally it has two main parts: the summary data area (Always Visible) and the detailed chart area (Collapsible).

<!-- Stats Overlay -->
<div class="stats-overlay" id="stats-overlay">
<div class="stats-main-card">
<!-- Header: title and collapse button -->
<div class="stats-header">
<span class="stats-title">2024 桐庐半程马拉松</span>
<button class="stats-toggle-btn" id="stats-toggle">
<i class="fas fa-chevron-up"></i>
</button>
</div>

<div class="stats-content-wrapper" id="stats-content">
<!-- Core data area: time, distance, pace, cadence -->
<div class="stats-summary-section">
<!-- ... data-item DOM structure ... -->
</div>

<!-- Heart-rate display area -->
<div class="chart-block hr-block-card">
<!-- ... heart-rate visualization DOM ... -->
</div>

<!-- Detailed chart area: the collapsible part -->
<div class="stats-charts-section">
<canvas id="hr-chart"></canvas>
<canvas id="pace-chart"></canvas>
<canvas id="cadence-chart"></canvas>
</div>
</div>
</div>
</div>

3. CSS Styles and Transition Animation

The styles are defined in style.styl. To achieve a smooth reveal, we use a CSS Transition.

The key is the switch between the initial state and the .visible state of .stats-overlay:

/* Stats Overlay base styles */
.stats-overlay
position absolute
top 40px
right 40px
/* Glassmorphism background */
background rgba(10, 10, 10, 0.95)
backdrop-filter blur(20px)
/* Transition effect */
transition opacity 0.5s ease
opacity 1 /* Default state, controlled with JS */

/* Show/hide state class */
&.visible
opacity 1

Note: in practice you’d usually set the initial opacity to 0, then add the .visible class via JS to set it to 1, achieving the fade-in effect.

4. JavaScript Logic

The logic lives mainly in the <script> tag of layout.ejs; the core is the animate function and DOM manipulation.

4.1 The Show-Trigger Logic

In the marathon track animation’s render loop animate(timestamp), there’s a block of code dedicated to controlling the Overlay’s display:

// Animation loop function
function animate(timestamp) {
// ... compute progress and draw the track ...

// Force the Stats Overlay to appear
const statsOverlay = document.getElementById('stats-overlay');
// When the animation starts and the Overlay isn't shown yet, add the visible class
if (statsOverlay && !statsOverlay.classList.contains('visible')) {
statsOverlay.classList.add('visible');
}

// ... update the data ...

if (progress < 1) {
requestAnimationFrame(animate);
}
}

This logic ensures the data overlay only appears once the track animation actually starts, avoiding an abrupt look during initial page load.

4.2 Real-Time Data Updates

On every animation frame, JS computes, from the current animation progress (the ease variable), which data point should be shown:

// Compute the current data index from the progress
const currentIdx = Math.floor(ease * pathArr.length);

if (currentIdx < stats.length) {
const stat = stats[currentIdx];

// Update the DOM text
document.getElementById('stats-time').innerText = formatTime(stat.time);
document.getElementById('stats-dist').innerText = (ease * totalDist).toFixed(2);
// ... update other data ...

// Update the heart-rate color and zone indicator
const zone = getHeartRateZone(stat.hr);
const elHR = document.getElementById('stats-hr');
elHR.style.color = zone.color;
}

4.3 Overall Show/Hide Control (SessionStorage)

So as not to bother the user on later visits, we use sessionStorage to record whether the user has already seen the splash animation:

const heroSplash = document.getElementById('hero-splash');

// Check whether it's been visited already
if (sessionStorage.getItem('splashVisited')) {
if (heroSplash) heroSplash.style.display = 'none'; // Hide the entire Splash outright
return;
}

When the user scrolls or triggers an interaction, the hideSplash() function is called, storing the splashVisited flag in SessionStorage and hiding the entire Splash layer, including the Stats Overlay.

5. Summary

The Stats Overlay is a typical front-end visualization case that combines:

  1. DOM manipulation: dynamic style changes via class toggling.
  2. CSS3 animation: smooth visual transitions via transition.
  3. Data-driven views: real-time UI updates based on the timeline (RequestAnimationFrame).
  4. UX optimization: use SessionStorage to avoid repeated displays, respecting the user’s habits.

This approach delivers a cool visual effect while balancing performance and user experience—ideal for the personalized display of a personal blog.

文章作者:阿文
文章链接: https://www.awen.me/post/b80bf915.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客!
本文于 2026-01-27 14:00 发布,已超过半年(262天),请注意甄别内容是否已过期。

评论

0 条评论
😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️
加载中...

留言反馈

😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️