2026年3月

刚看到隔壁帖子说毕业的学校从 xx 学院变成了大学,就很好奇是不是现在 xx 学院这种二本院校在就业市场约等于大专了?有 v 友们现身说法么?

一些未经证实的猜测:

项目参数
总参数量~1 万亿 (1 Trillion)
激活参数量320 亿 (32B) - 仅 3% 激活率
上下文窗口100 万 (1M) Tokens
多模态能力文本 + 视觉 + 音频 (Audio)
开源许可MIT 协议(极致开放)
硬件优化深度适配 华为昇腾 910C

看了下公众号,V4 还是没发布,发布了估计也是媒体到处宣扬的。从过年到现在一直有不少的消息说下周发,到现在还是没有,你们觉得会在这个月发布吗?我看发帖时有竞猜我加上给你们猜猜看 🤣,话说做庄有收益不

大家好,我是 Java陈序员

在快节奏的生活里,我们常常被焦虑、失眠、注意力不集中困扰。想睡个安稳觉,想安安静静专注一会儿,想给自己一段不被打扰的放松时光,却总被各种杂音打断。

今天,给大家介绍一款开源免费的安卓应用,专注于白噪音播放!

关注微信公众号:【Java陈序员】,获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。

项目介绍

XMSLEEP —— 一个专注于白噪音播放的 Android 应用,提供多种自然声音帮助您放松、专注和入眠。应用采用 Material Design 3 设计规范,界面简洁美观,操作流畅。

功能特色

  • 内置多种白噪音:提供雨声、篝火、雷声、森林鸟鸣、猫咪呼噜、键盘等多种自然/场景音效,分类清晰(自然/雨声/城市/河流等),无需联网直接播放
  • 音频拓展无上限:音效库随心扩展,不仅支持从 GitHub 动态加载网络音频资源,也可播放手机本地音频文件
  • 沉浸式无缝循环:音频支持无缝循环播放,提供沉浸式体验
  • 实用功能:支持设置自动停止播放的时间,支持预设、收藏、最近播放等功能
  • 精美界面:采用最新的 Material Design 3 设计规范,内置声音配有 webp 动画,支持多种颜色主题,支持浅色/深色模式切换

技术栈Kotlin + Jetpack Compose + Material Design 3 + ExoPlayer/Media3

快速上手

项目已经提供构建好的安装包,只需下载安装即可使用。

1、打开下载地址,下载安装包

https://github.com/Tosencen/XMSLEEP/releases

2、在安卓手机中运行安装包进行安装

功能体验

  • 内置白噪音

  • 预设

  • 设置倒计时

  • 远程白噪音

  • 设置

本地开发

1、环境要求:

  • Android Studio
  • JDK:17+
  • Android SDK: API33+
  • Gradle:8+

2、克隆或下载项目源码

git clone https://github.com/Tosencen/XMSLEEP.git
cd XMSLEEP

3、配置 Gradle:复制 gradle.properties.examplegradle.properties

4、使用 Android Studio 打开项目并同步 Gradle 依赖

5、连接设备或启动模拟器,运行项目

可以说,XMSLEEP 作为一款简单的白噪音播放工具,没有广告,不用联网,无需付费,能在你失眠、疲惫、需要专注的时候,提供最治愈的声音陪伴。快去下载安装体验吧~

项目地址:https://github.com/Tosencen/XMSLEEP

最后

推荐的开源项目已经收录到 GitHub 项目,欢迎 Star

https://github.com/chenyl8848/great-open-source-project

或者访问网站,进行在线浏览:

https://chencoding.top:8090/#/

我创建了一个开源项目交流群,方便大家在群里交流、讨论开源项目

但是任何人在群里打任何广告,都会被 T 掉

如果你对这个交流群感兴趣或者在使用开源项目中遇到问题,可以通过如下方式进群

关注微信公众号:【Java陈序员】,回复【开源项目交流群】进群,或者通过公众号下方的菜单添加个人微信,并备注【开源项目交流群】,通过后拉你进群

大家的点赞、收藏和评论都是对作者的支持,如文章对你有帮助还请点赞转发支持下,谢谢!

打算买一个 Macbook Air M5 ,一边当自己的主力笔记本,另外来学习 AI Coding 。
请教下内存和硬盘选择多大适合呢?
问了下 GPT 说是 16+512 的配置对于初学者来说够用了。
如果是开发原生应用( Mac 、iOS 、Android )这类的话,是不是还是要上到 32+1TB 才行呢?

编程 Agent 会分析/解决问题,但不会积累经验。它们有印象,但没有笔记。RadioHeader 给 Claude Code 补上了这一层。


先说个真事儿

我用 Claude Code 做 iOS 的时候,遇到过一次 app 启动白屏几十秒的问题。Claude 一路查日志、翻文件、试各种排查,折腾了很久都没定位到。最后还是我想起来,另一个项目以前也出过差不多的事,让它去翻那个项目的旧记录,才找到根因——Xcode 的 scheme 设置问题。

那一刻我就很确定一件事:像 Claude Code 这样的编程 Agent ,分析问题、写代码都很强,但它们不会共享项目经验。后来 Claude Code 虽然也加了 memory ,但本质问题没变——它记得方向,记不住关键细节,更别说跨项目复用了。

项目之间的记忆完全隔离,就意味着项目 A 踩过的坑,项目 B 根本不知道。如果我自己记得”我好像遇到过这个问题”,还能手动指路,要是我也忘了呢?

现有的工具几乎都是在做"规则"层面的事。规则当然有用,但规则无很难达到“这个坑我真的踩过,而且已经知道怎么修"这种程度。做硬件产品的时候我也经常遇到这类"认知型经验"——不是文档里写了你就会的,得自己撞上、解决了,才真正印象深刻。

在没找到做编程 agent”经验"层面的工具的情况下,我自己做了一个。


RadioHeader 不是一开始设计好的,是被真实问题一步步逼出来的。

第一步:让 Agent 学会记笔记

起点是单项目内的记忆缺失。Claude Code 在一个项目里工作久了,早期的经验就开始模糊。它记得"之前遇到过类似问题",但记不住具体怎么解决的。

换句话说:Agent 需要的是笔记,不是印象。 自带的 memory 记的是大致印象,而能被精确搜索到的结构化笔记才有复用价值。

我的想法很朴素:让 Claude 每次解决完问题后,主动把经验记下来。具体分两种:一种是经验条目,写进 memory/ 目录——根因、修法、注意事项,按主题组织,方便以后检索;另一种是任务日志,写进 logs/ 目录——以一个问题的解决为单位,记录背景、过程、结论,保留完整上下文。日志的文件名经过设计:日期-主题-撰写者,Agent 和人类都要署名,标清楚方便追溯。起初我会在关键节点手动提醒 Claude"同步一下项目信息",后来逐渐做成了自动化。

我把这个机制叫经验回流——完成一个任务后,经验自动流回记忆系统。每次修完 bug 、做了架构决策、踩了非直觉的坑,Claude 都把经验写回去。用 Claude Code 的 hook 机制让这个过程自动:PostToolUse hook 在写入 memory 时触发检查,Stop hook 在会话结束时提醒有没有新经验遗漏。

同一个项目里,以前要重新排查的问题,现在翻笔记就能解决。

但很快就遇到了开头那个场景:项目 A 的笔记,项目 B 搜不到。

第二步:打通项目之间的壁垒

于是我在所有项目之上建了一个全局经验中枢:~/.claude/radioheader/

名字的由来——我喜欢 Radiohead 这个乐队,而这个全局中枢又像一个信号塔,各个项目需要的时候就从里面接收信息。RadioHeader ,就这么出现了。

架构变成了三层:

┌─────────────────────────────────┐
│  RadioHeader (全局经验中枢)      │  ← 所有项目共享
├─────────────────────────────────┤
│  项目 memory/(项目专属记忆)     │  ← 当前项目
├─────────────────────────────────┤
│  会话上下文(临时)               │  ← 当前对话
└─────────────────────────────────┘

每次经验回流时,Claude 会顺手判断一下:这条经验是不是跨项目通用的?如果是,写入全局层,标注来源项目。之后任何项目遇到类似问题,先搜这里。有了 RadioHeader 这个信号塔的隐喻之后,我把经验回流叫做 Echo(回波)——经验像信号的回波一样,从项目返回信号塔。

装了之后再遇到白屏问题:

你:App 启动白屏 10 秒以上

Claude:RadioHeader 中有来自 ProjectA 的经验:
        "Xcode scheme 的 Launch 配置导致 iOS app 启动白屏,
        检查 scheme 设置中的相关选项……"

        验证一下是否适用…… ✓ 同样的模式。正在应用修复。

不用我手动指路了。同一类问题从第二次开始,解决时间从分钟级掉到秒级。不是 AI 变聪明了,是答案已经在那里了。

精炼:从项目经验到通用知识

跨项目共享之后冒出一个新问题:经验一旦放到全局以后,原始条目里保留的项目上下文太多,搜索噪音和 token 消耗都上来了。比如一条经验写着 [来源:DarkWriting] iCloud I/O 阻塞主线程,另一个项目用的是 CoreData 不是 iCloud——但根因相同。项目名和具体技术细节反而成了搜索障碍。

于是我加了一层精炼:短波( Shortwave )——去掉项目名、文件路径、框架细节,只保留通用知识。这不光是为了降噪,也是为了保护隐私——经验条目在精炼之前可能包含项目路径、内部命名甚至 API key ,短波会把这些全部剥离。

这样三层就接上了:经验先通过 Echo 回到 RadioHeader,再由 Shortwave 去掉项目噪音,变成可广播的通用知识。

---
id: sw-ios-task-inherits-mainactor
domain: iOS, SwiftUI, Concurrency
tags: 白屏 | 启动慢 | white screen | slow launch | 10s+ | Main Actor | Task
---
### Task {} 在 @MainActor 上下文中继承主线程,I/O 阻塞导致白屏

symptoms: 应用启动后 10s+ 白屏,首次加载卡死
cause: Task {} 在 @MainActor 标记的上下文中创建时继承主线程
fix: 使用 Task.detached(priority:) 将 I/O 操作移出主线程

注意 tags 里的"白屏"、"启动慢"、"slow launch"——这些是开发者实际会搜的词。如果只保留 "Task.detached" 这种解法关键词,这条经验就搜不到了。症状关键词比解法关键词重要得多。 经验条目删掉了症状词就等于不存在。

比搜不到更烦的,是搜到了也不用

到这里遇到了另一个让人头疼的问题:Claude 搜到了经验,但不用。

早期我在 CLAUDE.md 里写:"遇到技术问题时先搜索 RadioHeader"。Claude 确实搜了,也找到了相关结果——然后完全忽略,直接跳进独立分析。就像你给新同事一本 wiki ,他打开看了一眼,还是选择自己摸索。

后来搞明白了:行为指令比知识描述有效得多。CLAUDE.md 里写"这里有个东西可以查"没用,得写"你必须搜,搜到必须引用,禁止搜到不用"才行。这两种写法在 Agent 系统中效果差很远。

最终方案是"搜→用→追"三步强制规则:

  1. :遇到技术问题,先搜 RadioHeader
  2. :搜到相关经验,必须引用并验证
  3. :需要更多细节,追溯到来源项目的 memory

外加一句禁令:"禁止搜到相关经验却不引用、不应用,直接跳过去做独立分析"——这句话比前面三条都管用。

第三步:从个人记忆到社区共享

前两步解决了自己不重复踩坑的问题,但我的经验仍然是一座孤岛。我遇到的问题,世界上某个人在开发中一定早就有经验了;反过来,我踩过的坑可能也正好能帮到别人。

能不能把精炼好的短波共享出去?难题不在技术,在质量治理——不能什么都往池子里丢,但我一个人也做不了人工审核。

当时我想到试试从生物学里找找灵感,结果还真找到了一个概念——Stigmergy (痕迹协作)。这是蚁群行为学里的东西:蚂蚁在路径上留信息素,走的越多越浓,没蚂蚁走的自然蒸发。不需要谁来管理,好路径自己就显现出来了。

用这个思路做知识共享的质量治理:

  • 自动投票:Claude 用了社区短波解决问题后,自动判断这条经验是不是真帮上忙了,+1 或 -1
  • 时间衰减:分数随时间下降(半衰期约 125 天),过时的经验自然淘汰(需要验证一下效果)
  • 每周清洗:GitHub Actions 聚合投票,高分标记 verified,低分归档

发布也有门槛——质量评分(≥6/8 )、隐私扫描(确保没泄露路径和密钥)、去重检查。不需要管理员,好经验自己浮上来,差经验自己沉下去。

目前的状态

我不是做完一个 demo 就拿出来讲故事,这套东西已经在我自己手里滚了几个月。13 个项目,覆盖 iOS/SwiftUI 、Rust 、后端部署、网络代理、AI API 、Claude Code 、硬件产品 7 个技术领域。205 条原始经验,120 条精炼短波,114 条发布到社区池。(截止发稿时)

试试看

不用先研究完整设计,找一个你最近最常重复踩坑的项目,装上跑一周就知道它有没有用。

git clone https://github.com/ZaptainZ/radioheader.git
cd radioheader
./install.sh

之后在任何项目中启动 Claude Code 就生效了。

# 开启社区共享
radioheader community on
radioheader sync

GitHub:ZaptainZ/radioheader


RadioHeader 还在继续打磨,但它已经在我自己的真实项目里跑出价值了,所以我把它公开出来。开源社区帮了我很多,这次也算把自己真用出来的一套东西拿出来回馈。

MIT 协议。你要是也受不了重复踩坑,直接装上试试。有用欢迎 star ,没用也欢迎来 issue 吐槽。

AI 擅长执行明确指令,但不擅长理解复杂的业务场景、做需要权衡的技术选型、跨团队的沟通协调、从 0 到 1 的产品定义。这些“模糊地带”才是 AI 时代程序员的护城河。越靠近执行层越危险,越靠近决策层越安全。

昨天 claude 和 pro 刚刷新,昨晚看还是满的,今天看直接干周限了。

antigravity 官方给出的说法是,pro 就是让你尝尝鲜的,额外给了一个 1000 ai credit 的额度。可以应急使用,但是这个额度消耗具体规则也没给出来。

同时额度好像还是家庭组共享的,组 team 车的有点麻麻的。

原文:
We’re evolving Google AI plans to give you more control over how you build. Every subscription includes built-in AI credits, which can now be used for Antigravity, giving you a seamless path to scale.

Google AI Pro is the home for the practical builder, hobbyists, students, and developers who live in the IDE and don't necessarily rely on an agent. This plan features generous limits for Gemini Flash, with a baseline quota included to "taste test" our most advanced premium models.

Google AI Ultra serves as the daily driver for those shipping at the highest scale who need consistent, high-volume access to our most complex models.

If you’re on Pro but need "extra juice" for a heavy sprint or deeper access to premium models, simply top up your AI credits to customize your plan.

Keep building. Keep shipping.

我们每个人,都会死去,都会离开,会被遗忘,所有的一切,最后都会归零。

但人生,其实也在不断地重启、格式化、重装系统、恢复出厂设置。 📲📲📲

人生也能重装系统


人生也能重装系统

早上开车上班,随机播放到某一首歌。歌本身一般般,不作为特别推荐,可以当成 BGM 随便听听,但里面几句歌词,确实一下子戳到我了。

  • 周深深《把每天当作最后一天》

歌词很直白,说的很简单,甚至有点残酷:我们每个人,终究都会死去,都会离开,都会被遗忘,所有的一切,最后都会归零。

就这么一首歌,我在路上循环听了好几遍,越想越对。突然就觉得,人生这事,跟我们平时 给电脑重装系统、给手机恢复出厂设置,简直一模一样。

640

经常折腾电脑、手机的兄弟,应该一下就能听懂我在说什么。

每次准备重装系统前,我们都会先做一件事:备份重要文件,整理资料,做好启动 U 盘,把该保留的都留好。

但我还有个习惯:在一切准备就绪、马上就要格式化之前,我会放开手脚,随便折腾。

平时不敢装的软件,怕它乱要权限、乱改系统,这会儿直接装上试试;平时一直用 A 方案,觉得 B 是替代品懒得换,这会儿也装上体验对比一下,说不定更好用;甚至那些担心带病毒、有风险的黑客工具,也敢拿出来玩一玩。

反正系统马上就要清空了,坏了也无所谓,乱了也不心疼。

等折腾够了,点一下格式化,一切清零,重新装一个干净、清爽、流畅的新系统。

手机恢复出厂设置也是一样的逻辑:先把通讯录、照片、聊天记录备份好,剩下的就随便造。

下几个平时不玩的手游,装几个好奇但不敢用的 APP,卡一点、慢一点都没关系。体验完,一键恢复出厂,又是一台干干净净的新手机。

640 (1)

那一刻我突然觉得:人生,其实也一直在不断地重启、格式化、重装系统。

往大了说:考上大学、换一份新工作、搬到一座新城市、结婚、成为父母。。。每一次人生的重大节点,我们心里都会冒出一个念头 —— 「人生翻篇,重新开始」。这就是人生的 重装系统。把过去的美好留在备份里,把不开心、内耗、遗憾,全部当成垃圾文件删掉,轻装上阵。

往小了说:每年元旦、春节,我们都会说 —— 「新的一年,从头开始」;哪怕只是一天之内,心情不好、烦躁、沮丧,吃顿好的,再睡一觉,第二天醒来,心里那股阴霾散了,人又精神了。这就是最小单位的 恢复出厂设置

640 (2)

今天开车那一路,我脑子里全是这个比喻。越想越觉得轻松,越想越觉得没什么大不了。

人生没有那么多过不去的坎,也没有那么多盯着你看的观众。你做过的尴尬事、搞砸的瞬间、放不下的包袱、内耗的情绪。。。其实都可以像系统垃圾一样,一键清理。

真正想通 「一切终将归零」 这件事,反而不是消极,而是解脱。

既然最后都要重启,那现在何必那么紧绷?何必那么在意别人的眼光?何必揪着过去不放?

人生可以随时按一下 Reset 键。 每一天,都可以是一个全新安装、干干净净、没有冗余、没有卡顿的新系统。

放下包袱,轻装上阵。往后的日子,我们都好好重启,好好做自己。

  • 周深深《把每天当作最后一天》

人生不过三万天 出去看看这世界
请勇敢一点 我们只活这一遍
和过去告别 拥抱更好的明天
人生的意义 该由自己来挑选


全文链接 发布新帖子 - 分享你的想法 - 2Libra

架构师的核心思维方式是其进行系统架构设计和决策的关键,除了上文提到的设计思维特点外,还包括以下几种核心思维方式。

问题定义思维

架构师设计系统时,应善于发现问题、定义问题。

  • 精准识别问题:架构师要能够从复杂的业务场景和技术环境中,精准地识别出真正的问题所在。不能仅仅停留在问题的表面现象,而是要深入挖掘问题的本质。例如,当用户反馈系统响应速度慢时,不能简单地认为是服务器性能问题,而要通过深入分析,可能发现是数据库查询语句不合理、网络延迟、系统架构设计导致的资源竞争等深层次问题。
  • 清晰界定问题边界:明确问题的范围和边界,确定哪些因素与问题相关,哪些是无关的干扰因素。这有助于将问题聚焦,避免在解决问题的过程中陷入无关的细节和复杂性中。比如,在设计一个物流配送系统时,需要明确问题是关于配送路线规划的优化,还是库存管理与配送的协同,抑或是配送人员的调度管理,不同的问题边界决定了不同的架构设计方向。

数据驱动思维

架构师设计系统时,应善于利用数据。

  • 重视数据收集:意识到数据在架构设计中的重要性,主动收集与系统相关的各种数据,包括业务数据、用户行为数据、系统性能数据等。这些数据是架构师了解系统运行状况、用户需求和业务流程的重要依据。例如,在设计一个在线教育平台时,会收集学生的学习进度数据、课程播放时长数据、用户互动数据等,以便更好地了解用户行为和需求,为架构设计提供支持。
  • 基于数据决策:在进行架构设计和优化时,依靠数据进行分析和判断,而不是仅凭经验或直觉。通过对数据的分析,评估不同架构方案的可行性和优缺点,选择最优的方案。比如,通过对系统性能数据的分析,确定是否需要增加缓存机制、调整数据库架构或优化网络拓扑结构等。

复用思维

架构师设计系统时,要有复用思维,减少“重复造轮子”。

  • 识别可复用元素:在进行架构设计时,能够敏锐地识别出系统中具有复用潜力的功能、模块、组件或代码。这些可复用元素可以是已经存在的成熟技术框架、开源组件,也可以是企业内部以往项目中积累的经验和资产。例如,在多个不同的业务系统中,可能都需要用户认证和授权功能,架构师可以将这部分功能提取出来,设计成一个可复用的身份认证模块。
  • 设计可复用架构:不仅仅是对现有元素的复用,还会在架构层面进行设计,使系统具有良好的可复用性和扩展性。通过采用模块化、分层架构、接口化等设计原则,将系统设计成由多个可独立复用的模块组成,每个模块具有明确的职责和功能,模块之间通过清晰的接口进行交互。这样,在未来的项目中,可以方便地复用这些模块,提高开发效率,降低成本。

创新思维

创新思维可以让架构始终保持生命力。

  • 突破传统局限:不局限于传统的架构设计模式和方法,敢于尝试新的技术和理念,突破现有的思维定式和技术瓶颈。例如,在面对大规模数据处理问题时,不局限于传统的关系型数据库架构,而是尝试采用分布式数据库、大数据处理框架等新技术,以实现更高效的数据处理和分析。
  • 创造独特价值:通过创新的架构设计,为系统带来独特的价值和竞争优势。可以是提高系统的性能、降低成本、提升用户体验,也可以是实现一些独特的功能,满足市场上尚未被满足的需求。比如,在设计一个社交媒体平台时,通过创新的架构设计,实现了更高效的内容推荐算法,为用户提供了更个性化的内容推荐,从而吸引了更多的用户,提升了平台的竞争力。

风险管理思维

风险管理可以让架构能够抵御不确定因素带来的影响,有时这些影响甚至是致命的。

  • 风险识别:在架构设计的各个阶段,能够全面地识别出可能存在的风险,包括技术风险、业务风险、市场风险等。技术风险可能包括新技术的不成熟、技术选型的不匹配等;业务风险可能包括业务需求的频繁变更、业务流程的不合理等;市场风险可能包括竞争对手的压力、市场趋势的变化等。例如,在采用一种新的区块链技术进行金融系统架构设计时,要识别出该技术可能存在的性能瓶颈、安全漏洞以及监管政策的不确定性等风险。
  • 风险应对:针对识别出的风险,制定相应的风险应对策略。对于可以接受的风险,制定监控和应急措施;对于可能对系统产生重大影响的风险,要采取措施进行规避或降低风险的发生概率和影响程度。比如,为了应对业务需求变更的风险,可以采用敏捷开发方法,在架构设计中增加灵活性和可扩展性,以便能够快速响应需求的变化;为了应对技术风险,可以进行技术预研和原型验证,选择更成熟、稳定的技术方案。

参考引用

加入鸿蒙生态,共建万物互联。以下是鸿蒙应用开发常用教程。

需求是给不喜欢折腾的女朋友每天自动创建一个第二天上午第一堂课前一小时的闹钟
我已经把课程表做成 ics 文件导入到日历里了
我很久没用过安卓了,本来以为像快捷指令一样很好做,结果研究到现在也没好使
首先测试了系统自带的智慧助手-智慧场景:没有根据日历来触发的,而且也没有条件语句
然后按照 gpt 推荐测试了 MacroDroid ,安装时就废了点劲,然后也是没找到日历(包括其他推荐的如 IFTTT 、tasker 这些软件都没有上架华为的应用商店?也没在商店里看到类似的软件
而且这些第三方软件我不是很放心,MacroDroid 自己也提示了可能会杀后台之类的,加上女朋友不会也不愿意每天检查这些软件的存活状态并在出现问题时进行修复

所以想请教一下有没有什么稳定的方式来实现这个需求,不局限于每天检查第二天日程的思路,希望大家不吝赐教

我已经用快捷指令写好了这套逻辑,保底的方案就是用我手机上的闹钟,但是现在不是很稳定,有时候会异地,或者用旧手机当闹钟,但是她也可能忘记充电:(

地库里经常会有流浪猫,有时候会爬到车前盖或者车顶,留下一串脚印,其实这倒也没啥,但是今早上出门的时候,发现左后轮附近有 n 撮猫毛,没养过猫,不知道是猫自己把毛弄下来的还是两只猫在这里打了架,轮胎上还有不明液体,同样也不知道是被猫尿了还是啥,觉得有点苦恼,爬车我没太所谓,但是这 n 撮猫毛还有不明液体,属实有点难顶了

我对面是隔壁组的外包人员,大概来了有四个月了,刚刚他们组长过来跟他说话,我把对话内容捋一下。

a:你现在手上活都干完了吗
b:干完了
a:那你今天不用来了,跟你们那边说一下
b:收拾东西准备起身
a:有没有什么要交接的,要交接一下
b:你不是让我走吗,我今天来就是来交接的
a:那你先别走,今天上午交接一下

我在旁边听着挺不爽的。1.外包真的是召之即来挥之即去?不用提前告知沟通下? 2.让人家走没有更体面的方式?不能进小隔间说或者提前说,或者线上说,有必要当着大家面这样?

点赞 + 关注 + 收藏 = 学会了

html2canvas 简介

html2canvas 是一个纯前端的 JavaScript 库,能直接在浏览器里把 HTML 元素 “拍” 成 Canvas 图片。不用麻烦后端接口,就能实现网页截图、生成分享海报、保存页面快照等需求。

安装方法

html2canvas 支持两种常见的安装方式,根据你的项目环境选就行:

1. npm /yarn 安装(推荐)

如果你的项目用了 Webpack、Vite 等构建工具,直接用包管理器安装:

# npm 安装
npm install html2canvas

# yarn 安装
yarn add html2canvas

本文使用 Vite 创建 Vue3 项目,通过 Vue3 的语法来讲解。在其他框架使用 html2canvas 用法也是差不多的。

2. CDN 引入

如果是简单的 HTML 项目,直接在页面里引入 CDN 链接:

<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>

基础用法

先看一个最简单的例子:点击按钮,把指定的 div 转成 Canvas 并显示在页面上。

<template>
  <div>
    <!-- 要截图的区域 -->
    <div ref="captureRef" style="padding: 20px; background: #f0f0f0;">
      <p style="font-size: 16px; margin: 0;">随便写点什么,html2canvas 会把它变成图片~</p>
    </div>

    <!-- 截图按钮 -->
    <button @click="handleCapture">点击截图</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import html2canvas from 'html2canvas'

// 获取要截图的 DOM 元素
const captureRef = ref(null)

// 截图函数
const handleCapture = async () => {
  // 调用 html2canvas,等待生成 Canvas
  const canvas = await html2canvas(captureRef.value)
  // 把 Canvas 加到页面上看看效果
  document.body.appendChild(canvas)
}
</script>

这段代码看似简单,但第一个坑也出现了。

此时如果在“截图区”加多一个 h2,结果可能会出乎你所料。

<!-- 要截图的区域 -->
<div ref="captureRef" style="padding: 20px; background: #f0f0f0;">
    <h2>雷猴</h2>
    <p style="font-size: 16px; margin: 0;">随便写点什么,html2canvas 会把它变成图片~</p>
</div>

<h2> 标题的样式没跟着生成出来啊。。。

解决方法很简单,不使用浏览器默认样式,给所有标签都加上指定样式。

<template>
  <div>
    <!-- 要截图的区域 -->
    <div 
      ref="captureRef" 
      class="capture-area" 
      style="padding: 20px; background: #f0f0f0;"
    >
      <!-- 关键样式建议写在行内,或者确保非 scoped -->
      <h2 class="title">这是要截图的内容</h2>
      <p style="font-size: 16px; margin: 0;">随便写点什么,html2canvas 会把它变成图片~</p>
    </div>

    <!-- 截图按钮 -->
    <button @click="handleCapture">点击截图</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import html2canvas from 'html2canvas'

// 获取要截图的 DOM 元素
const captureRef = ref(null)

// 截图函数
const handleCapture = async () => {
  // 调用 html2canvas,等待生成 Canvas
  const canvas = await html2canvas(captureRef.value)
  // 把 Canvas 加到页面上看看效果
  document.body.appendChild(canvas)
}
</script>

<style scoped>
.capture-area {
  width: 400px;
  box-sizing: border-box;
  border: 1px solid #ccc;
}
.title {
  font-size: 24px;
  font-weight: bold;
  margin: 0 0 10px 0;
}
</style>

常用配置选项

html2canvas 提供了很多配置项,能帮你解决各种场景问题,这里列几个最常用的:

配置项类型默认值说明
scalenumberwindow.devicePixelRatio缩放比例,默认用设备像素比,提高截图清晰度(设为 1 会模糊)。
useCORSbooleanfalse是否允许加载跨域图片,设为 true 才能正确显示跨域图片。
backgroundColorstring#ffffffCanvas 背景色,设为 null 可以得到透明背景。
loggingbooleanfalse是否在控制台打印日志,调试时可以设为 true 看问题。
scrollXnumber0截图时的水平滚动偏移,解决页面滚动导致的内容错位。
scrollYnumber0截图时的垂直滚动偏移,同上。

如果你要截的元素包含图片,而且这张图片和你的网站存在跨域情况,大概率会出现图片出空白的情况。

<template>
  <div>
    <!-- 要截图的区域 -->
    <div 
      ref="captureRef" 
      class="capture-area" 
      style="padding: 20px; background: #f0f0f0;"
    >
      <!-- 关键样式建议写在行内,或者确保非 scoped -->
      <h2 class="title">这是要截图的内容</h2>
      <img src="https://iili.io/fcR7gSe.md.png" alt="" style="width: 140px;">
      <p style="font-size: 16px; margin: 0;">随便写点什么,html2canvas 会把它变成图片~</p>
    </div>

    <!-- 截图按钮 -->
    <button @click="handleCapture">点击截图</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import html2canvas from 'html2canvas'

// 获取要截图的 DOM 元素
const captureRef = ref(null)

// 截图函数
const handleCapture = async () => {
  // 调用 html2canvas,等待生成 Canvas
  const canvas = await html2canvas(captureRef.value)
  // 把 Canvas 加到页面上看看效果
  document.body.appendChild(canvas)
}
</script>

<style scoped>
.capture-area {
  width: 400px;
  box-sizing: border-box;
  border: 1px solid #ccc;
}
.title {
  font-size: 24px;
  font-weight: bold;
  margin: 0 0 10px 0;
}
</style>

要解决这个问题需要2步操作。

  1. 图片服务器配置 Access-Control-Allow-Origin: *(允许跨域)
  2. html2canvas 配置 useCORS: true

如果想更稳妥一点,还可以给图片标签加 crossorigin="anonymous" 属性。

https://iili.io 这个图床是允许跨域的,所以我们直接在 html2canvas 里配置 useCORS: true 即可。

// 省略部分代码

const canvas = await html2canvas(captureRef.value, {
  useCORS: true
})

高级用法:导出并下载图片

生成 Canvas 后,通常需要把它转成图片文件让用户下载,这里用 toDataURL 实现

<template>
  <div>
    <!-- 要截图的区域 -->
    <div 
      ref="captureRef" 
      class="capture-area" 
      style="padding: 20px; background: #f0f0f0;"
    >
      <!-- 关键样式建议写在行内,或者确保非 scoped -->
      <h2 class="title">这是要截图的内容</h2>
      <img src="https://iili.io/fcR7gSe.md.png" alt="" style="width: 140px;">
      <p style="font-size: 16px; margin: 0;">随便写点什么,html2canvas 会把它变成图片~</p>
    </div>

    <!-- 截图按钮 -->
    <button @click="handleCapture">点击截图</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import html2canvas from 'html2canvas'

// 获取要截图的 DOM 元素
const captureRef = ref(null)

// 截图函数
const handleCapture = async () => {
  // 调用 html2canvas,等待生成 Canvas
  const canvas = await html2canvas(captureRef.value, { useCORS: true });
  
  // 1. 把 Canvas 转成 PNG 格式的 base64 数据
  const imgData = canvas.toDataURL('image/png');
  
  // 2. 创建下载链接
  const link = document.createElement('a');
  link.download = '我的截图.png'; // 下载的文件名
  link.href = imgData;
  
  // 3. 触发点击下载
  link.click();
}
</script>

<style scoped>
.capture-area {
  width: 400px;
  box-sizing: border-box;
  border: 1px solid #ccc;
}
.title {
  font-size: 24px;
  font-weight: bold;
  margin: 0 0 10px 0;
}
</style>

常见坑点及避坑指南

html2canvas 虽然好用,但坑也不少。

跨域图片不显示或报错

前面已经提到了,如果截图里有跨域图片(比如图床的图),默认会显示空白。原因是浏览器的同源策略限制,html2canvas 无法直接读取跨域图片的像素。

解决方法

  • 图片服务器配置 Access-Control-Allow-Origin: *(允许跨域)
  • 给图片标签加 crossorigin="anonymous" 属性
  • html2canvas 配置 useCORS: true
<!-- 图片标签加 crossorigin -->
<img src="https://example.com/image.jpg" crossorigin="anonymous">

CSS 样式显示异常

部分 CSS 属性(如 transformbox-shadow、复杂渐变、伪元素 ::before/::after)可能渲染不对,甚至完全不显示。

html2canvas 是通过解析 DOM 和 CSS 手动绘制 Canvas 的,不是所有 CSS 都支持。

解决方法

  • 尽量用简单的 CSS 布局,避免太复杂的特效
  • 把复杂样式(如渐变、阴影)转成图片代替
  • 查看官方文档的 CSS 支持列表,避开不支持的属性

自定义字体渲染失败

用了自定义字体(如 @font-face),截图里却变成了默认字体。

html2canvas 截图时,字体可能还没加载完,就用了默认字体渲染。

解决方法

  • 等待字体加载完成后再截图,用 document.fonts.ready 即可
document.getElementById('btn').addEventListener('click', async () => {
  // 等待所有字体加载完成
  await document.fonts.ready;
  
  const element = document.getElementById('capture');
  const canvas = await html2canvas(element);
  document.body.appendChild(canvas);
});

滚动条导致截图不全或有滚动条

如果要截图的元素有滚动条,截图会包含滚动条,或者内容被截断。

解决方法

  • 截图前先隐藏滚动条,截图后恢复
const element = document.getElementById('capture');
// 保存原来的 overflow 样式
const originalOverflow = element.style.overflow;
// 隐藏滚动条
element.style.overflow = 'hidden';

const canvas = await html2canvas(element, {
  scrollX: 0,
  scrollY: 0 // 重置滚动偏移
});

// 恢复原来的样式
element.style.overflow = originalOverflow;

iframe 内容无法捕获

如果页面里有 iframe,html2canvas 无法截图 iframe 里的内容。

同源策略限制,无法直接访问 iframe 内部的 DOM。

解决方法

  • 如果 iframe 和主页面同域,可以手动获取 iframe 里的元素再截图
  • 如果跨域,基本无解,建议换其他方案(比如后端截图)

页面元素太多导致性能差

截图区域元素多、图片大时,生成 Canvas 会很慢,甚至浏览器卡顿。

解决方法

  • 适当降低 scale(比如设为 1.5 而不是 2)
  • 截图前隐藏不必要的元素(比如 display: none
  • 只截图需要的部分,不要截整个页面

SVG 元素显示异常

直接截图 SVG 可能会变形、错位,甚至不显示。

解决方法

  • 先把 SVG 转成图片,再插入 DOM 截图。可以用 canvg 库辅助转换,或者手动把 SVG 转成 base64 图片
// 简单的 SVG 转图片示例
const svgElement = document.querySelector('svg');
const svgData = new XMLSerializer().serializeToString(svgElement);
const img = new Image();
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));

// 等图片加载完后,替换原来的 SVG,再截图
img.onload = async () => {
  svgElement.parentNode.replaceChild(img, svgElement);
  const canvas = await html2canvas(element);
  document.body.appendChild(canvas);
};

z-index 层级错乱

截图里的元素层级和实际页面不一样,该在上面的元素跑到下面了。

html2canvas 对 z-index 的解析有时会出问题,尤其是元素没有显式设置 position 时。

解决方法

  • 给需要层级的元素显式设置 position(如 relativeabsolute
  • 调整 DOM 顺序(后面的元素会覆盖前面的),代替 z-index

点赞 + 关注 + 收藏 = 学会了