ゴゴゴゴゴ
Skip to content
JOJO
首页文章项目知识库相册音乐
全站搜索
  • Navigate
  • Go to Page
  • escClose
© 2026 JOJOBuilt with Next.js

四种声音一个时代

这里收藏周杰伦、王力宏、陶喆与林俊杰的专辑,也把四位创作者放回完整唱片的语境。陶喆以松弛的律动重塑华语 R&B,周杰伦把说唱、古典与中国式旋律带进流行音乐,王力宏在抒情、摇滚和东方元素之间拓宽制作边界,林俊杰则用细密旋律与宽阔声线记录城市情绪。这里不打乱曲序,也不把专辑压缩成热门单曲;挑一张封面,从第一首听到最后一首,感受开场、中段与尾声如何共同完成一段叙事。

左右滑动浏览,轻触封面,从第一首开始听

正在播放的唱片

选择一首歌曲查看歌词
专辑封面

专辑

    请选择一张唱片等待选曲
    0:00
    0:00

    键盘快捷键

    搜索
    / 或 ⌘K
    回到首页
    G H
    博客列表
    G B
    切换主题
    T
    关闭弹窗
    Esc
    显示帮助
    ?

    按 Esc 关闭此窗口

    Blog550
    • 全部文章(550)
    • 包管理工具(8)
      • Bun 完全指南
      • fnm 完全指南
      • ni 完全指南
      • npm 完全指南
      • nrm 完全指南
      • nvm 完全指南
      • pnpm 完全指南
      • Yarn 完全指南
    • 并发异步(7)
      • 异步任务取消机制
      • Promise静态方法汇总
      • 封装超时工具方法
      • 并发与并行
      • 封装并发任务方法
      • 一道并发相关的面试题
      • 文件写入的原子化与并发操作
    • 打包工具(10)
      • Bun - 全栈 JavaScript 运行时与打包器
      • esbuild - 用 Go 写的极速打包器
      • Parcel - 零配置的极速打包工具
      • Rolldown - Vite 团队打造的 Rust 打包器
      • Rollup - 专注于库开发的打包工具
      • Rspack - Rust 驱动的 Webpack 替代品
      • SWC - Rust 驱动的超快编译器
      • Turbopack - Rust 驱动的下一代打包工具
      • Vite - 下一代前端构建工具
      • Webpack - 功能最强大的模块打包器
    • 算法(74)
      • 课程必看
      • 算法基本概念
      • 评判算法的好坏
      • 复习对数
      • 常见的复杂度
      • 常见算法思维
      • 递归
      • 数据结构
      • 数组结构
      • 顺序查找
      • 二分查找
      • 插值查找
      • 冒泡排序
      • 选择排序
      • 插入排序
      • 折半插入排序
      • 希尔排序
      • 归并排序
      • 快速排序
      • 链表结构
      • 双向链表
      • 单向循环链表
      • 双向循环链表
      • 栈结构
      • 队列结构
      • 循环队列
      • 击鼓传花游戏
      • 双端队列
      • 回文检查器
      • 集合结构
      • 字典结构
      • 树结构
      • 二叉树
      • 二叉树遍历
      • 前序中序还原二叉树
      • 后序中序还原二叉树
      • 二叉树深度优先搜索
      • 二叉树广度优先搜索
      • 二叉树的比较
      • 二叉树diff算法
      • 二叉搜索树
      • 二叉搜索树常见操作
      • 平衡二叉树
      • 二叉树左单旋
      • 二叉树右单旋
      • 二叉树双旋
      • 二叉堆
      • 堆排序
      • 哈夫曼树
      • 图结构
      • 邻接矩阵
      • 邻接表
      • 构建图
      • 图的深度优先搜索
      • 图的广度优先搜索
      • 普里姆算法
      • 克鲁斯卡尔算法
      • LRU算法
      • 动态规划
      • 青蛙跳台阶
      • 不同路径问题
      • 凑零钱问题
      • 打家劫舍
      • 背包问题
      • 回溯算法
      • 数字组合
      • 电话号码字母组合
      • 复原IP地址
      • 数组子集
      • 贪心算法
      • 柠檬水找零
      • 摆动序列
      • 分发糖果
      • 跳跃游戏
    • 文档协同(12)
      • 课程说明
      • 文档协同常见方案
      • OT冲突算法
      • CRDT冲突算法
      • CRDT数据结构
      • 文章翻译
      • Yjs基础介绍
      • Yjs快速上手
      • Yjs细节补充
      • 监听机制
      • 标签页同步
      • WebSocket基础知识
    • 项目实战(1)
      • ShipAny:1小时开发海外游客「中国攻略 + AI旅游助手」SaaS网站
    • AI工具(9)
      • Claude Code 完全指南
      • ComfyUI 完全指南
      • GitHub Copilot 入门指南
      • Coze 完全指南
      • Cursor vs GitHub Copilot 全面对比
      • Cursor-完整指南
      • Dify 完全指南
      • Droid 完全指南
      • n8n 完全指南
    • Astro(5)
      • Astro 入门指南
      • Astro 路由与页面
      • Astro 组件与样式
      • Astro 内容集合
      • Astro 集成与部署
    • CSS(25)
      • CSS 选择器基础
      • CSS 盒模型
      • CSS 定位与层叠
      • 浮动与 BFC
      • Flexbox 布局
      • Grid 布局
      • 响应式设计
      • 伪类与伪元素
      • 现代选择器
      • CSS 变量
      • 层叠层
      • 过渡效果
      • CSS 动画
      • 变换与滤镜
      • 容器查询
      • 文字与排版
      • 背景与渐变
      • 滚动与溢出
      • 裁剪与遮罩
      • 颜色系统
      • 边框与阴影
      • 逻辑属性
      • CSS 函数
      • 多列布局
      • 用户交互
    • ES6+(40)
      • let 和 const 命令
      • 解构赋值基础
      • 解构赋值进阶
      • 字符串的扩展
      • 字符串新增方法
      • 正则表达式扩展
      • 数值的扩展
      • BigInt 大整数
      • 函数默认参数
      • rest 参数与扩展运算符
      • 箭头函数详解
      • 尾调用优化
      • 数组扩展方法
      • 数组新增方法
      • 数组不可变方法
      • 对象的扩展
      • Object 新增方法
      • Symbol 基础
      • Symbol 进阶应用
      • Set 数据结构
      • Map 数据结构
      • TypedArray 类型化数组
      • Proxy 基础
      • Proxy 进阶应用
      • Reflect 元编程
      • 元编程实战案例
      • 装饰器 Decorator
      • Iterator 迭代器
      • Generator 基础
      • Generator 进阶
      • Promise 基础
      • Promise 进阶
      • async 函数
      • await 与错误处理
      • 异步迭代
      • Class 基础
      • Class 继承
      • Module 语法
      • Module 加载
      • ES 新特性汇总
    • HTML(8)
      • HTML 入门:构建 Web 的基石
      • HTML5 语义化:让页面更有意义
      • HTML 表单基础:用户交互的起点
      • HTML 表单进阶:验证与现代特性
      • HTML 多媒体:音视频与响应式图片
      • HTML APIs(上):客户端存储完全指南
      • HTML APIs(下):交互与设备能力
      • Canvas 与 SVG:Web 图形绘制入门
    • JavaScript(30)
      • JavaScript 简介
      • 变量与数据类型
      • 运算符
      • 类型转换
      • 控制流语句
      • 函数基础
      • 对象基础
      • 数组基础
      • 原型与原型链
      • this 关键字
      • 面向对象编程
      • 作用域
      • 闭包
      • 执行上下文
      • DOM 选择器
      • DOM 操作
      • 事件处理
      • 事件委托
      • 错误处理
      • 调试技巧
      • 正则表达式
      • Date 日期对象
      • Math 对象
      • 定时器
      • JSON 处理
      • BOM 基础
      • 本地存储
      • 表单处理
      • AJAX 与 Fetch
      • 严格模式
    • Langchain(37)
      • Langchain基本介绍
      • 模型接口
      • 缓存
      • 提示词模板
      • 结构化提示词
      • Message消息对象
      • 消息占位
      • LCEL
      • 并发流式返回结果
      • Runnable接口
      • 条件路由
      • 字符串解析器
      • 结构化输出解析器
      • 列表输出解析器
      • JSON解析器
      • 正则解析器
      • 本地资源Loader
      • Web资源Loader
      • 文本切割
      • 嵌入处理
      • 内存型向量库
      • 检索优化
      • RAG实践
      • Memory基础
      • 自动维护聊天记录
      • 实现长程记忆
      • 持久化记忆
      • 第三方存储
      • Tools基础
      • Tools配置
      • Zod基础
      • LangChain中使用工具
      • 重新认识Agent
      • LangChain中创建Agent
      • Agent实践
      • 回退机制
      • 回调机制
    • LLM(34)
      • LLM发展历史
      • N-Gram模型
      • 词袋模型
      • 词元
      • 嵌入
      • Transformer内部结构
      • Transformer解码策略
      • Transformer块
      • 本地部署大模型
      • Agent
      • 流式返回信息
      • 支持上下文
      • 获取实时消息
      • 外接DeepSeek
      • Function Calling
      • MCP理论知识
      • Resources
      • Schema
      • 注册资源模板
      • Prompts
      • 监听资源更新
      • 远程通信方式
      • MCP Client
      • 大模型幻觉
      • RCT构词法
      • 提示词常见技巧
      • 基于反馈迭代优化
      • 改善提示效果
      • 提示词评估工具
      • RAG基础知识
      • RAG实践
      • RAG架构演进
      • 大模型微调
      • LoRA微调
    • MCP(8)
      • MCP 入门:连接 AI 与外部世界的桥梁
      • 搭建第一个 MCP Server
      • MCP Tools 深入:让 AI 具备行动能力
      • MCP Resources:向 AI 暴露数据
      • MCP Prompts:预定义 AI 交互模板
      • MCP Transport 与部署
      • 构建 MCP Client:集成到你的应用
      • 实战:构建文档助手 MCP Server
    • Nest(31)
      • 快速上手Nest Cli
      • 三层架构与MVC
      • IoC
      • Nest中的装饰器
      • 模块的基本概念
      • 什么是AOP
      • 中间件与守卫
      • 拦截器
      • 文件上传
      • 自定义装饰器
      • mysql相关回顾
      • 对于ORM的理解
      • 快速上手Redis
      • Nest中使用Redis
      • 状态保存方式
      • Nest中实现Session与JWT
      • JWT登录注册后端处理
      • 使用Passport与循环引用问题
      • 通过环境变量获取配置信息
      • RBAC权限设计1
      • RBAC权限设计2
      • Nest中使用Websocket
      • 日志处理
      • 面向切面的日志处理
      • 快速入门GraphQL
      • Nest中使用GraphQL
      • Swagger API文档
      • 单元测试与E2E测试
      • 安全防护实践
      • 定时任务与任务调度
      • Bull队列与异步任务
    • Next(36)
      • Next.js 简介
      • 项目创建与配置
      • App Router 概述
      • 创建第一个页面
      • 开发工具与调试
      • TypeScript 配置
      • 文件系统路由
      • 动态路由
      • 嵌套路由与布局
      • 路由组
      • 并行路由
      • 拦截路由
      • 导航与链接
      • 中间件
      • Server Components
      • Client Components
      • 混合渲染模式
      • Streaming 与 Suspense
      • 静态与动态渲染
      • 数据获取基础
      • 缓存策略
      • Server Actions
      • Route Handlers
      • 数据库集成
      • 错误处理
      • 图片优化
      • 字体优化
      • 脚本优化
      • 元数据与 SEO
      • 性能分析
      • 认证基础
      • NextAuth 集成
      • 环境变量与配置
      • 测试策略
      • 部署方案
      • 项目实战
    • Nuxt(36)
      • web应用变迁史
      • 课程说明
      • SSR初体验
      • 水合操作
      • 改造CSR项目
      • 基本介绍
      • 配置概览
      • 组件类型
      • Nuxt模块
      • 嵌套路由
      • 动态路由
      • 请求数据
      • server目录
      • 应用部署
      • 中间件
      • 插件
      • layers
      • 过渡
      • 状态管理
      • 全匹配路由
      • 路由补充知识
      • 自定义路由
      • 自定义模块
      • 自定义指令
      • meta相关处理
      • 渲染模式
      • 错误处理
      • 不同阶段错误
      • 客户端渲染组件
      • 其余内置组件
      • 配置相关细节
      • 常用组合式函数
      • 服务器引擎
      • NuxtUI
      • SSR注意事项
      • 课程收官
    • React(55)
      • 序章:React课程介绍
      • React 基本介绍
      • JSX 基础语法
      • 组件与事件绑定
      • 组件状态与数据传递
      • 表单
      • 生命周期
      • Hooks
      • React-router介绍
      • React-router v版本学生管理系统课堂笔记
      • React-redux 介绍
      • Antd介绍
      • coder station前台系统笔记
      • coder station 后台系统笔记
      • Antd Pro介绍
      • Dva介绍
      • Umi.js 介绍
      • 属性默认值和类型验证
      • 高阶组件
      • Ref
      • Context
      • Render Props
      • Portals
      • 错误边界
      • 组件渲染性能优化
      • 前端框架的理解
      • Scheduler调度延时任务
      • 最小堆
      • React中的位运算
      • beginWork工作流程
      • completeWork工作流程
      • 图解diff算法
      • commit 工作流程
      • lane模型
      • React 中的事件
      • Hooks原理
      • React和Vue描述页面的区别
      • useState和useReducer
      • effect相关hook
      • useCallback和useMemo
      • useRef
      • Update
      • 性能优化策略之eagerState
      • 性能优化策略之bailout
      • bailout和ContextAPI
      • 性能优化对日常开发启示
      • 前端框架的分类
      • 虚拟 DOM
      • React 整体架构
      • React 渲染流程
      • Fiber双缓冲
      • MessageChannel
      • Scheduler调度普通任务
      • 课程介绍
      • 课程总结
    • ReactNative(33)
      • RN 基本介绍
      • 快速搭建 RN 开发环境
      • 复习 React 基础知识
      • RN 基础知识
      • 实战案例
      • RN 内置基础组件
      • 容器组件
      • Pressable 组件
      • 列表组件
      • 功能组件
      • 第三方组件库
      • 自定义组件案例:弹框组件
      • 自定义组件案例:单选组件
      • 基础API
      • 屏幕API
      • 设备API
      • 动画API
      • 手势API
      • React Navigation简介
      • 参数传递和标题栏信息配置
      • 嵌套路由与生命周期
      • 其他类型的导航
      • 状态管理介绍
      • Redux
      • 其他第三方库
      • Expo SDK
      • Fabric 架构
      • 渲染、提交和挂载
      • 实现跨平台
      • 扁平化视图
      • 线程模型
      • 如何打包发布应用
      • 通过 React Native CLI 搭建环境
    • TailwindCSS(7)
      • TailwindCSS基础介绍
      • 环境搭建与配置
      • 布局系统
      • 间距与尺寸系统
      • 颜色与主题
      • 响应式设计
      • 交互与动画
    • TypeScript(35)
      • 初识 TypeScript
      • 基础类型
      • 数组与元组
      • 对象类型
      • 函数类型
      • 类型推断与断言
      • 联合类型与交叉类型
      • 字面量类型与类型别名
      • 接口基础
      • 接口进阶
      • 接口继承与实现
      • 类基础
      • 类的访问修饰符
      • 类的继承
      • 类与接口对比
      • 泛型基础
      • 泛型接口与泛型类
      • 泛型约束
      • 内置工具类型(上)
      • 内置工具类型(下)
      • 泛型实战
      • 类型守卫
      • 类型收窄
      • 索引类型与索引签名
      • 映射类型
      • 条件类型
      • infer 关键字
      • 模板字面量类型
      • 模块系统
      • 命名空间
      • 声明文件基础
      • 声明文件进阶
      • tsconfig 配置详解
      • 类型体操入门
      • TypeScript 最佳实践
    • 杂谈(9)
      • Cloudflare 全栈部署指南
      • Creem 支付集成指南
      • Dokploy 自托管部署指南
      • GitHub OAuth 登录集成指南
      • Google OAuth 登录集成指南
      • Stripe 支付集成指南
      • Supabase 完全指南
      • Vercel 自动化部署指南
      • Zeabur 部署指南
    • 全部文章(550)
    • 包管理工具(8)
      • Bun 完全指南
      • fnm 完全指南
      • ni 完全指南
      • npm 完全指南
      • nrm 完全指南
      • nvm 完全指南
      • pnpm 完全指南
      • Yarn 完全指南
    • 并发异步(7)
      • 异步任务取消机制
      • Promise静态方法汇总
      • 封装超时工具方法
      • 并发与并行
      • 封装并发任务方法
      • 一道并发相关的面试题
      • 文件写入的原子化与并发操作
    • 打包工具(10)
      • Bun - 全栈 JavaScript 运行时与打包器
      • esbuild - 用 Go 写的极速打包器
      • Parcel - 零配置的极速打包工具
      • Rolldown - Vite 团队打造的 Rust 打包器
      • Rollup - 专注于库开发的打包工具
      • Rspack - Rust 驱动的 Webpack 替代品
      • SWC - Rust 驱动的超快编译器
      • Turbopack - Rust 驱动的下一代打包工具
      • Vite - 下一代前端构建工具
      • Webpack - 功能最强大的模块打包器
    • 算法(74)
      • 课程必看
      • 算法基本概念
      • 评判算法的好坏
      • 复习对数
      • 常见的复杂度
      • 常见算法思维
      • 递归
      • 数据结构
      • 数组结构
      • 顺序查找
      • 二分查找
      • 插值查找
      • 冒泡排序
      • 选择排序
      • 插入排序
      • 折半插入排序
      • 希尔排序
      • 归并排序
      • 快速排序
      • 链表结构
      • 双向链表
      • 单向循环链表
      • 双向循环链表
      • 栈结构
      • 队列结构
      • 循环队列
      • 击鼓传花游戏
      • 双端队列
      • 回文检查器
      • 集合结构
      • 字典结构
      • 树结构
      • 二叉树
      • 二叉树遍历
      • 前序中序还原二叉树
      • 后序中序还原二叉树
      • 二叉树深度优先搜索
      • 二叉树广度优先搜索
      • 二叉树的比较
      • 二叉树diff算法
      • 二叉搜索树
      • 二叉搜索树常见操作
      • 平衡二叉树
      • 二叉树左单旋
      • 二叉树右单旋
      • 二叉树双旋
      • 二叉堆
      • 堆排序
      • 哈夫曼树
      • 图结构
      • 邻接矩阵
      • 邻接表
      • 构建图
      • 图的深度优先搜索
      • 图的广度优先搜索
      • 普里姆算法
      • 克鲁斯卡尔算法
      • LRU算法
      • 动态规划
      • 青蛙跳台阶
      • 不同路径问题
      • 凑零钱问题
      • 打家劫舍
      • 背包问题
      • 回溯算法
      • 数字组合
      • 电话号码字母组合
      • 复原IP地址
      • 数组子集
      • 贪心算法
      • 柠檬水找零
      • 摆动序列
      • 分发糖果
      • 跳跃游戏
    • 文档协同(12)
      • 课程说明
      • 文档协同常见方案
      • OT冲突算法
      • CRDT冲突算法
      • CRDT数据结构
      • 文章翻译
      • Yjs基础介绍
      • Yjs快速上手
      • Yjs细节补充
      • 监听机制
      • 标签页同步
      • WebSocket基础知识
    • 项目实战(1)
      • ShipAny:1小时开发海外游客「中国攻略 + AI旅游助手」SaaS网站
    • AI工具(9)
      • Claude Code 完全指南
      • ComfyUI 完全指南
      • GitHub Copilot 入门指南
      • Coze 完全指南
      • Cursor vs GitHub Copilot 全面对比
      • Cursor-完整指南
      • Dify 完全指南
      • Droid 完全指南
      • n8n 完全指南
    • Astro(5)
      • Astro 入门指南
      • Astro 路由与页面
      • Astro 组件与样式
      • Astro 内容集合
      • Astro 集成与部署
    • CSS(25)
      • CSS 选择器基础
      • CSS 盒模型
      • CSS 定位与层叠
      • 浮动与 BFC
      • Flexbox 布局
      • Grid 布局
      • 响应式设计
      • 伪类与伪元素
      • 现代选择器
      • CSS 变量
      • 层叠层
      • 过渡效果
      • CSS 动画
      • 变换与滤镜
      • 容器查询
      • 文字与排版
      • 背景与渐变
      • 滚动与溢出
      • 裁剪与遮罩
      • 颜色系统
      • 边框与阴影
      • 逻辑属性
      • CSS 函数
      • 多列布局
      • 用户交互
    • ES6+(40)
      • let 和 const 命令
      • 解构赋值基础
      • 解构赋值进阶
      • 字符串的扩展
      • 字符串新增方法
      • 正则表达式扩展
      • 数值的扩展
      • BigInt 大整数
      • 函数默认参数
      • rest 参数与扩展运算符
      • 箭头函数详解
      • 尾调用优化
      • 数组扩展方法
      • 数组新增方法
      • 数组不可变方法
      • 对象的扩展
      • Object 新增方法
      • Symbol 基础
      • Symbol 进阶应用
      • Set 数据结构
      • Map 数据结构
      • TypedArray 类型化数组
      • Proxy 基础
      • Proxy 进阶应用
      • Reflect 元编程
      • 元编程实战案例
      • 装饰器 Decorator
      • Iterator 迭代器
      • Generator 基础
      • Generator 进阶
      • Promise 基础
      • Promise 进阶
      • async 函数
      • await 与错误处理
      • 异步迭代
      • Class 基础
      • Class 继承
      • Module 语法
      • Module 加载
      • ES 新特性汇总
    • HTML(8)
      • HTML 入门:构建 Web 的基石
      • HTML5 语义化:让页面更有意义
      • HTML 表单基础:用户交互的起点
      • HTML 表单进阶:验证与现代特性
      • HTML 多媒体:音视频与响应式图片
      • HTML APIs(上):客户端存储完全指南
      • HTML APIs(下):交互与设备能力
      • Canvas 与 SVG:Web 图形绘制入门
    • JavaScript(30)
      • JavaScript 简介
      • 变量与数据类型
      • 运算符
      • 类型转换
      • 控制流语句
      • 函数基础
      • 对象基础
      • 数组基础
      • 原型与原型链
      • this 关键字
      • 面向对象编程
      • 作用域
      • 闭包
      • 执行上下文
      • DOM 选择器
      • DOM 操作
      • 事件处理
      • 事件委托
      • 错误处理
      • 调试技巧
      • 正则表达式
      • Date 日期对象
      • Math 对象
      • 定时器
      • JSON 处理
      • BOM 基础
      • 本地存储
      • 表单处理
      • AJAX 与 Fetch
      • 严格模式
    • Langchain(37)
      • Langchain基本介绍
      • 模型接口
      • 缓存
      • 提示词模板
      • 结构化提示词
      • Message消息对象
      • 消息占位
      • LCEL
      • 并发流式返回结果
      • Runnable接口
      • 条件路由
      • 字符串解析器
      • 结构化输出解析器
      • 列表输出解析器
      • JSON解析器
      • 正则解析器
      • 本地资源Loader
      • Web资源Loader
      • 文本切割
      • 嵌入处理
      • 内存型向量库
      • 检索优化
      • RAG实践
      • Memory基础
      • 自动维护聊天记录
      • 实现长程记忆
      • 持久化记忆
      • 第三方存储
      • Tools基础
      • Tools配置
      • Zod基础
      • LangChain中使用工具
      • 重新认识Agent
      • LangChain中创建Agent
      • Agent实践
      • 回退机制
      • 回调机制
    • LLM(34)
      • LLM发展历史
      • N-Gram模型
      • 词袋模型
      • 词元
      • 嵌入
      • Transformer内部结构
      • Transformer解码策略
      • Transformer块
      • 本地部署大模型
      • Agent
      • 流式返回信息
      • 支持上下文
      • 获取实时消息
      • 外接DeepSeek
      • Function Calling
      • MCP理论知识
      • Resources
      • Schema
      • 注册资源模板
      • Prompts
      • 监听资源更新
      • 远程通信方式
      • MCP Client
      • 大模型幻觉
      • RCT构词法
      • 提示词常见技巧
      • 基于反馈迭代优化
      • 改善提示效果
      • 提示词评估工具
      • RAG基础知识
      • RAG实践
      • RAG架构演进
      • 大模型微调
      • LoRA微调
    • MCP(8)
      • MCP 入门:连接 AI 与外部世界的桥梁
      • 搭建第一个 MCP Server
      • MCP Tools 深入:让 AI 具备行动能力
      • MCP Resources:向 AI 暴露数据
      • MCP Prompts:预定义 AI 交互模板
      • MCP Transport 与部署
      • 构建 MCP Client:集成到你的应用
      • 实战:构建文档助手 MCP Server
    • Nest(31)
      • 快速上手Nest Cli
      • 三层架构与MVC
      • IoC
      • Nest中的装饰器
      • 模块的基本概念
      • 什么是AOP
      • 中间件与守卫
      • 拦截器
      • 文件上传
      • 自定义装饰器
      • mysql相关回顾
      • 对于ORM的理解
      • 快速上手Redis
      • Nest中使用Redis
      • 状态保存方式
      • Nest中实现Session与JWT
      • JWT登录注册后端处理
      • 使用Passport与循环引用问题
      • 通过环境变量获取配置信息
      • RBAC权限设计1
      • RBAC权限设计2
      • Nest中使用Websocket
      • 日志处理
      • 面向切面的日志处理
      • 快速入门GraphQL
      • Nest中使用GraphQL
      • Swagger API文档
      • 单元测试与E2E测试
      • 安全防护实践
      • 定时任务与任务调度
      • Bull队列与异步任务
    • Next(36)
      • Next.js 简介
      • 项目创建与配置
      • App Router 概述
      • 创建第一个页面
      • 开发工具与调试
      • TypeScript 配置
      • 文件系统路由
      • 动态路由
      • 嵌套路由与布局
      • 路由组
      • 并行路由
      • 拦截路由
      • 导航与链接
      • 中间件
      • Server Components
      • Client Components
      • 混合渲染模式
      • Streaming 与 Suspense
      • 静态与动态渲染
      • 数据获取基础
      • 缓存策略
      • Server Actions
      • Route Handlers
      • 数据库集成
      • 错误处理
      • 图片优化
      • 字体优化
      • 脚本优化
      • 元数据与 SEO
      • 性能分析
      • 认证基础
      • NextAuth 集成
      • 环境变量与配置
      • 测试策略
      • 部署方案
      • 项目实战
    • Nuxt(36)
      • web应用变迁史
      • 课程说明
      • SSR初体验
      • 水合操作
      • 改造CSR项目
      • 基本介绍
      • 配置概览
      • 组件类型
      • Nuxt模块
      • 嵌套路由
      • 动态路由
      • 请求数据
      • server目录
      • 应用部署
      • 中间件
      • 插件
      • layers
      • 过渡
      • 状态管理
      • 全匹配路由
      • 路由补充知识
      • 自定义路由
      • 自定义模块
      • 自定义指令
      • meta相关处理
      • 渲染模式
      • 错误处理
      • 不同阶段错误
      • 客户端渲染组件
      • 其余内置组件
      • 配置相关细节
      • 常用组合式函数
      • 服务器引擎
      • NuxtUI
      • SSR注意事项
      • 课程收官
    • React(55)
      • 序章:React课程介绍
      • React 基本介绍
      • JSX 基础语法
      • 组件与事件绑定
      • 组件状态与数据传递
      • 表单
      • 生命周期
      • Hooks
      • React-router介绍
      • React-router v版本学生管理系统课堂笔记
      • React-redux 介绍
      • Antd介绍
      • coder station前台系统笔记
      • coder station 后台系统笔记
      • Antd Pro介绍
      • Dva介绍
      • Umi.js 介绍
      • 属性默认值和类型验证
      • 高阶组件
      • Ref
      • Context
      • Render Props
      • Portals
      • 错误边界
      • 组件渲染性能优化
      • 前端框架的理解
      • Scheduler调度延时任务
      • 最小堆
      • React中的位运算
      • beginWork工作流程
      • completeWork工作流程
      • 图解diff算法
      • commit 工作流程
      • lane模型
      • React 中的事件
      • Hooks原理
      • React和Vue描述页面的区别
      • useState和useReducer
      • effect相关hook
      • useCallback和useMemo
      • useRef
      • Update
      • 性能优化策略之eagerState
      • 性能优化策略之bailout
      • bailout和ContextAPI
      • 性能优化对日常开发启示
      • 前端框架的分类
      • 虚拟 DOM
      • React 整体架构
      • React 渲染流程
      • Fiber双缓冲
      • MessageChannel
      • Scheduler调度普通任务
      • 课程介绍
      • 课程总结
    • ReactNative(33)
      • RN 基本介绍
      • 快速搭建 RN 开发环境
      • 复习 React 基础知识
      • RN 基础知识
      • 实战案例
      • RN 内置基础组件
      • 容器组件
      • Pressable 组件
      • 列表组件
      • 功能组件
      • 第三方组件库
      • 自定义组件案例:弹框组件
      • 自定义组件案例:单选组件
      • 基础API
      • 屏幕API
      • 设备API
      • 动画API
      • 手势API
      • React Navigation简介
      • 参数传递和标题栏信息配置
      • 嵌套路由与生命周期
      • 其他类型的导航
      • 状态管理介绍
      • Redux
      • 其他第三方库
      • Expo SDK
      • Fabric 架构
      • 渲染、提交和挂载
      • 实现跨平台
      • 扁平化视图
      • 线程模型
      • 如何打包发布应用
      • 通过 React Native CLI 搭建环境
    • TailwindCSS(7)
      • TailwindCSS基础介绍
      • 环境搭建与配置
      • 布局系统
      • 间距与尺寸系统
      • 颜色与主题
      • 响应式设计
      • 交互与动画
    • TypeScript(35)
      • 初识 TypeScript
      • 基础类型
      • 数组与元组
      • 对象类型
      • 函数类型
      • 类型推断与断言
      • 联合类型与交叉类型
      • 字面量类型与类型别名
      • 接口基础
      • 接口进阶
      • 接口继承与实现
      • 类基础
      • 类的访问修饰符
      • 类的继承
      • 类与接口对比
      • 泛型基础
      • 泛型接口与泛型类
      • 泛型约束
      • 内置工具类型(上)
      • 内置工具类型(下)
      • 泛型实战
      • 类型守卫
      • 类型收窄
      • 索引类型与索引签名
      • 映射类型
      • 条件类型
      • infer 关键字
      • 模板字面量类型
      • 模块系统
      • 命名空间
      • 声明文件基础
      • 声明文件进阶
      • tsconfig 配置详解
      • 类型体操入门
      • TypeScript 最佳实践
    • 杂谈(9)
      • Cloudflare 全栈部署指南
      • Creem 支付集成指南
      • Dokploy 自托管部署指南
      • GitHub OAuth 登录集成指南
      • Google OAuth 登录集成指南
      • Stripe 支付集成指南
      • Supabase 完全指南
      • Vercel 自动化部署指南
      • Zeabur 部署指南

    Canvas 与 SVG:Web 图形绘制入门

    2025年12月3日 · 12 min
    ChatGPTClaude

    Web 上绑制图形有两种主要技术:Canvas(位图)和 SVG(矢量)。它们各有所长,选择哪个取决于具体场景。

    🎯 Canvas vs SVG 对比

    特性CanvasSVG
    类型位图(像素)矢量(数学描述)
    缩放放大会模糊无限缩放不失真
    DOM单个元素每个图形都是 DOM 元素
    事件需手动计算坐标原生支持事件绑定
    动画需手动重绘CSS/SMIL 动画支持
    性能大量元素时更优少量复杂图形时更优
    适用场景游戏、图像处理、大数据可视化图标、图表、地图、UI 图形
    选择指南:
    │
    ├─ 需要大量图形(>1000)? → Canvas
    ├─ 需要缩放不失真? → SVG
    ├─ 需要交互(点击、悬停)? → SVG
    ├─ 实时动画/游戏? → Canvas
    ├─ 图标/Logo? → SVG
    └─ 复杂数据图表? → Canvas(性能)或 SVG(交互)

    Canvas 基础

    创建画布

    <canvas id="myCanvas" width="400" height="300">
    您的浏览器不支持 Canvas
    </canvas>
    <script>
    const canvas = document.getElementById('myCanvas')
    const ctx = canvas.getContext('2d') // 获取 2D 上下文
    </script>

    🔶 注意:Canvas 尺寸通过 HTML 属性设置,不要用 CSS(会导致缩放模糊)。

    // ✅ 正确:通过属性设置
    canvas.width = 800
    canvas.height = 600
    // ❌ 错误:通过 CSS 设置会导致缩放
    // canvas.style.width = '800px';

    坐标系统

    (0,0) ──────────────────→ x
    │
    │ Canvas 坐标系
    │ 原点在左上角
    │
    ↓
    y

    绑制基本图形

    矩形

    const ctx = canvas.getContext('2d')
    // 填充矩形
    ctx.fillStyle = '#3b82f6'
    ctx.fillRect(10, 10, 100, 50) // x, y, width, height
    // 描边矩形
    ctx.strokeStyle = '#ef4444'
    ctx.lineWidth = 2
    ctx.strokeRect(130, 10, 100, 50)
    // 清除矩形区域
    ctx.clearRect(20, 20, 30, 30)

    路径

    路径是 Canvas 绑制复杂图形的核心:

    // 开始新路径
    ctx.beginPath()
    // 移动画笔(不绑线)
    ctx.moveTo(50, 50)
    // 绑直线
    ctx.lineTo(150, 50)
    ctx.lineTo(100, 100)
    // 闭合路径
    ctx.closePath()
    // 描边
    ctx.strokeStyle = '#000'
    ctx.stroke()
    // 或填充
    ctx.fillStyle = '#3b82f6'
    ctx.fill()

    圆形和弧线

    ctx.beginPath()
    // arc(x, y, radius, startAngle, endAngle, counterclockwise)
    ctx.arc(100, 100, 50, 0, Math.PI * 2) // 完整圆
    ctx.fill()
    // 半圆
    ctx.beginPath()
    ctx.arc(250, 100, 50, 0, Math.PI)
    ctx.stroke()
    // 扇形
    ctx.beginPath()
    ctx.moveTo(400, 100) // 移动到圆心
    ctx.arc(400, 100, 50, 0, Math.PI / 2)
    ctx.closePath()
    ctx.fill()

    贝塞尔曲线

    // 二次贝塞尔曲线
    ctx.beginPath()
    ctx.moveTo(50, 200)
    ctx.quadraticCurveTo(100, 100, 150, 200) // 控制点, 终点
    ctx.stroke()
    // 三次贝塞尔曲线
    ctx.beginPath()
    ctx.moveTo(200, 200)
    ctx.bezierCurveTo(220, 100, 280, 100, 300, 200) // 控制点1, 控制点2, 终点
    ctx.stroke()

    样式与颜色

    // 填充色
    ctx.fillStyle = '#3b82f6'
    ctx.fillStyle = 'rgb(59, 130, 246)'
    ctx.fillStyle = 'rgba(59, 130, 246, 0.5)'
    // 描边色
    ctx.strokeStyle = '#ef4444'
    // 线宽
    ctx.lineWidth = 5
    // 线端点样式
    ctx.lineCap = 'butt' // 默认
    ctx.lineCap = 'round' // 圆角
    ctx.lineCap = 'square' // 方形
    // 线连接样式
    ctx.lineJoin = 'miter' // 默认,尖角
    ctx.lineJoin = 'round' // 圆角
    ctx.lineJoin = 'bevel' // 斜角
    // 虚线
    ctx.setLineDash([10, 5]) // 线长, 间隔
    ctx.lineDashOffset = 0 // 偏移

    渐变

    // 线性渐变
    const linearGradient = ctx.createLinearGradient(0, 0, 200, 0)
    linearGradient.addColorStop(0, '#3b82f6')
    linearGradient.addColorStop(1, '#8b5cf6')
    ctx.fillStyle = linearGradient
    ctx.fillRect(10, 10, 200, 100)
    // 径向渐变
    const radialGradient = ctx.createRadialGradient(100, 200, 10, 100, 200, 80)
    radialGradient.addColorStop(0, '#fbbf24')
    radialGradient.addColorStop(1, '#f59e0b')
    ctx.fillStyle = radialGradient
    ctx.beginPath()
    ctx.arc(100, 200, 80, 0, Math.PI * 2)
    ctx.fill()

    文本

    ctx.font = '24px Arial'
    ctx.fillStyle = '#000'
    ctx.textAlign = 'center' // left, center, right
    ctx.textBaseline = 'middle' // top, middle, bottom
    // 填充文本
    ctx.fillText('Hello Canvas!', 200, 50)
    // 描边文本
    ctx.strokeText('Hello Canvas!', 200, 100)
    // 测量文本宽度
    const metrics = ctx.measureText('Hello')
    console.log('文本宽度:', metrics.width)

    图像

    const img = new Image()
    img.src = 'photo.jpg'
    img.onload = () => {
    // 绘制图像
    ctx.drawImage(img, 10, 10)
    // 指定尺寸
    ctx.drawImage(img, 10, 10, 200, 150)
    // 裁剪并绘制
    // drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
    ctx.drawImage(img, 0, 0, 100, 100, 10, 10, 200, 200)
    }

    变换

    // 保存当前状态
    ctx.save()
    // 平移
    ctx.translate(100, 100)
    // 旋转(弧度)
    ctx.rotate(Math.PI / 4) // 45度
    // 缩放
    ctx.scale(2, 2)
    // 绘制
    ctx.fillRect(-25, -25, 50, 50)
    // 恢复状态
    ctx.restore()

    动画

    Canvas 动画的基本原理:清除画布 → 绘制新帧 → 重复

    const canvas = document.getElementById('myCanvas')
    const ctx = canvas.getContext('2d')
    let x = 0
    function animate() {
    // 清除画布
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    // 绘制小球
    ctx.beginPath()
    ctx.arc(x, 150, 20, 0, Math.PI * 2)
    ctx.fillStyle = '#3b82f6'
    ctx.fill()
    // 更新位置
    x += 2
    if (x > canvas.width + 20) x = -20
    // 下一帧
    requestAnimationFrame(animate)
    }
    animate()

    SVG 基础

    内联 SVG

    <svg width="200" height="200" viewBox="0 0 200 200">
    <circle cx="100" cy="100" r="50" fill="#3b82f6" />
    </svg>

    viewBox 详解

    viewBox 定义了 SVG 的坐标系统:

    <!-- viewBox="x y width height" -->
    <svg width="400" height="200" viewBox="0 0 100 50">
    <!-- 内部坐标系是 100x50,但显示为 400x200 -->
    <rect x="0" y="0" width="100" height="50" fill="#eee" />
    <circle cx="50" cy="25" r="20" fill="#3b82f6" />
    </svg>

    这实现了响应式缩放:内部图形会自动缩放以适应外部尺寸。

    基本图形

    <svg width="500" height="400" xmlns="http://www.w3.org/2000/svg">
    <!-- 矩形 -->
    <rect x="10" y="10" width="100" height="50" fill="#3b82f6" />
    <!-- 圆角矩形 -->
    <rect x="130" y="10" width="100" height="50" rx="10" ry="10" fill="#8b5cf6" />
    <!-- 圆形 -->
    <circle cx="60" cy="120" r="40" fill="#22c55e" />
    <!-- 椭圆 -->
    <ellipse cx="180" cy="120" rx="60" ry="30" fill="#f59e0b" />
    <!-- 直线 -->
    <line x1="10" y1="200" x2="200" y2="200" stroke="#000" stroke-width="2" />
    <!-- 折线 -->
    <polyline
    points="10,250 50,230 90,270 130,220 170,260"
    fill="none"
    stroke="#ef4444"
    stroke-width="2"
    />
    <!-- 多边形 -->
    <polygon
    points="60,310 10,370 110,370"
    fill="#06b6d4"
    stroke="#0891b2"
    stroke-width="2"
    />
    </svg>

    路径 <path>

    <path> 是 SVG 最强大的元素,可以绑制任意形状:

    <svg width="300" height="200">
    <path
    d="M 10 80 L 50 10 L 90 80 Z"
    fill="#3b82f6"
    stroke="#1d4ed8"
    stroke-width="2"
    />
    </svg>

    路径命令

    命令说明参数
    M/m移动到x y
    L/l直线到x y
    H/h水平线到x
    V/v垂直线到y
    Z闭合路径无
    Q/q二次贝塞尔曲线cx cy x y
    C/c三次贝塞尔曲线cx1 cy1 cx2 cy2 x y
    A/a弧线rx ry rotation large-arc sweep x y

    大写为绝对坐标,小写为相对坐标。

    <!-- 心形 -->
    <svg width="100" height="100" viewBox="0 0 32 32">
    <path
    d="M16 28 C4 18 4 8 12 6 C16 6 16 10 16 10 C16 10 16 6 20 6 C28 8 28 18 16 28"
    fill="#ef4444"
    />
    </svg>

    样式

    <svg width="200" height="200">
    <!-- 内联样式 -->
    <circle
    cx="50"
    cy="50"
    r="30"
    fill="#3b82f6"
    stroke="#1d4ed8"
    stroke-width="3"
    fill-opacity="0.5"
    stroke-dasharray="5,3"
    />
    <!-- CSS 样式 -->
    <style>
    .my-rect {
    fill: #22c55e;
    stroke: #16a34a;
    stroke-width: 2;
    transition: fill 0.3s;
    }
    .my-rect:hover {
    fill: #16a34a;
    }
    </style>
    <rect class="my-rect" x="100" y="20" width="60" height="60" />
    </svg>

    渐变和滤镜

    <svg width="200" height="200">
    <defs>
    <!-- 线性渐变 -->
    <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
    <stop offset="0%" style="stop-color:#3b82f6" />
    <stop offset="100%" style="stop-color:#8b5cf6" />
    </linearGradient>
    <!-- 径向渐变 -->
    <radialGradient id="grad2" cx="50%" cy="50%" r="50%">
    <stop offset="0%" style="stop-color:#fbbf24" />
    <stop offset="100%" style="stop-color:#f59e0b" />
    </radialGradient>
    <!-- 投影滤镜 -->
    <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
    <feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.3" />
    </filter>
    </defs>
    <rect x="10" y="10" width="80" height="60" fill="url(#grad1)" />
    <circle cx="150" cy="40" r="30" fill="url(#grad2)" />
    <rect
    x="10"
    y="100"
    width="80"
    height="60"
    fill="#3b82f6"
    filter="url(#shadow)"
    />
    </svg>

    文本

    <svg width="300" height="100">
    <text x="10" y="40" font-size="24" fill="#000">Hello SVG!</text>
    <!-- 文本路径 -->
    <defs>
    <path id="textPath" d="M 20 80 Q 150 20 280 80" fill="none" />
    </defs>
    <text font-size="16" fill="#3b82f6">
    <textPath href="#textPath">沿着曲线排列的文字</textPath>
    </text>
    </svg>

    分组和复用

    <svg width="300" height="200">
    <defs>
    <!-- 定义可复用的图形 -->
    <g id="star">
    <polygon
    points="25,0 31,18 50,18 35,29 40,47 25,36 10,47 15,29 0,18 19,18"
    fill="currentColor"
    />
    </g>
    </defs>
    <!-- 复用 -->
    <use href="#star" x="10" y="10" fill="#fbbf24" />
    <use href="#star" x="70" y="10" fill="#f59e0b" />
    <use href="#star" x="130" y="10" fill="#ef4444" />
    <!-- 分组 -->
    <g fill="#3b82f6" stroke="#1d4ed8" stroke-width="2">
    <rect x="10" y="100" width="50" height="50" />
    <rect x="70" y="100" width="50" height="50" />
    </g>
    </svg>

    SVG 动画

    CSS 动画

    <style>
    @keyframes rotate {
    from {
    transform: rotate(0deg);
    }
    to {
    transform: rotate(360deg);
    }
    }
    .spinner {
    animation: rotate 2s linear infinite;
    transform-origin: center;
    }
    </style>
    <svg width="100" height="100">
    <circle
    class="spinner"
    cx="50"
    cy="50"
    r="40"
    fill="none"
    stroke="#3b82f6"
    stroke-width="4"
    stroke-dasharray="60 200"
    />
    </svg>

    SMIL 动画

    <svg width="200" height="100">
    <circle cx="30" cy="50" r="20" fill="#3b82f6">
    <!-- 位置动画 -->
    <animate
    attributeName="cx"
    from="30"
    to="170"
    dur="2s"
    repeatCount="indefinite"
    />
    </circle>
    <rect x="10" y="70" width="30" height="20" fill="#22c55e">
    <!-- 颜色动画 -->
    <animate
    attributeName="fill"
    values="#22c55e;#16a34a;#22c55e"
    dur="1s"
    repeatCount="indefinite"
    />
    </rect>
    </svg>

    🎯 实战:简单柱状图

    用 Canvas 实现一个柱状图:

    <!doctype html>
    <html lang="zh-Hans">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>柱状图</title>
    <style>
    body {
    font-family: system-ui, sans-serif;
    padding: 2rem;
    }
    canvas {
    border: 1px solid #e2e8f0;
    }
    </style>
    </head>
    <body>
    <h1>月度销售数据</h1>
    <canvas id="chart" width="600" height="400"></canvas>
    <script>
    const canvas = document.getElementById('chart')
    const ctx = canvas.getContext('2d')
    const data = [
    { label: '1月', value: 65 },
    { label: '2月', value: 78 },
    { label: '3月', value: 90 },
    { label: '4月', value: 81 },
    { label: '5月', value: 95 },
    { label: '6月', value: 110 },
    ]
    const padding = 50
    const chartWidth = canvas.width - padding * 2
    const chartHeight = canvas.height - padding * 2
    const barWidth = (chartWidth / data.length) * 0.6
    const barGap = (chartWidth / data.length) * 0.4
    const maxValue = Math.max(...data.map((d) => d.value))
    // 绘制坐标轴
    ctx.beginPath()
    ctx.moveTo(padding, padding)
    ctx.lineTo(padding, canvas.height - padding)
    ctx.lineTo(canvas.width - padding, canvas.height - padding)
    ctx.strokeStyle = '#64748b'
    ctx.lineWidth = 2
    ctx.stroke()
    // 绘制刻度线和标签
    ctx.font = '12px Arial'
    ctx.fillStyle = '#64748b'
    ctx.textAlign = 'right'
    for (let i = 0; i <= 5; i++) {
    const y = canvas.height - padding - (chartHeight / 5) * i
    const value = Math.round((maxValue / 5) * i)
    // 刻度线
    ctx.beginPath()
    ctx.moveTo(padding - 5, y)
    ctx.lineTo(padding, y)
    ctx.stroke()
    // 标签
    ctx.fillText(value.toString(), padding - 10, y + 4)
    // 网格线
    ctx.beginPath()
    ctx.moveTo(padding, y)
    ctx.lineTo(canvas.width - padding, y)
    ctx.strokeStyle = '#e2e8f0'
    ctx.lineWidth = 1
    ctx.stroke()
    ctx.strokeStyle = '#64748b'
    ctx.lineWidth = 2
    }
    // 绘制柱子
    const colors = [
    '#3b82f6',
    '#22c55e',
    '#f59e0b',
    '#ef4444',
    '#8b5cf6',
    '#06b6d4',
    ]
    data.forEach((item, index) => {
    const x = padding + index * (barWidth + barGap) + barGap / 2
    const height = (item.value / maxValue) * chartHeight
    const y = canvas.height - padding - height
    // 渐变填充
    const gradient = ctx.createLinearGradient(x, y, x, y + height)
    gradient.addColorStop(0, colors[index])
    gradient.addColorStop(1, colors[index] + '99')
    ctx.fillStyle = gradient
    ctx.fillRect(x, y, barWidth, height)
    // 数值标签
    ctx.fillStyle = '#1f2937'
    ctx.font = 'bold 14px Arial'
    ctx.textAlign = 'center'
    ctx.fillText(item.value.toString(), x + barWidth / 2, y - 10)
    // X轴标签
    ctx.fillStyle = '#64748b'
    ctx.font = '12px Arial'
    ctx.fillText(item.label, x + barWidth / 2, canvas.height - padding + 20)
    })
    // 标题
    ctx.fillStyle = '#1f2937'
    ctx.font = 'bold 16px Arial'
    ctx.textAlign = 'center'
    ctx.fillText('2024年上半年销售额(万元)', canvas.width / 2, 25)
    </script>
    </body>
    </html>

    SVG 图标示例

    <!-- 首页图标 -->
    <svg
    width="24"
    height="24"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
    <polyline points="9 22 9 12 15 12 15 22" />
    </svg>
    <!-- 搜索图标 -->
    <svg
    width="24"
    height="24"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <circle cx="11" cy="11" r="8" />
    <line x1="21" y1="21" x2="16.65" y2="16.65" />
    </svg>
    <!-- 关闭图标 -->
    <svg
    width="24"
    height="24"
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    stroke-width="2"
    stroke-linecap="round"
    stroke-linejoin="round"
    >
    <line x1="18" y1="6" x2="6" y2="18" />
    <line x1="6" y1="6" x2="18" y2="18" />
    </svg>

    常见问题

    🤔 Canvas 模糊怎么解决?

    高 DPI 屏幕需要处理设备像素比:

    const dpr = window.devicePixelRatio || 1
    canvas.width = 400 * dpr
    canvas.height = 300 * dpr
    canvas.style.width = '400px'
    canvas.style.height = '300px'
    ctx.scale(dpr, dpr)

    🤔 如何在 Canvas 上实现点击事件?

    Canvas 是一个整体,需要手动计算坐标:

    canvas.addEventListener('click', (e) => {
    const rect = canvas.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top
    // 判断是否在某个图形内
    if (x > 10 && x < 110 && y > 10 && y < 60) {
    console.log('点击了矩形区域')
    }
    })

    🤔 SVG 作为背景图怎么用?

    /* 外部文件 */
    .icon {
    background-image: url('icon.svg');
    }
    /* 内联(需要 URL 编码) */
    .icon {
    background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg">...</svg>');
    }

    参考资料

    • MDN: Canvas API
    • MDN: SVG 教程
    • SVG Path Editor - 可视化路径编辑器
    • Feather Icons - 开源 SVG 图标库
    返回 BlogHTML
    上一篇HTML APIs(下):交互与设备能力下一篇JavaScript 简介
    Skip toc
    • 🎯 Canvas vs SVG 对比
    • Canvas 基础
      • 创建画布
      • 坐标系统
      • 绑制基本图形
      • 样式与颜色
      • 渐变
      • 文本
      • 图像
      • 变换
      • 动画
    • SVG 基础
      • 内联 SVG
      • viewBox 详解
      • 基本图形
      • 路径 <path>
      • 样式
      • 渐变和滤镜
      • 文本
      • 分组和复用
      • SVG 动画
    • 🎯 实战:简单柱状图
    • SVG 图标示例
    • 常见问题
      • 🤔 Canvas 模糊怎么解决?
      • 🤔 如何在 Canvas 上实现点击事件?
      • 🤔 SVG 作为背景图怎么用?
    • 参考资料
    Table of Contents
    • 🎯 Canvas vs SVG 对比
    • Canvas 基础
      • 创建画布
      • 坐标系统
      • 绑制基本图形
      • 样式与颜色
      • 渐变
      • 文本
      • 图像
      • 变换
      • 动画
    • SVG 基础
      • 内联 SVG
      • viewBox 详解
      • 基本图形
      • 路径 <path>
      • 样式
      • 渐变和滤镜
      • 文本
      • 分组和复用
      • SVG 动画
    • 🎯 实战:简单柱状图
    • SVG 图标示例
    • 常见问题
      • 🤔 Canvas 模糊怎么解决?
      • 🤔 如何在 Canvas 上实现点击事件?
      • 🤔 SVG 作为背景图怎么用?
    • 参考资料
    > share on twitter