最近的笔记
静态站配了强缓存,然后发现改动推不下去
2026-07-30
给一个纯静态站配 nginx,顺手给 CSS 和 JS 加了七天强缓存。当时觉得挺合理——
文件不常改,能省不少流量。结果改完样式重新部署,自己手机上打开还是旧的,
清了缓存才对。
原因很直白:文件名没变,浏览器在缓存过期前根本不会来问服务器。
Cache-Control: max-age=604800 的意思就是「这七天你别来烦我」,
服务器上换了内容它也不知道。
正经做法是文件名带内容哈希,改一次换一个名。但没有构建步骤的项目做不了这个,
于是折中:HTML 里给资源挂个版本查询串,nginx 那边把 CSS/JS 改成协商缓存。
location ~* \.(css|js)$ {
add_header Cache-Control "no-cache";
etag on;
}
no-cache 这个名字有点误导,它不是「不缓存」,而是「每次先来问一下」。
没变就回 304,几百字节的事。图片仍然留着长缓存——它们的内容确实不怎么变。
nginx缓存
一个让元素永远不出现的写法
2026-07-29
做滚动揭示动画,元素初始状态用 clip-path: inset(0 0 100% 0) 藏起来,
进视口后再由 IntersectionObserver 加个类展开。写完发现有两个元素死活不出现,
滚到跟前也没反应。
排查半天才想明白:被 clip-path 完全裁空的元素,在 IntersectionObserver 眼里
面积是零,永远不算「进入视口」。于是它等着观察器触发,观察器等着它可见——
自己把自己锁死了。
改法是别让被观察的元素本身不可见:外层 overflow: hidden,
内层做位移,观察挂在外层上。
<span class="line"><i>要揭示的文字</i></span>
.line { overflow: hidden; }
.line > i { transform: translateY(103%); }
.line.in > i { transform: none; }
opacity 为 0 倒是不影响观察器,它只看几何位置。真正会出问题的是
clip-path、祖先的 overflow 裁剪,还有 display: none。
CSS踩坑
srcset 的宽度描述符,浏览器是怎么挑的
2026-07-30
给手机端做图片优化,用 srcset 给了两档尺寸,结果发现手机还是在下大图。
以为是写错了,其实是算错了。
<img src="a.jpg"
srcset="a-sm.jpg 1300w, a.jpg 1920w"
sizes="(max-width: 700px) 100vw, 33vw">
浏览器的算法是:先按 sizes 算出这张图实际要显示多宽(CSS 像素),
再乘设备像素比,得到真正需要的物理像素数,然后从 srcset 里挑第一个不小于它的。
iPhone 上 390 CSS 像素宽、DPR 是 3,实际需要 1170px。我给的小图是 1300px,
看着是够的。但换成 430 宽的大屏手机,需求就变成 1290px,1300 只差一点点,
稍微一变就退回去用 1920 的原图了。
另外调试时别信 naturalWidth。带 srcset 的图片,这个属性返回的是
密度校正之后的 CSS 尺寸,不是文件真实宽度——我一度以为图片被压坏了。
要看实际选了哪张,用 currentSrc。
前端性能
浏览器自带的语音合成,够用但有两个坑
2026-07-30
想给页面加语音朗读,又不想为了这个引第三方服务。浏览器自带的
SpeechSynthesis 其实够用,零依赖零流量,中文音色各系统都有内置。
第一个坑是长文本。整段丢进去,Chrome 念十几秒就会静默停下,不报错也不触发
onend。解决办法是按句号切开,排成队列一句一句念,前一句 onend 再启动下一句。
第二个坑是 iOS。Safari 只允许在用户手势里首次触发朗读,页面加载完自动念是不行的。
绑在按钮点击上就没问题。
还有个细节:getVoices() 是异步填充的,页面刚加载时拿到的经常是空数组,
得监听 voiceschanged。音色也别指望统一,各家系统内置的不一样。
Web API前端