前言
Astro 的「零 JS」哲学很香,但组件开发还是有些地方需要注意。这篇聊聊开发中总结的一些经验。
一、样式作用域的坑
Astro 组件中的 <style> 默认是 scoped(作用域),只对当前组件生效。
<style> .nav-links { color: blue; }</style>编译后会变成:
.nav-links[data-astro-cid-xxx] { color: blue;}如果需要全局样式
<style is:global> .global-class { color: red; }</style>或者在 src/styles/global.css 中定义。
二、组件复用:Hero 的条件渲染
首页需要 Hero 全屏背景,博客页不需要。通过 props 控制:
---interface Props { hideHero?: boolean;}
const { hideHero = false } = Astro.props;---
{hideHero === false && ( <section class="hero"> <div class="hero-overlay"></div> <div class="hero-content"> <h1 class="hero-title"> <span id="typing-title"></span> <span class="typing-cursor">|</span> </h1> </div> </section>)}---// 首页import Header from '../components/Header.astro';---<Header /> <!-- 显示 Hero -->
// 博客页<Header hideHero={true} /> <!-- 隐藏 Hero -->三、打字机效果实现
const titleEl = document.getElementById('typing-title');if (titleEl) { const lines = ['安静且有趣的日子', '是大家最期盼的平淡']; let lineIndex = 0; let charIndex = 0;
function typeWriter() { if (lineIndex < lines.length) { const currentLine = lines[lineIndex]; charIndex++; currentText = lines.slice(0, lineIndex).join('\n') + '\n' + currentLine.substring(0, charIndex); titleEl.textContent = currentText;
if (charIndex < currentLine.length) { setTimeout(typeWriter, 120); } else { lineIndex++; charIndex = 0; if (lineIndex < lines.length) { setTimeout(typeWriter, 300); // 行间停顿更长 } } } }
setTimeout(typeWriter, 500);}光标闪烁用 CSS 动画:
.typing-cursor { display: inline-block; animation: blink 1s infinite;}
@keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; }}四、Header 滚动行为
滚动超过 Hero 一半时:
- 背景色变化(透明 → 白色)
- 向下滚动时隐藏 Header
- 向上滚动时显示 Header
let lastScroll = 0;const header = document.querySelector('.header-nav');const hero = document.querySelector('.hero');const threshold = hero ? hero.clientHeight * 0.5 : 0;
window.addEventListener('scroll', () => { const currentScroll = window.pageYOffset;
if (currentScroll > threshold) { header?.classList.add('scrolled');
if (currentScroll > lastScroll) { header?.classList.add('hidden'); // 向下滚,隐藏 } else { header?.classList.remove('hidden'); // 向上滚,显示 } } else { header?.classList.remove('hidden', 'scrolled'); }
lastScroll = currentScroll;}, { passive: true });五、主题切换
const themeToggle = document.querySelector('.theme-toggle');const html = document.documentElement;
// 读取保存的主题const savedTheme = localStorage.getItem('theme') || 'light';html.setAttribute('data-theme', savedTheme);
themeToggle?.addEventListener('click', () => { const current = html.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; html.setAttribute('data-theme', next); localStorage.setItem('theme', next);});配合 CSS 变量:
:root { --bg-primary: #ffffff; --text-primary: #1a1a1a;}
[data-theme="dark"] { --bg-primary: #1a1a1a; --text-primary: #ffffff;}总结
- 样式作用域:小样式放组件内,大样式放 global.css
- 条件渲染:通过 props 控制组件行为
- 零 JS 理念:只在需要交互的地方写 JS
- passive 监听:滚动事件加上
{ passive: true }提升性能
Astro 的开发体验还是很舒服的,特别是静态生成 + 组件化的组合。