feat: migrate and organize documentation and blog posts

refactor(blog): restructure blog post layouts and metadata

docs: add markdown migration guide and project rules update

style(global.css): update typography theme variables

chore: move temp_docs to appropriate blog post locations
This commit is contained in:
joyzhao
2025-06-19 20:24:09 +08:00
parent c064c8a1c5
commit f8173fd706
14 changed files with 1613 additions and 37 deletions

View File

@@ -4,17 +4,19 @@
## 1. 项目概述
`zhaoguiyang.site` 是一个现代化的个人作品集网站模板,具有动画和玻璃拟态效果。该项目旨在展示个人信息、工作经历、技能和项目。
`zhaoguiyang.site` 是一个现代化的个人作品集网站模板,具有动画和玻璃拟态效果。该项目旨在展示个人信息、工作经历、技能和项目,并包含完整的博客功能,支持多语言(英文和中文)
## 2. 技术栈和框架
- **主要框架**: [Astro](https://astro.build/) - 一个用于构建快速、内容驱动的网站的现代前端框架。它允许使用多种 UI 框架(如 React, Vue, Svelte 等)并支持服务器端渲染 (SSR) 和静态站点生成 (SSG)。
- **UI 框架**: [React](https://react.dev/) - 用于构建用户界面的 JavaScript 库。在本项目中,部分组件(如头部、各个内容区域)是使用 React 实现的,并通过 Astro 的集成功能嵌入到页面中。
- **样式**: [Tailwind CSS](https://tailwindcss.com/) - 一个实用工具优先的 CSS 框架,用于快速构建自定义用户界面。通过 `@tailwindcss/vite` 集成到 Astro 项目中。
- **样式**: [Tailwind CSS](https://tailwindcss.com/) - 一个实用工具优先的 CSS 框架,用于快速构建自定义用户界面。通过 `@tailwindcss/vite` 集成到 Astro 项目中。项目还使用了 `@tailwindcss/typography` 插件来美化博客文章内容。
- **动画**: [Framer Motion](https://www.framer.com/motion/) (`framer-motion`) - 一个用于 React 的生产级动画库,用于实现声明式的、基于物理的动画以及复杂的交互动效。在项目中,它被用于增强用户体验,例如在 <mcsymbol name="HeroSection" path="src/components/HeroSection.tsx" filename="HeroSection.tsx" type="function" startline="5"></mcsymbol> 等组件中实现元素的入场动画、悬停效果等。具体使用方式可以参考 <mcfile path="src/components/MotionWrapper.tsx" name="MotionWrapper.tsx"></mcfile> 组件,它是一个统一处理动画逻辑的封装。
- **图标**: [Lucide React](https://lucide.dev/) - 一个简单、美观的 SVG 图标库。
- **包管理器**: [pnpm](https://pnpm.io/) - 一个快速、磁盘空间高效的包管理器。
- **TypeScript**: 用于类型检查和提高代码质量。
- **国际化**: 使用自定义的国际化解决方案,通过 `src/i18n` 目录下的文件管理多语言内容。
- **UI 组件增强**: 使用 Radix UI 的 `@radix-ui/react-scroll-area``@radix-ui/react-slot` 组件来增强用户界面的可访问性和交互性。
## 3. 组件库和 UI
@@ -27,16 +29,29 @@
- `Footer.tsx`: 页脚。
- `LanguageSwitcher.tsx`: 语言切换组件。
- `ThemeToggle.tsx`: 主题切换组件 (位于 `src/components/ui/`)
- `AuthorCard.tsx`: 作者信息卡片,用于博客文章页面。
- `ShareButtons.tsx`: 社交媒体分享按钮组件,用于博客文章页面。
- `SkillsMarquee.tsx`: 技能标签滚动展示组件,使用 Framer Motion 实现无限滚动效果。
- **博客组件 (src/components/blog/)**: 专门用于博客功能的组件集合:
- `BlogList.astro`: 博客文章列表组件,支持分类和标签筛选。
- `CategoryCard.astro`: 博客分类卡片组件,用于展示和筛选文章分类。
- `TagCard.astro`: 博客标签卡片组件,用于展示和筛选文章标签。
- `PostMeta.astro`: 博客文章元数据组件,展示发布日期、阅读时间等信息。
- **布局组件 (src/components/layout/)**: 用于页面布局的组件:
- `BlogNavigation.astro`: 博客文章导航组件,用于在文章之间导航。
- `TableOfContents.astro`: 文章目录组件,自动生成文章内容的目录导航。
- **UI 组件 (src/components/ui/)**: 基于 Shadcn UI 风格,提供可复用的基础 UI 元素。
- `button.tsx`: 按钮组件。
- `card.tsx`: 卡片组件 (包含 `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`)。
- `glass-card.tsx`: 自定义玻璃拟态效果的卡片组件,使用 Framer Motion。
- `scroll-area.tsx`: 基于 Radix UI 的滚动区域组件,用于目录导航等场景。
- `Container.tsx`: 容器组件,用于统一页面内容的宽度和边距。
- **UI 辅助库**:
- `class-variance-authority`: 用于创建可变样式的组件。
- `clsx`: 用于有条件地组合类名。
- `tailwind-merge`: 用于合并 Tailwind CSS 类名,避免冲突。
- `tw-animate-css`: 用于集成 Animate.css 的 Tailwind CSS 插件。
- **Radix UI**: `@radix-ui/react-slot` 被用作依赖,用于构建可访问和可组合的 UI 组件。
- **Radix UI**: `@radix-ui/react-slot``@radix-ui/react-scroll-area` 被用作依赖,用于构建可访问和可组合的 UI 组件。
## 4. 目录结构
@@ -59,34 +74,59 @@
│ └── profile.jpg
├── src/
│ ├── components/ # React 组件目录
│ │ ├── ExperienceSection.tsx
│ │ ├── Footer.tsx
│ │ ├── GlassHeader.tsx
│ │ ├── HeroSection.tsx
│ │ ├── MotionWrapper.tsx # Framer Motion 包装组件
│ │ ├── ProjectsSection.tsx
│ │ ├── SkillsSection.tsx
│ │ ├── TimelineItem.tsx # 时间轴条目组件
│ │ ├── AuthorCard.tsx # 作者信息卡片组件
│ │ ├── Footer.tsx # 页脚组件
│ │ ├── GlassHeader.tsx # 玻璃拟态效果的导航栏
│ │ ├── LanguageSwitcher.tsx # 语言切换组件
│ │ ── ui/ # 通用 UI 组件 (基于 Shadcn UI 风格)
│ │ ├── button.tsx # 按钮组件
│ │ ├── card.tsx # 卡片组件
│ │ ├── glass-card.tsx # 玻璃拟态卡片组件
│ │ ── MotionWrapper.tsx # Framer Motion 包装组件
│ │ ├── ShareButtons.tsx # 社交媒体分享按钮组件
│ │ ├── SkillsMarquee.tsx # 技能标签滚动展示组件
│ │ ├── blog/ # 博客相关组件
│ │ │ ├── BlogList.astro # 博客文章列表组件
│ │ │ ├── CategoryCard.astro # 博客分类卡片组件
│ │ │ ├── PostMeta.astro # 博客文章元数据组件
│ │ │ └── TagCard.astro # 博客标签卡片组件
│ │ ├── layout/ # 布局相关组件
│ │ │ ├── BlogNavigation.astro # 博客文章导航组件
│ │ │ └── TableOfContents.astro # 文章目录组件
│ │ └── ui/ # 通用 UI 组件 (基于 Shadcn UI 风格)
│ │ ├── button.tsx # 按钮组件
│ │ ├── card.tsx # 卡片组件
│ │ ├── glass-card.tsx # 玻璃拟态卡片组件
│ │ ├── scroll-area.tsx # 滚动区域组件
│ │ └── theme-toggle.tsx # 主题切换组件
│ ├── i18n/ # 国际化相关文件
│ │ ├── ui.ts # 存储翻译文本
│ │ └── utils.ts # i18n 辅助函数
│ ├── layouts/ # Astro 布局组件
│ │ ── Layout.astro # 全局页面布局
│ │ ── AboutLayout.astro # 关于页面布局
│ │ ├── BlogLayout.astro # 博客页面布局
│ │ ├── BlogPostLayout.astro # 博客文章页面布局
│ │ ├── Layout.astro # 全局页面布局
│ │ └── TaxonomyPageLayout.astro # 分类/标签页面布局
│ ├── lib/ # 辅助函数和数据
│ │ ├── data.ts # 存储个人信息、经历、技能等静态数据
│ │ └── utils.ts # 通用工具函数
│ ├── pages/ # Astro 页面组件
│ │ ├── about.md # 关于页面 (Markdown)
│ │ ├── blog/ # 博客相关页面
│ │ │ ├── index.astro # 博客首页
│ │ │ ├── category/ # 分类页面
│ │ │ ├── tag/ # 标签页面
│ │ │ └── posts/ # 博客文章 (Markdown)
│ │ ├── index.astro # 网站主页 (英文)
│ │ ├── projects.astro # 项目页面
│ │ ├── services.md # 服务页面 (Markdown)
│ │ └── zh/ # 中文语言目录
│ │ ── index.astro # 网站主页 (中文)
└── styles/ # 全局样式
└── global.css
│ │ ── blog/ # 中文博客页面
│ └── index.astro # 网站主页 (中文)
├── styles/ # 全局样式
│ │ └── global.css
│ ├── types/ # TypeScript 类型定义
│ │ └── index.ts # 集中管理项目中的类型定义
│ └── utils/ # 工具函数
│ └── blog-utils.ts # 博客相关工具函数
├── temp_docs/ # 临时文档目录
└── tsconfig.json # TypeScript 配置文件
```
@@ -96,26 +136,48 @@
- **`package.json`**: 定义项目元数据、依赖项和 npm 脚本(如 `dev`, `build`, `preview`)。
- **`tsconfig.json`**: 配置 TypeScript 编译器选项,包括路径别名(`@/*` 指向 `./src/*`)和 JSX 设置。
- **`src/layouts/Layout.astro`**: 定义网站的全局 HTML 结构、头部信息(`<head>`)、全局样式和客户端脚本。所有页面都将使用此布局。
- **`src/layouts/BlogLayout.astro`**: 博客页面的布局组件,包含博客特有的样式和结构。
- **`src/layouts/BlogPostLayout.astro`**: 博客文章页面的布局组件,包含文章内容、目录导航、作者信息和分享按钮等。
- **`src/pages/index.astro`**: 网站的英文版入口页面。它导入并使用 `Layout.astro` 作为布局,并按顺序引入各个 React 内容区域组件。通过 `getLangFromUrl` 从 URL 判断当前语言并传递给布局和组件。`client:only="react"`指令表示这些 React 组件仅在客户端渲染。
- **`src/pages/zh/index.astro`**: 网站的中文版入口页面,结构与 `index.astro` 类似,但为中文内容服务,展示个人简介、工作经历、技能和项目。
- **`src/pages/blog/index.astro`**: 博客首页,展示所有博客文章、分类和标签。
- **`src/lib/data.ts`**: 存储网站展示的静态数据,如个人信息、工作经历、技能列表和项目信息。文件中的许多键值对应于 `src/i18n/ui.ts` 中的翻译键,以支持多语言。这使得内容易于更新和管理,而无需直接修改组件代码。
- **`src/i18n/ui.ts`**: 定义了支持的语言 (英语和简体中文) 以及所有需要翻译的文本内容的键值对。是实现网站国际化的核心文件。
- **`src/i18n/utils.ts`**: 包含国际化相关的辅助函数,如 `getLangFromUrl` (从 URL 获取当前语言)、`useTranslations` (提供一个 `t` 函数用于在组件中获取翻译文本) 和 `getLocalizedPath` (生成本地化路径)。
- **`src/utils/blog-utils.ts`**: 博客功能的工具函数集合,包括获取博客文章、按日期排序、按分类/标签筛选等功能。
- **`src/types/index.ts`**: 集中管理项目中的类型定义,包括博客文章、作者信息、组件 Props 等接口定义。
- **`src/components/*.tsx`**: 构成页面主要内容的 React 组件。它们从 `src/lib/data.ts` 获取数据并使用 Tailwind CSS 进行样式设置,使用 Framer Motion 实现动画效果。
- **`public/`**: 存放可以直接通过 URL 访问的静态文件,如 `favicon.svg``profile.jpg`
## 6. 数据流和状态管理
- **静态数据驱动**:项目主要依赖 `src/lib/data.ts` 中的静态数据来渲染内容。这意味着大部分内容在构建时就已经确定,无需复杂的运行时数据获取或状态管理。
- **无复杂状态管理**:由于项目性质(个人简历/作品集),没有引入 Redux、Zustand 或 React Context 等复杂的状态管理库。组件的状态管理主要通过 React 自身的 `useState``useEffects` Hook 在组件内部完成,或者通过 props 从父组件传递
- **博客内容管理**:博客文章使用 Markdown 文件存储在 `src/pages/blog/posts/``src/pages/zh/blog/posts/` 目录中,通过 Astro 的 `import.meta.glob` 功能在构建时读取和处理
- **无复杂状态管理**:由于项目性质(个人简历/作品集/博客),没有引入 Redux、Zustand 或 React Context 等复杂的状态管理库。组件的状态管理主要通过 React 自身的 `useState``useEffects` Hook 在组件内部完成,或者通过 props 从父组件传递。
- **国际化数据流**`src/i18n/ui.ts` 提供翻译文本,`src/i18n/utils.ts` 提供辅助函数来处理语言切换和文本获取,确保多语言内容正确显示。
## 7. 构建和部署
## 7. 博客功能
项目实现了完整的博客功能,主要特点包括:
- **Markdown 支持**:使用 Astro 的 Markdown 集成,支持在 Markdown 文件中编写博客文章。
- **前置元数据**:通过 Markdown 文件的 frontmatter 部分定义文章的标题、描述、发布日期、标签、分类等元数据。
- **多语言支持**:博客文章支持英文和中文两种语言,分别存储在不同的目录中。
- **分类和标签**:支持对文章进行分类和添加标签,并提供按分类/标签筛选文章的功能。
- **文章导航**:在文章页面提供上一篇/下一篇导航功能,方便读者浏览相关内容。
- **目录导航**:自动生成文章目录,支持点击跳转到对应章节。
- **作者信息**:在文章页面展示作者信息,包括头像、简介和社交媒体链接。
- **分享功能**:提供社交媒体分享按钮,方便读者分享文章。
- **响应式设计**:博客页面适配各种屏幕尺寸,提供良好的移动端体验。
## 8. 构建和部署
- **构建命令**:项目使用 `pnpm run build` 命令进行构建。此命令会触发 Astro 的构建过程,将项目编译为静态文件,输出到 `dist/` 目录。
- **开发环境**:使用 `pnpm run dev` 启动开发服务器,支持热重载和实时预览。
- **预览构建**:使用 `pnpm run preview` 预览构建后的静态文件。
- **部署**:由于是静态网站,构建产物可以直接部署到任何静态文件托管服务,如 Netlify, Vercel, GitHub Pages 等。
## 8. AI 代码重构和功能开发指南
## 9. AI 代码重构和功能开发指南
在进行代码重构或新功能开发时,请遵循以下原则:
@@ -129,15 +191,23 @@
- **可访问性**:确保所有 UI 元素都符合 WCAG 标准,特别是对于交互式组件。
- **代码质量**:遵循 TypeScript 最佳实践,编写清晰、可读、有注释的代码。
- **测试**:对于复杂逻辑或关键组件,考虑编写单元测试或集成测试。
- **博客功能扩展**:在扩展博客功能时,保持与现有的文件结构和数据流一致,确保新功能与多语言支持兼容。
## 9. 潜在的改进和扩展方向
## 10. 潜在的改进和扩展方向
- **CMS 集成**:将静态数据(如项目、博客文章)迁移到 CMS如 Contentful, Sanity, Strapi实现更灵活的内容管理。
- **博客功能**添加一个简单的博客模块,用于发布文章。
- **博客功能增强**
- 添加评论系统(如 Disqus, Giscus
- 实现文章搜索功能
- 添加相关文章推荐
- 支持更多的内容格式(如代码高亮、数学公式)
- **国际化完善**:除了文本,考虑图片、日期格式等内容的国际化。
- **性能监控**:集成 Web Vitals 监控,持续跟踪网站性能。
- **SEO 优化**:进一步优化元标签、结构化数据等,提升搜索引擎排名。
- **无障碍优化**:深入研究 ARIA 属性和键盘导航,提升无障碍体验。
- **动画库扩展**:探索 Framer Motion 更多高级特性,或集成其他动画库以实现更丰富的视觉效果。
- **组件库扩展**:基于 Radix UI 或其他无头 UI 库构建更多可复用的组件。
- **测试框架引入**:引入 Vitest 或 React Testing Library 等测试框架,提高代码质量和可维护性。
- **测试框架引入**:引入 Vitest 或 React Testing Library 等测试框架,提高代码质量和可维护性。
- **交互式项目展示**:为项目展示部分添加更多交互元素,如项目详情模态框、项目筛选等。
- **暗色模式优化**:进一步完善暗色模式的视觉效果和用户体验。
- **性能优化**:实现图片懒加载、代码分割等技术,提升页面加载速度。

View File

@@ -0,0 +1,206 @@
# Markdown博客文章迁移指南
本文档规定了将Markdown文档迁移到`zhaoguiyang.site`博客系统的标准流程和必要步骤。所有迁移工作必须严格按照本指南执行。
## 1. 博客文章结构
`zhaoguiyang.site`项目中,博客文章按照语言分别存储在以下目录:
- 英文文章:`/src/pages/blog/posts/`
- 中文文章:`/src/pages/zh/blog/posts/`
## 2. 文件命名规范
为了保持一致性和良好的URL结构请遵循以下命名规范
- 使用小写字母
- 单词之间使用连字符(`-`)分隔
- 文件名应简洁地反映文章内容
- 使用`.md`扩展名
示例:`react-performance-optimization.md`
## 3. 前置元数据Frontmatter要求
每篇博客文章必须在文件顶部包含YAML格式的前置元数据用三个连字符`---`)包围。
### 必需属性
```yaml
---
title: "文章标题" # 文章标题(必需)
description: "文章描述" # 文章简短描述或摘要(必需)
date: "2023-06-15" # 发布日期格式YYYY-MM-DD必需
image: "图片URL" # 文章封面图片URL必需
tags: ["标签1", "标签2"] # 文章标签(必需)
tagId: ["tag1", "tag2"] # 标签唯一标识符(必需)
category: "文章分类" # 文章分类(必需)
categoryId: "category-id" # 分类唯一标识符(必需)
readTime: "5 min read" # 阅读时间(必需)
---
```
### 前置元数据示例
```yaml
---
title: "React性能优化最佳实践"
description: "本文探讨了提升React应用性能的多种策略和技术包括组件优化、状态管理和渲染优化。"
date: "2023-06-15"
image: "https://example.com/images/react-performance.jpg"
tags: ["React", "性能优化", "前端开发"]
tagId: ["react", "performance", "frontend"]
category: "前端开发"
categoryId: "frontend"
readTime: "5 min read"
---
```
### 属性说明
| 属性 | 类型 | 描述 | 是否必需 |
|------|------|------|----------|
| `title` | 字符串 | 文章标题 | 是 |
| `description` | 字符串 | 文章描述/摘要 | 是 |
| `date` | 字符串 | 发布日期YYYY-MM-DD格式 | 是 |
| `image` | 字符串 | 文章封面图片URL | 是 |
| `tags` | 字符串数组 | 文章标签 | 是 |
| `tagId` | 字符串数组 | 标签唯一标识符(用于多语言环境) | 是 |
| `category` | 字符串 | 文章分类 | 是 |
| `categoryId` | 字符串 | 分类唯一标识符(用于多语言环境) | 是 |
| `readTime` | 字符串 | 阅读时间(如"5 min read" | 是 |
## 4. 迁移步骤
### 4.1 准备Markdown文件
1. 检查并确认Markdown文件的格式和内容符合项目规范
2. 文件必须使用UTF-8编码
3. 必须移除所有不兼容的HTML标签和特殊格式
### 4.2 添加前置元数据
1. 在文件顶部添加前置元数据区域(用`---`包围)
2. 填写所有必需属性(`title``description``date``image``tags``tagId``category``categoryId``readTime`
3. 确保所有属性都按照规定格式填写完整
### 4.3 图片处理
文章中的图片必须按照以下两种方式之一进行处理:
#### 方式一:使用外部图片链接
必须在Markdown中使用完整的URL
```markdown
![图片描述](https://example.com/path/to/image.jpg)
```
#### 方式二:使用本地图片
1. 必须将图片文件放在`public/images/blog/`目录下
2. 必须在Markdown中使用以下格式引用图片
```markdown
![图片描述](/images/blog/image.jpg)
```
### 4.4 放置文件
必须根据文章语言将处理好的Markdown文件放在指定目录
- 英文文章:必须放在`/src/pages/blog/posts/your-article-name.md`
- 中文文章:必须放在`/src/pages/zh/blog/posts/your-article-name.md`
### 4.5 验证
1. 必须通过以下命令启动开发服务器:`pnpm run dev`
2. 必须访问博客页面,确认文章显示正常
3. 必须检查文章内容、元数据、图片和格式是否完全符合规范
## 5. Markdown语法支持
本博客系统仅支持以下Markdown语法
### 5.1 基本语法
- 标题:必须使用`#``##``###`等标记,一级标题`#`仅用于文章标题
- 强调:必须使用`*斜体*`表示斜体,`**粗体**`表示粗体
- 列表:必须使用`-``1.`分别表示无序和有序列表
- 链接:必须使用`[链接文本](URL)`格式
- 图片:必须使用`![替代文本](图片URL)`格式
- 引用:必须使用`>`标记
- 代码:行内代码必须使用单反引号`` ` ``包围,代码块必须使用三反引号包围
### 5.2 代码块
必须使用三个反引号(```)包围代码,并明确指定语言以启用语法高亮:
````markdown
```javascript
function greeting() {
console.log("Hello, world!");
}
```
````
### 5.3 表格
表格必须使用以下格式:
```markdown
| 列1 | 列2 | 列3 |
|-----|-----|-----|
| 内容1 | 内容2 | 内容3 |
| 内容4 | 内容5 | 内容6 |
```
## 6. 多语言内容管理
本项目必须同时提供英文和中文两个版本的文章,遵循以下规则:
1. 创建两个版本的Markdown文件内容对应但语言不同
2. 两个文件必须使用完全相同的`tagId`和`categoryId`值
3. 两个文件必须使用相同的文件名,但放置在不同的语言目录中
4. 英文版文章必须放在`/src/pages/blog/posts/`目录
5. 中文版文章必须放在`/src/pages/zh/blog/posts/`目录
文章对应示例:
- 英文版:`/src/pages/blog/posts/react-performance-tips.md`
- 中文版:`/src/pages/zh/blog/posts/react-performance-tips.md`
两个版本的文章必须保持同步更新,确保内容的一致性。
### 6.1 文档翻译规则
处理文档翻译时必须遵循以下规则:
1. 如果只有中文版文档,必须将其翻译为专业的英文版
2. 如果只有英文版文档,必须将其翻译为专业的中文版
3. 如果同时存在中英文两个版本,无需翻译,直接将文件放置到对应的语言目录中
4. 对于必需的前置元数据属性:
- 如果原始Markdown文件中已存在该属性必须直接使用原有内容
- 如果原始Markdown文件中缺少某些必需属性必须根据文档内容自动生成合适的属性值
- 生成的属性必须与文章内容高度相关,不得使用通用或无关的内容
## 7. 注意事项
- **图片大小**必须优化所有图片大小图片文件大小不得超过500KB
- **内容格式**必须使用正确的标题层级从h2开始和段落分隔
- **代码示例**:所有代码示例必须使用正确的语法高亮标记
- **内部链接**:必须使用相对路径链接到网站内的其他页面
- **外部链接**必须对所有外部链接使用完整URL并添加`target="_blank"`属性
## 8. 故障排除
迁移过程中出现问题时,必须按以下顺序检查:
1. 前置元数据格式必须完全符合YAML语法规范
2. 文件编码必须为UTF-8不允许使用其他编码
3. 文件路径和名称必须完全符合命名规范
4. Markdown语法必须正确无误
---
本指南是将Markdown文档迁移到`zhaoguiyang.site`博客系统的标准流程。所有迁移工作必须严格遵循本文档的规定执行。如有任何问题,请联系项目维护者。

View File

@@ -50,6 +50,17 @@ const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : unde
<!-- Blog post header -->
<header class="mb-10">
<PostMeta
lang={lang}
publishDate={finalPublishDate}
readingTime={finalReadingTime}
tags={tags}
tagId={tagId}
category={category}
categoryId={categoryId}
className="justify-start mb-4"
/>
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground mb-6 leading-tight tracking-tight">
{title}
</h1>
@@ -59,21 +70,11 @@ const finalReadingTime = readTime ? parseInt(readTime.replace(/\D/g, '')) : unde
{description}
</p>
)}
<PostMeta
lang={lang}
publishDate={finalPublishDate}
readingTime={finalReadingTime}
tags={tags}
tagId={tagId}
category={category}
categoryId={categoryId}
className="justify-start"
/>
</header>
<!-- Blog post content with typography styles -->
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-2xl sm:prose-h1:text-3xl prose-h2:text-xl sm:prose-h2:text-2xl prose-h3:text-lg sm:prose-h3:text-xl prose-h4:text-base sm:prose-h4:text-lg prose-p:text-base sm:prose-p:text-lg prose-p:leading-relaxed prose-p:mb-6 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-foreground prose-code:text-sm prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-muted prose-pre:border prose-pre:text-sm prose-blockquote:border-l-primary prose-blockquote:bg-muted/30 prose-blockquote:text-base prose-li:text-base sm:prose-li:text-lg prose-li:leading-relaxed">
<article class="prose prose-lg dark:prose-invert max-w-none prose-headings:scroll-mt-24 prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-2xl sm:prose-h1:text-3xl prose-h2:text-xl sm:prose-h2:text-2xl prose-h3:text-lg sm:prose-h3:text-xl prose-h4:text-base sm:prose-h4:text-lg prose-p:text-base sm:prose-p:text-lg prose-p:leading-relaxed prose-p:mb-6 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-foreground prose-code:text-sm prose-code:px-2 prose-code:py-1 prose-code:rounded prose-pre:bg-muted prose-pre:border prose-pre:text-sm prose-blockquote:border-l-primary prose-blockquote:bg-muted/30 prose-blockquote:text-base prose-li:text-base sm:prose-li:text-lg prose-li:leading-relaxed
prose-code:before:content-none prose-code:after:content-none">
<slot />
</article>

View File

@@ -0,0 +1,100 @@
---
layout: "@/layouts/BlogPostLayout.astro"
title: "浏览器的运行原理"
description: "本文详细介绍了浏览器渲染页面的七个阶段HTML解析、样式计算、布局、分层、绘制、分块、光栅化以及reflow回流、repaint重绘的概念和transform效率高的原因。"
date: "2023-08-10"
image: "https://images.unsplash.com/photo-1510511459019-5dda7724fd87?q=80&w=1470&auto=format&fit=crop"
tags: ["浏览器", "前端", "渲染原理"]
tagId: ["browser", "frontend", "rendering"]
category: "前端开发"
categoryId: "frontend"
readTime: "7 min read"
---
浏览器渲染页面的过程可以分为七个阶段:
### 1. HTML解析
浏览器接收到服务器响应的HTML文档后会首先解析HTML文档构建DOM树。DOM树是由DOM元素及属性节点组成的树的根是document对象。
### 2. 样式计算
浏览器会解析CSS文件和元素上的inline样式计算出DOM节点的样式。这个过程包括
- 把CSS转换为浏览器能够理解的结构
- 计算出DOM节点的具体样式
### 3. 布局
布局阶段会计算出DOM树中可见元素的几何位置这个过程叫做布局或重排。
### 4. 分层
浏览器会根据布局后的DOM树将其分为多个图层。这个过程主要是为了处理页面中的复杂效果如3D变换、页面滚动等。
### 5. 绘制
在分层的基础上,浏览器会为每个图层生成绘制列表,并将其提交到合成线程。
### 6. 分块
合成线程会将图层划分为图块这些图块的大小通常是256x256或者512x512。
### 7. 光栅化
合成线程会按照视口附近的图块来优先生成位图,这个过程叫做光栅化。光栅化过程中,会将图块转换为位图。
## 回流Reflow和重绘Repaint
### 回流Reflow
当我们对DOM的修改引发了DOM几何尺寸的变化比如修改元素的宽、高或隐藏元素等浏览器需要重新计算元素的几何属性然后再将计算的结果绘制出来。这个过程就是回流也叫重排
引起回流的操作包括:
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的DOM元素
- 激活CSS伪类例如:hover
- 查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- scrollIntoView()、scrollIntoViewIfNeeded()
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
### 重绘Repaint
当我们对DOM的修改导致了样式的变化、但未影响其几何属性比如修改了颜色或背景色浏览器不需要重新计算元素的几何属性、直接为该元素绘制新的样式。这个过程叫做重绘。
相比回流,重绘的性能开销较小,因为重绘只是重新绘制元素的外观,而不需要重新计算元素的位置。
## 为什么transform效率高
transform是通过创建一个新的复合图层来实现的这个复合图层会单独进行绘制然后再与其他图层进行合成。这样当我们使用transform进行变换时只需要重新绘制这个复合图层而不需要重新绘制整个页面。
使用transform可以避免回流和重绘因为它不会影响到DOM的布局只会影响到最终的渲染结果。这就是为什么使用transform进行动画会比直接修改元素的top、left等属性效率高的原因。
## 优化策略
为了减少回流和重绘,我们可以采取以下策略:
1. **批量修改DOM**使用DocumentFragment或者先将元素设为display: none然后进行多次修改最后再显示。
2. **避免频繁读取会引发回流/重绘的属性**:如果必须多次读取,就将值缓存起来。
3. **使用transform和opacity来实现动画效果**:这两个属性不会触发回流。
4. **使用will-change属性**:这个属性可以提前告知浏览器元素将要发生的变化,浏览器可以提前做好优化工作。
5. **使用绝对定位或固定定位**:绝对定位和固定定位的元素发生变化时,只会影响自身,不会影响其他元素。
通过理解浏览器的渲染原理和优化策略,我们可以编写出更高效的前端代码,提升用户体验。

View File

@@ -0,0 +1,129 @@
---
layout: "@/layouts/BlogPostLayout.astro"
title: "Docusaurus v3中使用Tailwind CSS的样式"
description: "本文详细介绍了如何在Docusaurus v3中集成和配置Tailwind CSS包括安装依赖、配置tailwind.config.js、创建tailwind.css和postcss.config.js等步骤。"
date: "2023-11-05"
image: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?q=80&w=1470&auto=format&fit=crop"
tags: ["Docusaurus", "TailwindCSS", "前端框架"]
tagId: ["docusaurus", "tailwindcss", "frontend-framework"]
category: "前端开发"
categoryId: "frontend"
readTime: "4 min read"
---
## 安装相关依赖
```bash
npm install tailwindcss
```
## 配置tailwind.config.js
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx,md,mdx}",
"./docs/**/*.{md,mdx}",
],
theme: {
screens: {
xs: '480px',
sm: '576px',
md: '768px',
lg: '998px',
xl: '1200px',
'2xl': '1400px',
},
extend: {
fontFamily: {
sans: 'var(--ifm-font-family-base)',
firacode: 'var(--font-family-firacode)',
kaiti: 'var(--font-family-kaiti)',
},
colors: {
'font-color': 'var(--ifm-font-color-base)',
'link-color': 'var(--ifm-link-color)',
'link-hover-color': 'var(--ifm-link-hover-color)',
primary: 'var(--ifm-color-primary)',
'primary-light': 'var(--ifm-color-primary-light)',
'primary-lighter': 'var(--ifm-color-primary-lighter)',
'primary-lightest': 'var(--ifm-color-primary-lightest)',
'primary-dark': 'var(--ifm-color-primary-dark)',
'primary-darker': 'var(--ifm-color-primary-darker)',
'primary-darkest': 'var(--ifm-color-primary-darkest)',
},
boxShadow: {
nysm: '0 0 2px 0 rgb(0 0 0 / 0.05)',
ny: '0 0 3px 0 rgb(0 0 0 / 0.1), 0 0 2px - 1px rgb(0 0 0 / 0.1)',
nymd: '0 0 6px -1px rgb(0 0 0 / 0.1), 0 0 4px -2px rgb(0 0 0 / 0.1)',
nylg: '0 0 15px -3px rgb(0 0 0 / 0.1), 0 0 6px -4px rgb(0 0 0 / 0.1)',
spread: '0 5px 40px rgb(0 0 0 / 0.1)',
},
},
},
plugins: [],
darkMode: ["class", '[data-theme="dark"]'],
corePlugins: {
preflight: false,
},
blocklist: ["container"],
}
```
## 创建tailwind.css
`src/css`目录下创建`tailwind.css`文件,并写入以下内容:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
**注意创建完成后是需要在custom.css中导入tailwind.css的否则tailwind样式不会生效**
## 创建postcss.config.js
在项目根目录下创建`postcss.config.js`文件,并写入以下内容:
```js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
```
## 在custom.css中导入tailwind.css
找到项目中的`src/css/custom.css`文件,在文件顶部添加以下内容:
```css
@import './tailwind.css';
```
## 使用Tailwind CSS
现在你可以在项目中使用Tailwind CSS的样式了。例如
```jsx
<div className="flex flex-col items-center justify-center p-4 bg-gray-100 dark:bg-gray-800 rounded-lg shadow-md">
<h2 className="text-2xl font-bold text-primary mb-2">Hello Tailwind CSS</h2>
<p className="text-gray-700 dark:text-gray-300">This is a paragraph styled with Tailwind CSS</p>
</div>
```
## 注意事项
1. 由于Docusaurus已经有自己的CSS样式所以我们在配置Tailwind CSS时禁用了`preflight`,这样可以避免样式冲突。
2. 我们使用了`darkMode: ["class", '[data-theme="dark"]']`配置这样可以让Tailwind CSS的暗黑模式与Docusaurus的暗黑模式保持一致。
3. 我们在`theme.extend.colors`中添加了一些变量这些变量使用了Docusaurus的CSS变量这样可以让Tailwind CSS的颜色与Docusaurus的主题颜色保持一致。
4. 我们在`blocklist`中添加了`container`这是因为Docusaurus已经有自己的容器样式我们不希望Tailwind CSS的容器样式覆盖它。
## 总结
通过以上步骤我们成功地在Docusaurus v3中集成了Tailwind CSS。现在我们可以使用Tailwind CSS的所有功能同时保持与Docusaurus主题的一致性。
希望这篇文章对你有所帮助!如果你有任何问题,欢迎在评论区留言。

View File

@@ -0,0 +1,152 @@
---
layout: "@/layouts/BlogPostLayout.astro"
title: "Git提交规范"
description: "本文详细介绍了Git提交规范包括Header、Body和Footer三个部分的具体内容和使用规则帮助团队成员更好地理解和遵循统一的提交标准。"
date: "2023-10-20"
image: "https://images.unsplash.com/photo-1618401471353-b98afee0b2eb?q=80&w=1476&auto=format&fit=crop"
tags: ["Git", "规范", "版本控制"]
tagId: ["git", "convention", "version-control"]
category: "开发工具"
categoryId: "dev-tools"
readTime: "5 min read"
---
### 为什么需要提交规范
在团队协作中如果Git的提交说明精准在后期协作以及Bug处理时会变得有据可查项目的开发可以根据规范的提交说明快速生成开发日志从而方便开发者或用户追踪项目的开发信息和功能特性。
### 提交说明的结构
提交说明包含三个部分Header、Body 和 Footer。
```
<Header>
<Body>
<Footer>
```
#### Header
Header部分包含三个字段type必需、scope可选和subject必需
```
<type>(<scope>): <subject>
```
##### type
type用于说明commit的类别只允许使用下面7个标识
- **feat**新功能feature
- **fix**修补bug
- **docs**文档documentation
- **style** 格式(不影响代码运行的变动)
- **refactor**重构即不是新增功能也不是修改bug的代码变动
- **test**:增加测试
- **chore**:构建过程或辅助工具的变动
如果type为feat和fix则该commit将肯定出现在Change log之中。其他情况docs、chore、style、refactor、test由你决定要不要放入Change log建议是不要。
##### scope
scope用于说明commit影响的范围比如数据层、控制层、视图层等等视项目不同而不同。
##### subject
subject是commit目的的简短描述不超过50个字符。
- 以动词开头使用第一人称现在时比如change而不是changed或changes
- 第一个字母小写
- 结尾不加句号(.
#### Body
Body部分是对本次commit的详细描述可以分成多行。
有两个注意点:
- 使用第一人称现在时比如使用change而不是changed或changes
- 应该说明代码变动的动机,以及与以前行为的对比
#### Footer
Footer部分只用于两种情况
##### 不兼容变动
如果当前代码与上一个版本不兼容则Footer部分以BREAKING CHANGE开头后面是对变动的描述、以及变动理由和迁移方法。
```
BREAKING CHANGE: isolate scope bindings definition has changed.
To migrate the code follow the example below:
Before:
scope: {
myAttr: 'attribute',
}
After:
scope: {
myAttr: '@',
}
The removed `inject` wasn't generaly useful for directives so there should be no code using it.
```
##### 关闭Issue
如果当前commit针对某个issue那么可以在Footer部分关闭这个issue。
```
Closes #234
```
也可以一次关闭多个issue。
```
Closes #123, #245, #992
```
### 示例
```
feat(browser): onUrlChange event (popstate/hashchange/polling)
Added new event to browser:
- forward popstate event if available
- forward hashchange event if popstate not available
- do polling when neither popstate nor hashchange available
Closes #123, #305, #392
```
```
fix(release): need to depend on latest rxjs and zone.js
The version in our package.json gets copied to the one we publish, and users need the latest of these.
```
```
docs(changelog): update change log to beta.5
```
```
style(router): fix linting errors
```
```
refactor(core): rename onInit to ngOnInit
```
```
chore(deps): bump rxjs from 5.0.0-beta.2 to 5.0.0-beta.6
```
### 总结
规范的Git提交说明可以帮助团队成员更好地理解和遵循统一的提交标准提高团队协作效率也为后期的项目维护提供了便利。

View File

@@ -0,0 +1,104 @@
---
layout: "@/layouts/BlogPostLayout.astro"
title: "利用Gitea actions创建项目部署的自动化流程"
description: "本文以部署一个前端项目为示例手把手教你如何利用Gitea actions创建项目部署的自动化流程对于不想使用Jenkins的小伙伴来说Gitea actions是一个不错的CI/CD解决方案。"
date: "2023-12-15"
image: "https://images.unsplash.com/photo-1607799279861-4dd421887fb3?q=80&w=1470&auto=format&fit=crop"
tags: ["Gitea", "Actions", "CI/CD", "自动化部署"]
tagId: ["gitea", "actions", "cicd", "automation-deployment"]
category: "DevOps"
categoryId: "devops"
readTime: "10 min read"
---
本文以部署一个前端项目为示例将手把手教你如何利用Gitea actions创建项目部署的自动化流程对于不想使用Jenkins的小伙伴来说Gitea actions是一个不错的`CI/CD`解决方案。
## 前言
在我们开发完一个项目后需要将项目部署到服务器上以便于用户可以访问。但是每次部署都需要手动操作这会浪费我们的时间和精力。因此我们可以利用Gitea actions创建一个自动化流程以便于我们快速部署项目。
## 什么是Gitea actions
Gitea actions是Gitea的一个功能它允许我们创建自动化流程以便于我们快速部署项目。Gitea actions可以执行各种任务例如构建、测试、部署等。Gitea actions可以使用各种编程语言和工具例如Python、Java、Docker等。并且Gitea actions在配置上尽量与GitHub actions的配置兼容因此如果你已经熟悉了GitHub actions那么你也可以轻松地使用Gitea actions。
详见官方文档:[Gitea actions](https://docs.gitea.com/zh-cn/usage/actions/overview)
## 准备工作
在我们开始创建此流程前,首先你需要准备以下资料:
1. 一台服务器,最好拥有root权限会安装一些工具
2. 一个已部署的Gitea仓库,版本至少需要>=`v1.19`,官方强烈建议使用`v1.20`或更高版本
3. docker环境非必须, 用在部署runner的主机上
> ps: 作者这里服务器准备的是:一台打包服务器 + 一台部署(目标)服务器, 你可以只用一台服务器,但注意环境的隔离
请在进行后续步骤前,确保已完成所有的准备工作,具体的安装配置,请参考官方文档,这里我不会再赘述。[安装](https://docs.gitea.com/zh-cn/category/installation)
如果你使用**1panel面板**,可直接在应用商店中一键完成安装,作者就就是使用此方式。
>注意:在版本 < `v1.21.0`之前的版本需要在配置文件中开启`actions`功能具体请参考官方文档[配置](https://docs.gitea.com/zh-cn/usage/actions/quickstart),之后的版本则是默认开启
本教程使用的Gitea版本为`v1.22.6` !
## 配置Runner
在Gitea actions中运行Job都是通过Runner来执行的因此我们需要先配置Runner以便于我们后续的部署流程有两种安装方式可直接安装到主机上也可以通过docker容器中这里作者将通过docker安装为避免过多的资源消耗可以将其安装到一个单独的主机中ps对于国内用户可以选择一台香港服务器这样运行job的速度会更快😄), 作者选择的主机就是一台香港的VPS
### 下载act_Runner
首先我们需要下载此工具用来注册或者生成配置[下载地址](https://gitea.com/gitea/act_runner/releases)请在此页面中选择与你的系统对应的版本进行下载下载完成后上传到服务器中这里我是上传到了服务器中的`/gitea_runner2`目录下也可以使用`curl`等工具直接下载, 可直接使用以下命令但注意名称和下载地址我的系统是`Debain12 x86_64`,因此下载的是`act_runner-0.2.11-linux-amd64`
```bash
curl -o act_runner2 https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64
```
完成后设置下执行权限
```bash
chmod +x act_runner2 # 注意文件名,请根据实际情况修改
./act_runner --version # 看到版本信息就代表成功了
```
### 生成配置
```
./act_runner2 generate-config > config.yaml
```
一般情况下默认的配置就够了如果你有其他的需求请自行修改`config.yaml`
**由于作者选用docker安装的方式所以如果你选用直接在主机安装的方式可以跳过后续的安装步骤参考官方文档进行安装[直接安装](https://docs.gitea.com/zh-cn/usage/actions/quickstart)**
### 注册runner
这里我选择通过`docker-compose.yml`配置的方式进行注册
**注意: 将配置中的参数结合你自己的情况修改,以下配置仅供参考!!!**
```yaml
version: "3.8"
services:
runner:
image: gitea/act_runner:latest
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "<your_gitea.com>"
GITEA_RUNNER_REGISTRATION_TOKEN: "<your_registration_token_here>"
GITEA_RUNNER_NAME: "act_vps_runner2"
GITEA_RUNNER_LABELS: "act_vps_runner2"
volumes:
- /gitea_runner2/config.yaml:/config.yaml
- /gitea_runner2/runner_data/data:/data
- /gitea_runner2/runner_data/cache:/root/.cache
- /var/run/docker.sock:/var/run/docker.sock
```
### 配置说明
**GITEA_RUNNER_REGISTRATION_TOKEN** 此参数为注册令牌需要从你的gitea中进行获取一共有三个级别我这里直接选用组织级别的在组织设置页中进行获取如下图所示
![image](https://images.unsplash.com/photo-1633356122102-3fe601e05bd2?q=80&w=1470&auto=format&fit=crop)
其他的实例配置页面及说明请参考官方文档有详细的说明
**如果您无法看到设置页面,请确保您具有正确的权限并且已启用 Actions, 检查Gitea版本 !!!**
### 运行并生成runner
由于作者使用1panel面板因此直接在容器选项中编排即可如果你没有使用此类的可视化工具就需要手动执行``docker-compose up -d``
成功后可查看容器的运行以及日志日志中会显示注册成功的信息如下图所示
![image](https://images.unsplash.com/photo-1607706189992-eae578626c86?q=80&w=1470&auto=format&fit=crop)
然后我们回到你获取runner令牌的页面进行刷新就能看到你的runner了

View File

@@ -0,0 +1,86 @@
---
layout: "@/layouts/BlogPostLayout.astro"
title: "JavaScript中map遍历数字的陷阱"
description: "本文详细解析了JavaScript中[\"1\",\"2\",\"3\"].map(parseInt)返回[1, NaN, NaN]的原因通过分析map和parseInt方法的工作原理揭示了这个常见的JavaScript陷阱。"
date: "2023-09-15"
image: "https://images.unsplash.com/photo-1579468118864-1b9ea3c0db4a?q=80&w=1470&auto=format&fit=crop"
tags: ["JavaScript", "函数式编程", "陷阱"]
tagId: ["javascript", "functional-programming", "trap"]
category: "前端开发"
categoryId: "frontend"
readTime: "3 min read"
---
在JavaScript中有一个经典的问题
```javascript
["1","2","3"].map(parseInt)
```
这段代码的返回值是什么?很多人可能会认为是`[1, 2, 3]`,但实际上是`[1, NaN, NaN]`。为什么会这样呢?让我们一起来分析一下。
### map方法的工作原理
首先,我们需要了解`map`方法的工作原理。`map`方法会为数组中的每个元素调用一次提供的函数,并使用函数的返回值创建一个新数组。
`map`方法的回调函数接受三个参数:
1. `currentValue`:当前正在处理的元素
2. `index`:当前元素的索引
3. `array`:调用`map`方法的数组
所以,当我们执行`["1","2","3"].map(parseInt)`时,实际上是在执行:
```javascript
["1","2","3"].map((item, index, array) => parseInt(item, index, array))
```
### parseInt方法的工作原理
接下来,我们需要了解`parseInt`方法的工作原理。`parseInt`方法解析一个字符串参数,并返回一个指定基数的整数。
`parseInt`方法接受两个参数:
1. `string`:要被解析的值。如果参数不是一个字符串,则将其转换为字符串。字符串开头的空白符将会被忽略。
2. `radix`一个介于2和36之间的整数表示上述字符串的基数。例如指定10表示使用十进制数值系统。始终指定此参数可以消除阅读该代码时的困惑并且保证转换结果可预测。当未指定基数时不同的实现会产生不同的结果通常将值默认为10。
### 分析问题
现在,让我们分析一下`["1","2","3"].map(parseInt)`的执行过程:
1. 对于第一个元素"1"`map`方法调用`parseInt("1", 0, ["1","2","3"])`。由于`radix`为0`parseInt`会根据字符串的前缀来决定基数。如果字符串不以"0x"或"0"开头则基数为10。所以`parseInt("1", 0)`返回1。
2. 对于第二个元素"2"`map`方法调用`parseInt("2", 1, ["1","2","3"])`。由于`radix`为1而有效的`radix`范围是2-36所以`parseInt("2", 1)`返回`NaN`
3. 对于第三个元素"3"`map`方法调用`parseInt("3", 2, ["1","2","3"])`。由于`radix`为2表示二进制而在二进制中只有0和1所以`parseInt("3", 2)`返回`NaN`
因此,`["1","2","3"].map(parseInt)`的返回值是`[1, NaN, NaN]`
### 如何正确使用
如果我们想要将字符串数组转换为数字数组,可以使用以下方法:
```javascript
["1","2","3"].map(Number) // [1, 2, 3]
```
或者使用箭头函数:
```javascript
["1","2","3"].map(item => parseInt(item)) // [1, 2, 3]
```
或者使用`parseInt`的绑定函数:
```javascript
["1","2","3"].map(parseInt.bind(null)) // [1, 2, 3]
```
### 总结
这个问题揭示了JavaScript中函数式编程的一个常见陷阱。当我们使用高阶函数如`map`时,需要注意回调函数的参数和行为。在这个例子中,`parseInt`函数接受的参数与`map`方法提供的参数不完全匹配,导致了意外的结果。
理解这个问题有助于我们更好地理解JavaScript中的函数式编程和类型转换。

View File

@@ -2,6 +2,11 @@
@import "tw-animate-css";
@plugin "@tailwindcss/typography";
@theme {
--typography-code-before-content: "";
--typography-code-after-content: "";
}
/* Blog List Component Styles */
.line-clamp-3 {
display: -webkit-box;

350
temp_docs/01-03.md Normal file
View File

@@ -0,0 +1,350 @@
---
authors: [joyZhao]
title: 利用Gitea actions创建项目部署的自动化流程
tags: [gitea, actions, CI/CD, 自动化部署]
---
本文以部署一个前端项目为示例将手把手教你如何利用Gitea actions创建项目部署的自动化流程对于不想使用Jenkins的小伙伴来说Gitea actions是一个不错的`CI/CD`解决方案。
<!-- truncate -->
## 前言
在我们开发完一个项目后需要将项目部署到服务器上以便于用户可以访问。但是每次部署都需要手动操作这会浪费我们的时间和精力。因此我们可以利用Gitea actions创建一个自动化流程以便于我们快速部署项目。
## 什么是Gitea actions
Gitea actions是Gitea的一个功能它允许我们创建自动化流程以便于我们快速部署项目。Gitea actions可以执行各种任务例如构建、测试、部署等。Gitea actions可以使用各种编程语言和工具例如Python、Java、Docker等。并且Gitea actions在配置上尽量与GitHub actions的配置兼容因此如果你已经熟悉了GitHub actions那么你也可以轻松地使用Gitea actions。
详见官方文档:[Gitea actions](https://docs.gitea.com/zh-cn/usage/actions/overview)
## 准备工作
在我们开始创建此流程前,首先你需要准备以下资料:
1. 一台服务器,最好拥有root权限会安装一些工具
2. 一个已部署的Gitea仓库,版本至少需要>=`v1.19`,官方强烈建议使用`v1.20`或更高版本
3. docker环境非必须, 用在部署runner的主机上
> ps: 作者这里服务器准备的是:一台打包服务器 + 一台部署(目标)服务器, 你可以只用一台服务器,但注意环境的隔离
请在进行后续步骤前,确保已完成所有的准备工作,具体的安装配置,请参考官方文档,这里我不会再赘述。[安装](https://docs.gitea.com/zh-cn/category/installation)
如果你使用**1panel面板**,可直接在应用商店中一键完成安装,作者就就是使用此方式。
>注意:在版本 < `v1.21.0`之前的版本需要在配置文件中开启`actions`功能具体请参考官方文档[配置](https://docs.gitea.com/zh-cn/usage/actions/quickstart),之后的版本则是默认开启
本教程使用的Gitea版本为`v1.22.6` !
## 配置Runner
在Gitea actions中运行Job都是通过Runner来执行的因此我们需要先配置Runner以便于我们后续的部署流程有两种安装方式可直接安装到主机上也可以通过docker容器中这里作者将通过docker安装为避免过多的资源消耗可以将其安装到一个单独的主机中ps对于国内用户可以选择一台香港服务器这样运行job的速度会更快😄), 作者选择的主机就是一台香港的VPS
### 下载act_Runner
首先我们需要下载此工具用来注册或者生成配置[下载地址](https://gitea.com/gitea/act_runner/releases)请在此页面中选择与你的系统对应的版本进行下载下载完成后上传到服务器中这里我是上传到了服务器中的`/gitea_runner2`目录下也可以使用`curl`等工具直接下载, 可直接使用以下命令但注意名称和下载地址我的系统是`Debain12 x86_64`,因此下载的是`act_runner-0.2.11-linux-amd64`
```bash
curl -o act_runner2 https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64
```
完成后设置下执行权限
```bash
chmod +x act_runner2 # 注意文件名,请根据实际情况修改
./act_runner --version # 看到版本信息就代表成功了
```
### 生成配置
```
./act_runner2 generate-config > config.yaml
```
一般情况下默认的配置就够了如果你有其他的需求请自行修改`config.yaml`
**由于作者选用docker安装的方式所以如果你选用直接在主机安装的方式可以跳过后续的安装步骤参考官方文档进行安装[直接安装](https://docs.gitea.com/zh-cn/usage/actions/quickstart)**
### 注册runner
这里我选择通过`docker-compose.yml`配置的方式进行注册
**注意: 将配置中的参数结合你自己的情况修改,以下配置仅供参考!!!**
```yaml
version: "3.8"
services:
runner:
image: gitea/act_runner:latest
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "<your_gitea.com>"
GITEA_RUNNER_REGISTRATION_TOKEN: "<your_registration_token_here>"
GITEA_RUNNER_NAME: "act_vps_runner2"
GITEA_RUNNER_LABELS: "act_vps_runner2"
volumes:
- /gitea_runner2/config.yaml:/config.yaml
- /gitea_runner2/runner_data/data:/data
- /gitea_runner2/runner_data/cache:/root/.cache
- /var/run/docker.sock:/var/run/docker.sock
```
### 配置说明
**GITEA_RUNNER_REGISTRATION_TOKEN** 此参数为注册令牌需要从你的gitea中进行获取一共有三个级别我这里直接选用组织级别的在组织设置页中进行获取如下图所示
![image](https://pic1.imgdb.cn/item/6779f8dfd0e0a243d4ef14ab.png)
其他的实例配置页面及说明请参考官方文档有详细的说明
**如果您无法看到设置页面,请确保您具有正确的权限并且已启用 Actions, 检查Gitea版本 !!!**
### 运行并生成runner
由于作者使用1panel面板因此直接在容器选项中编排即可如果你没有使用此类的可视化工具就需要手动执行``docker-compose up -d``
成功后,可查看容器的运行以及日志,日志中会显示注册成功的信息,如下图所示:
![image](https://pic1.imgdb.cn/item/677893b5d0e0a243d4eeccca.png)
![image](https://pic1.imgdb.cn/item/677893bad0e0a243d4eecccb.png)
然后我们回到你获取runner令牌的页面进行刷新就能看到你的runner了如下图所示
![image](https://pic1.imgdb.cn/item/6779f844d0e0a243d4ef1478.png)
图中的名称是根据你注册配置时的名称
> 至此我们的runner部署就算完成了接下来可以去在项目中使用了。
## 使用actions
gitea actions的使用方式与github actions类似如果你不熟悉这类配置可以先去了解下**github actions**,
作者将使用一个`vitepress`项目作为示例
### 创建配置文件
1. 在项目根目录下创建`.gitea/workflows`目录
2. 在其目录中创建`deploy.yaml`配置文件
3. 我们先写个测试配置,用于测试环境变量,如下所示:
```yaml
name: Deploy Test
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Print Environment Variables
run: env
- name: Print Current Directory
run: pwd
- name: List Files in Working Directory
run: ls -la
```
然后提交代码到仓库我们应该就可以在仓库的actions中看到执行任务了如下图所示:
![image](https://pic1.imgdb.cn/item/6779f972d0e0a243d4ef14da.png)
> **ps如果你没有看到actions执行甚至没看到actions这个选项可能需要你在仓库、组织或管理页面中开启actions选项**
### 配置打包
我们修改`deploy.yaml`配置文件,添加打包步骤, 我这里使用的是pnpm请根据自己的实际情况调整一些配置这里不过多展开
````yaml
name: Deploy docs for project
run-name: ${{ gitea.actor }} is building out Gitea Actions 🚀
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
# TODO 临时解决 https://github.com/go-gitea/gitea/issues/32481, 等待gitea发布1.23.0进行修复
- name: Install git
run: |
if ! command -v git &> /dev/null; then
apt-get update && apt-get install -y git
else
echo "git is already installed"
fi
- name: Checkout repository code
uses: https://gitea.com/actions/checkout@v4
with:
ref: 'main'
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# Skip pnpm cache to reduce overhead
- name: Install pnpm
run: |
if ! command -v pnpm &> /dev/null; then
npm install -g pnpm@9
else
echo "pnpm is already installed"
fi
- name: Check Node.js and pnpm versions
run: |
echo "pnpm version ===>" && pnpm -v
echo "node version ===>" && node -v
# Simplify node_modules handling without caching
- name: Install dependencies
run: |
if [ ! -d "node_modules" ] || [ "$(find package.json -newer node_modules)" ]; then
echo "Dependencies are outdated or missing, installing..."
pnpm install
else
echo "Dependencies are up-to-date, skipping installation."
fi
- name: Build docs
run: pnpm run docs:build
````
> **特意说明安装git是为了修复目前gitea actions中无法通过`actions/checkout`检出代码的问题,此问题预计会在`v1.23.0`中修复,目前我们只能这样处理, 但据作者的观察发现好像与使用的镜像版本也有关系,如果是`ubuntu-latest`好像就不用安装,请以实际情况为准**
如果一切顺利,那么你应该可以看到工作流中完成了打包任务,如图所示
![image](https://pic1.imgdb.cn/item/677bcf9bd0e0a243d4f00c7a.png)
### 部署项目
部署代码的原理,实际上是通过`ssh`的方式连接到目标服务器并执行部署脚本因此我们需要先配置好SSH然后使用`actions` [easingthemes/ssh-deploy](https://github.com/easingthemes/ssh-deploy) 连接到目标服务器并推送代码即可
#### 配置SSH
这一步骤实际上是为服务器配置一个用户并支持`SSH`登录,也就是免密登录,因此我们在**目标服务器**依次使用以下命令,作者使用的`Debian 12`,请根据实际情况调整命令!
##### 创建用户(建议创建一个操作部署目录的用户不要使用root用户)
```bash
sudo adduser deployuser # 请根据自己实际情况修改用户名
```
创建成功后,可以查看是否存在`/home/deployuser`目录,不存在则可能需要手动创建
##### 创建密钥对
我们需要先将用户切换到`deployuser`用户下,然后执行以下命令
```bash
# 1. 创建公私钥
ssh-keygen -t rsa -b 4096 -C ""
# 将公钥内容添加到 authorized_keys 文件
echo ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
# 设置正确的权限
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh
```
##### 赋予特定目录的读写权限(可跳过)
这里需要`root`权限的用户操作
```bash
# 1. 创建目录
mkdir -p /var/www/html/project
# 2. 修改目录权限
sudo chown deployuser:deployuser /var/www/project
sudo chmod 700 /var/www/project
```
由于作者这里使用了`1panel`进行管理网站所以目录就是自动生成的目录,与此有所出入,但流程步骤一致,请根据自己实际情况修改
#### 在gitea中配置密钥信息
1. 在你的gitea的个人、组织或管理的actions页面中点击`密钥`,将我们刚刚在服务器中生成的私钥复制到`密钥`中,然后点击`添加密钥`即可
2. 因为后续我们需要用到服务器IP、用户名这里也建议配置到密钥或变量中方便后续使用。
作者这里将私钥、服务器IP、用户名配置到了密钥中如图所示
![image](https://pic1.imgdb.cn/item/677bd9c0d0e0a243d4f011f2.png)
到这里我们就完成了部署项目所需要的配置了,接下来我们就可以开始部署流程了。
> ps由于`easingthemes/ssh-deploy`需要使用`rsync`执行脚本命令,所以在镜像容器中或者目标服务器都需要依赖此工具,如果不存在,就需要提前安装好,特别是**目标服务器**, 一般需要用root用户或者有安装权限的用户操作
### 项目部署
我们在`deploy.yml`中,添加以下代码,注意格式!!!
````yaml
- name: ssh deploy
uses: easingthemes/ssh-deploy@v5.1.0
with:
SSH_PRIVATE_KEY: ${{ secrets.SH_SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.SH_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.SH_REMOTE_USER }}
SOURCE: "/.vitepress/dist/"
TARGET: "/1Panel/1panel/apps/openresty/openresty/www/sites/demo.zhaoguiyang.cn/index/dist" # 请根据实际情况修改为你的部署目录
EXCLUDE: "/node_modules/"
````
如果一切顺利的话你就会看到actions执行成功并且在你的部署目录下有一个dist目录存在了如下图所示
![image](https://pic1.imgdb.cn/item/677bdd23d0e0a243d4f014a3.png)
![image](https://pic1.imgdb.cn/item/677bdd1bd0e0a243d4f014a1.png)
接下来,你就只需要使用`nginx`之类的工具指向此目录,就可以访问了。
> ps: 如果你在上面这个步骤出现了错误请仔细查看错误日志根据作者的经验来说大概率是出现在ssh密钥和权限处理的这两个问题上如果你使用的不是`ubuntu-latest`,那你可能还需根据报错信息,安装一些必须的工具,如:`git`、`rsync`等
### 完整配置
````yaml
name: Deploy docs for project
run-name: ${{ gitea.actor }} is building out Gitea Actions 🚀
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
steps:
- name: Checkout repository code
uses: https://gitea.com/actions/checkout@v4
with:
ref: 'main'
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# Skip pnpm cache to reduce overhead
- name: Install pnpm
run: |
if ! command -v pnpm &> /dev/null; then
npm install -g pnpm@9
else
echo "pnpm is already installed"
fi
- name: Check Node.js and pnpm versions
run: |
echo "pnpm version ===>" && pnpm -v
echo "node version ===>" && node -v
# Simplify node_modules handling without caching
- name: Install dependencies
run: |
if [ ! -d "node_modules" ] || [ "$(find package.json -newer node_modules)" ]; then
echo "Dependencies are outdated or missing, installing..."
pnpm install
else
echo "Dependencies are up-to-date, skipping installation."
fi
- name: Build docs
run: pnpm run docs:build
- name: ssh deploy
uses: easingthemes/ssh-deploy@v5.1.0
with:
SSH_PRIVATE_KEY: ${{ secrets.SH_SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.SH_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.SH_REMOTE_USER }}
SOURCE: "/.vitepress/dist/"
TARGET: "/1Panel/1panel/apps/openresty/openresty/www/sites/demo.zhaoguiyang.cn/index/dist" # 请根据实际情况修改为你的部署目录
EXCLUDE: "/node_modules/"
````
到这里本文就结束了如有错误请多多谅解,希望给有需要的小伙伴带来一些思路和帮助

90
temp_docs/05-06.md Normal file
View File

@@ -0,0 +1,90 @@
---
authors: [joyZhao]
title: Docusaurus v3中使用Tailwind Css的样式
tags: [docusaurus, tailwindcss]
---
今天来分享一下如何在Docusaurus v3中使用Tailwind Css的样式。
<!-- truncate -->
## 安装相关依赖
```bash
npm install tailwindcss
```
## 配置tailwind.config.js
```js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx,md,mdx}",
"./docs/**/*.{md,mdx}",
],
theme: {
screens: {
xs: '480px',
sm: '576px',
md: '768px',
lg: '998px',
xl: '1200px',
'2xl': '1400px',
},
extend: {
fontFamily: {
sans: 'var(--ifm-font-family-base)',
firacode: 'var(--font-family-firacode)',
kaiti: 'var(--font-family-kaiti)',
},
colors: {
'font-color': 'var(--ifm-font-color-base)',
'link-color': 'var(--ifm-link-color)',
'link-hover-color': 'var(--ifm-link-hover-color)',
primary: 'var(--ifm-color-primary)',
'primary-light': 'var(--ifm-color-primary-light)',
'primary-lighter': 'var(--ifm-color-primary-lighter)',
'primary-lightest': 'var(--ifm-color-primary-lightest)',
'primary-dark': 'var(--ifm-color-primary-dark)',
'primary-darker': 'var(--ifm-color-primary-darker)',
'primary-darkest': 'var(--ifm-color-primary-darkest)',
},
boxShadow: {
nysm: '0 0 2px 0 rgb(0 0 0 / 0.05)',
ny: '0 0 3px 0 rgb(0 0 0 / 0.1), 0 0 2px - 1px rgb(0 0 0 / 0.1)',
nymd: '0 0 6px -1px rgb(0 0 0 / 0.1), 0 0 4px -2px rgb(0 0 0 / 0.1)',
nylg: '0 0 15px -3px rgb(0 0 0 / 0.1), 0 0 6px -4px rgb(0 0 0 / 0.1)',
spread: '0 5px 40px rgb(0 0 0 / 0.1)',
},
},
},
plugins: [],
darkMode: ["class", '[data-theme="dark"]'],
corePlugins: {
preflight: false,
},
blocklist: ["container"],
}
```
## 创建tailwind.css
`src/css`目录下创建`tailwind.css`文件,并写入以下内容:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
**注意创建完成后是需要在custom.css中导入tailwind.css的否则tailwind样式不会生效**
## 创建postcss.config.js
在项目根目录下创建`postcss.config.js`文件,并写入以下内容:
```js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
```

View File

@@ -0,0 +1,132 @@
---
id: git-commit-rules
title: Git提交规范
author: Joy Zhao
sidebar_position: 2
---
## commit message 三部分组成
### Header 必需)
包括三个字段:
`type`(必需)
`scope`(可选)
`subject`(必需)
### **1 type**
用于说明 commit 的类别只允许使用下面7个标识
- feat新功能feature
- fix修补bug
- docs文档documentation
- style 格式(不影响代码运行的变动)
- refactor重构即不是新增功能也不是修改bug的代码变动
- test增加测试
- chore构建过程或辅助工具的变动
### 2scope
`scope`用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。
### 3subject
`subject`是 commit 目的的简短描述不超过50个字符。
- 以动词开头,使用第一人称现在时,比如`change`,而不是`changed``changes`
- 第一个字母小写
- 结尾不加句号(.``
### Body
Body 部分是对本次 commit 的详细描述,可以分成多行
例子:
````Plain
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Use a hanging indent ```
**注意点:**
1. 使用第一人称现在时比如使用change而不是changed或changes。
2. 应该说明代码变动的动机,以及与以前行为的对比。
### Footer
### Footer 部分只用于两种情况:
**1不兼容变动**
如果当前代码与上一个版本不兼容,则 Footer 部分以`BREAKING CHANGE`开头,后面是对变动的描述、以及变动理由和迁移方法。
```jsx
Plain Text BREAKING CHANGE: isolate scope bindings definition has changed.
To migrate the code follow the example below:
Before:
scope: {
myAttr: 'attribute',
}
After:
scope: {
myAttr: '@',
}
The removed `inject` wasn't generaly useful for directives so there should be no code using it.
````
**2关闭 Issue**
如果当前 commit 针对某个issue那么可以在 Footer 部分关闭这个 issue 。
```jsx
Plain Text Closes #234
or
Closes #123, #245, #992
```
### Revert 特殊情况
如果当前 commit 用于撤销以前的 commit则必须以`revert:`开头,后面跟着被撤销 Commit 的 Header
```Plain
This reverts commit 667ecc1654a317a13331b17617d973392f415f02. ```
Body部分的格式是固定的必须写成`This reverts commit &lt;hash>.`,其中的`hash`是被撤销 commit 的 SHA 标识符。
如果当前 commit 与被撤销的 commit在同一个发布release里面那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit会出现在 Change log 的`Reverts`小标题下面
## 生成 Change log 三部分
需要所有 Commit 都符合 Angular 格式Change log 就可以用脚本自动生成
- New features
- Bug fixes
- Breaking changes
每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。
[conventional-changelog](<https://github.com/ajoslin/conventional-changelog>) 就是生成 Change log 的工具,运行下面的命令即可
## Commitizen
[Commitizen](<https://github.com/commitizen/cz-cli>)是一个撰写合格 Commit message 的工具
## validate-commit-msg
[validate-commit-msg](<https://github.com/kentcdodds/validate-commit-msg>) 用于检查 Node 项目的 Commit message 是否符合格式
```

View File

@@ -0,0 +1,56 @@
---
id: map-traversal-traps
title: Map遍历数字的陷阱
sidebar_label: map遍历数字的陷阱
description: Map遍历数字的陷阱,['1','2','3'].map(parseInt)的返回值是什么?
---
# ['1','2','3'].map(parseInt)的返回值
> **先说结果:其运行的结果是: [1, NAN, NAN]**
## map方法
`map()`方法会创建一个新数组,其结果是该数组中的每个元素都调用一个提供的回调函数的返回值
```JavaScript
let arr = [ 1, 2, 3 ];
arr.map ( ( item, index, arr ) => item ) // 1, 2, 3 [ 1, 2, 3 ]
```
该函数接收三个参数:当前项, 下标索引, 操作的数组
## parsetInt方法
`parseInt()` 是用来解析字符串使字符串成为指定基数的整数parseInt的基本语法:
`parseInt(string, radix)`
**接收两个参数第一个表示被处理的值字符串第二个表示为解析时的基数。radix是一个介于2-36之间的整数返回解析后的整数值。 如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN**
```JavaScript
praseInt('111') // 111
parseInt('111', 0) // 111
// radix为0时且string参数不以"0x"和"0"开头时按照10为基数处理
parseInt('111', 1) // NaN 【2 <= radix <= 36】
parseInt('111', 2) // 7
```
## 结论
当运行 `[1, 2, 3].map(parseInt)` 上时实际上运行的是:
```JavaScript
['1', '2', '3'],map( parseInt('1', 0)); // radix为0时使用默认的10进制。
['1', '2', '3'],map( parseInt('2', 0)); // radix值在2-36无法解析返回NaN
['1', '2', '3'],map( parseInt('3', 0)); // 基数为22进制数表示的数中最大值小于3无法解析返回NaN
map函数返回的是一个数组所以最后结果为[1, NaN, NaN]
```

View File

@@ -0,0 +1,95 @@
---
id: browser-run-principle
title: 浏览器的运行原理
author: Joy Zhao
sidebar_position: 1
---
## 渲染流程
html → 字符串 → 渲染 → 像素点
## 一、浏览器是如何渲染页面的?
当浏览器的网络线程收到HTML文档后会产生一个渲染任务并将其传递给渲染主线程的消息队列。
在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。
整个渲染流程分为多个阶段分别是HTML解析、样式计算 布局、分层、绘制、分快、光栅化、每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入。
这样,整个渲染流程就形成了一套组织严密的生产流水线。
### 第一步解析HTML
解析过程中遇到CSS解析CSS遇到JS执行JS。为了提高解析效率浏览器在开始解析前会启动一个预解析的线程率先下载HTML中的外部CSS文件和外部的JS文件。
如果主线程解析到link位置此外部的CSS的文件还没有下载解析好主线程不会等待继续解析后续的HTML这是因为下载和解析CSS的工作是在预解析线程中进行的。这就是CSS不会阻塞HTML解析的根本原因。
如果主线程解析到script位置会停止解析HTML转而等待JS文件下载好并将全局代码解析执行完成后才继续解析HTML。这是因为JS代码的执行过程可能会修改当前的DOM树所以DOM树的生成必须暂停。这就是JS会阻塞HTML解析的根本原因。
在第一步完成后会得到DOM树和CSSOM树浏览器的默认样式、内部样式、外部样式、行内样式均会包含在CSSOM树中。
### 第二步:样式计算
主线程遍历得到的DOM树依次为树中的每个节点计算出它最终的样式称之为Computed Style。
在这一过程中很多预设值会变成绝对值比如red会变成rgb(255,0,0); 相对单位会变成绝对单位比如em会变成px
这一步完成后会得到一颗带有样式的DOM树。
### 第三步:布局
布局阶段会依次遍历DOM树的每一节点计算每个节点的几何信息。例如节点的宽高、相对包含块的位置。
大部分时候DOM树和布局树并非一一对应。
比如 display:noned的节点没有几何信息因此不会生成到布局树又比如使用了伪元素选择器虽然DOM树中不存在这些伪元素节点但它们拥有几何信息所以会生成到布局树中。还有匿名行盒、匿名块盒等等都会导致DOM树和布局树无法一一对应。
### 第四步:分层
主线程会使用一套复杂的策略对整个布局树中进行分层。
分层的好处在于,将某一层改变后,仅会对该层进行后续处理,从而提升效率。
滚动条、堆叠上下文、transform、opcity 等样式都会或多或少的影响分层结果也可以通过will-change属性更大层度的影响分层结果。
### 第五步:绘制
主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。
完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。
合成线程首先对每个图层进行分块,将其划为更多的区域。
它会从线程池中拿取多个线程来完成分块工作。
### 第六步:光栅化
合成线程会将块信息交给GPU进程以极高的速度完成光栅化。
GPU进程会开启多个线程来完成光栅化并且优化处理靠近视口区域的块。
光栅化的结果,就是一块一块的位图
### 第七步:画
合成线程拿到每个层、每个块的位图后生成一个个【指引quad】信息。
指引会标识出每个位图应该画到屏幕的哪个位置,以及考虑到旋转、缩放等变形。
变形发生在合成线程与渲染主线程无关这就是transform效率高的本质原因。
合成线程会把quad提交给GPU进程由GPU进程产生系统调用提交给GPU硬件完成最终的屏幕成像。
## 什么是reflow回流
reflow的本质就是重新计算layout树。
当进行了会影响布局树的操作后需要重新计算布局树会引发layout
为了避免连续的多次操作导致布局树反复计算浏览器会合并这些操作当js代码全部完成后再进行统一计算。所以改动属性造成的reflow是异步完成的。
也同样如此当js获取布局属性时就可能造成无法获取到最新的布局信息。
浏览器在反复权衡下最终决定获取属性立即reflow
## 什么是repaint重绘
repaint的本质是重新根据分层信息计算了绘制指令。
当改动了可见样式后就需要重新计算会引发repaint。
由于元素的布局信息也属于可见样式所以reflow一定会引起repaint
## 为什么transform的效率高
因为transform没有样式、布局、分层、光栅化的步骤直接进入了合成线程进行绘制。所以它的效率比直接改动样式或几何属性效率高。