2202 年我的 React 搭配
状态管理:局部 jotai、全局 zustand
网络请求:swr
路由:React Router
CSS:emotion + Tailwind CSS
UI:MUI、Mantine
动画:react-spring
表单验证:React Hook Form + zod
日期:Day.js
状态管理:局部 jotai、全局 zustand
网络请求:swr
路由:React Router
CSS:emotion + Tailwind CSS
UI:MUI、Mantine
动画:react-spring
表单验证:React Hook Form + zod
日期:Day.js
汇总:强制创建 layer 的属性,及 side effects
一般使用 backface-visibility 或 will-change: opacity,副作用相对最小。
See also: https://t.me/handrush/151
#learning
will-change: transform:会创建新的 containing block,导致 position: fixed 或 position: absolute 异常transform: translateZ(0):与 will-change: transform 有同样问题。早期的 Chrome、Safari 会引发闪烁,因此使用 backface-visibility: hidden 作为替代backface-visibility: hidden:会把 backface 的内容隐藏,在 3D 空间旋转元素时会有问题will-change: opacity:会创建新的 stacking context,影响元素的呈现顺序,但可以通过 z-index 恢复到预期状态一般使用 backface-visibility 或 will-change: opacity,副作用相对最小。
See also: https://t.me/handrush/151
#learning
FPS 不是流畅动画的全部
今天测试 CSS 与 JS 动画的性能差异时,发现一个有趣的现象:使用 JS 创建的动画,虽然能持续跑满 60fps,但动画本身“看起来”并不流畅;而 CSS 动画很流畅,但它却并不总是保持在 60fps。
稍微研究了下,发现这跟一个叫 subpixel 的东西有关,具体来说,对于 sub-pixel animations,它会在像素与像素之间添加模糊,使人眼看起来更平滑。
由于并不存在“半个像素”,一些更细小的动画效果就需要通过 subpixel 达到,它让人看起来动画是在“动”,实际上物体并没有动,而只是像素之间的交替变换。
因此,动画应尽量使用 CSS,或 Animations API。CSS 动画不仅能更充分利用 GPU,还能为动画提供诸如 subpixel 的优化,以减少 paint/composite 次数,并让视觉感到流畅。此外,它还不会破坏现有文档结构,而招致的频繁 lay out。
#learning
今天测试 CSS 与 JS 动画的性能差异时,发现一个有趣的现象:使用 JS 创建的动画,虽然能持续跑满 60fps,但动画本身“看起来”并不流畅;而 CSS 动画很流畅,但它却并不总是保持在 60fps。
稍微研究了下,发现这跟一个叫 subpixel 的东西有关,具体来说,对于 sub-pixel animations,它会在像素与像素之间添加模糊,使人眼看起来更平滑。
由于并不存在“半个像素”,一些更细小的动画效果就需要通过 subpixel 达到,它让人看起来动画是在“动”,实际上物体并没有动,而只是像素之间的交替变换。
因此,动画应尽量使用 CSS,或 Animations API。CSS 动画不仅能更充分利用 GPU,还能为动画提供诸如 subpixel 的优化,以减少 paint/composite 次数,并让视觉感到流畅。此外,它还不会破坏现有文档结构,而招致的频繁 lay out。
#learning
👍1
CSS Pixel 与 Device Pixel
CSS Pixel 不与真实的 Device Pixel 一一对应。在具有高 Device Pixel Ratio (DPR) 的设备上,一个 CSS Pixel 可能由多个 Device Pixel 表示,如被排列为 2x2 的方格。
DPR 为方格的高度,或宽度,2x2 grid 的 DPR 是 2。对于图像,其宽高与 Device Pixel 相关,若 image 元素 width 为 200px,对应 Device Pixel 为 400px,则图像的清晰显示宽度应为 400px。
#learning
CSS Pixel 不与真实的 Device Pixel 一一对应。在具有高 Device Pixel Ratio (DPR) 的设备上,一个 CSS Pixel 可能由多个 Device Pixel 表示,如被排列为 2x2 的方格。
DPR 为方格的高度,或宽度,2x2 grid 的 DPR 是 2。对于图像,其宽高与 Device Pixel 相关,若 image 元素 width 为 200px,对应 Device Pixel 为 400px,则图像的清晰显示宽度应为 400px。
#learning
https://featurepolicy.info/
一个 Permissions-Policy 可用值的列表。在里边我发现了
#tools
一个 Permissions-Policy 可用值的列表。在里边我发现了
sync-script 这个从来没见过的东西,不过目前也还没任何浏览器支持#tools
👍1
原来 MIME 还定义了 'example' media type,如 image/example、text/example,一般作为例子占位用。
还发现
#learning
还发现
Content-type: text/plain; charset=utf-8 中的 charset 原来是 MIME 的一部分,并不是特定于 Content-type 的。#learning
👍1
Service Worker 最佳缓存实践
SW script 的文件名应该是固定的,而不该包含 hash 或其它任何动态内容,如
此外,在 SW 的 installation lifecycle,一般会将 index.html 加入缓存,这意味着 index.html 由 SW 提供(依赖于 SW),若 SW 具有非固定文件名,这需要体现在 index.html 中(依附于 index.html),这会引发 circular dependency。
因此最佳策略是,将整个项目构建的 unique id 添加到 SW script,并在下次请求时,浏览器检查到 SW script 变更,重新执行完整的 SW lifecycle,并对包括 index.html 在内的所有静态资源重新缓存。当然也可以配合
#experience
SW script 的文件名应该是固定的,而不该包含 hash 或其它任何动态内容,如
sw.5b6aeb1b.js 是不被提倡的。因为 Chrome 68 及更高版本,在检查 SW 更新时会忽略缓存。此外,在 SW 的 installation lifecycle,一般会将 index.html 加入缓存,这意味着 index.html 由 SW 提供(依赖于 SW),若 SW 具有非固定文件名,这需要体现在 index.html 中(依附于 index.html),这会引发 circular dependency。
因此最佳策略是,将整个项目构建的 unique id 添加到 SW script,并在下次请求时,浏览器检查到 SW script 变更,重新执行完整的 SW lifecycle,并对包括 index.html 在内的所有静态资源重新缓存。当然也可以配合
skipWaiting 使其提前进入 activation lifecycle。#experience
👍1
alpha-value 如果告诉我是 alpha channel 倒很好理解。如果说成是 transparency,每次都要在脑子里想一想。
因为,100%透明 = 不透明,而不是“完全透明”,0% 透明 = 完全透明 🤯
如果这里说成 opacity,100% 不透明 = 不透明,0% 不透明 = 完全透明。感觉会更符合直觉(
因为,100%透明 = 不透明,而不是“完全透明”,0% 透明 = 完全透明 🤯
如果这里说成 opacity,100% 不透明 = 不透明,0% 不透明 = 完全透明。感觉会更符合直觉(
👍2
JavaScript NaN 捡屎
NaN 是 Not a Number 的缩写,如果这么理解,很快会发现
比较
---
我更倾向 JS 的 NaN 是指 "Not a NaN”:试着检查一个值是否为 NaN,
在比较时,将关注点放在了 NaN,而不是 Number 上,或许它应该是一个独立类型,毕竟它作为独立属性出现在 top-level scope 中,而不像其它语言那样使用
好在,
这并不是全部,JavaScript 中还有许多为了 backwards compatibility 而保留至今的糟粕设计,如在一众语言中
---
UPDATED:调整了下叙述逻辑
#屎前考古
NaN 是 Not a Number 的缩写,如果这么理解,很快会发现
typeof NaN === 'number'比较
NaN === NaN,结果 false,这说明 "Not a Number" not "Not a Number",但它确实是 number,这有点反直觉---
我更倾向 JS 的 NaN 是指 "Not a NaN”:试着检查一个值是否为 NaN,
isNaN(NaN) === true,这很好,但很快又会发现 isNaN('foo') === true、isNaN([]) === true在比较时,将关注点放在了 NaN,而不是 Number 上,或许它应该是一个独立类型,毕竟它作为独立属性出现在 top-level scope 中,而不像其它语言那样使用
float('nan') 或 Float::NAN 等形式表示好在,
Number.isNaN() 被加到了 ES6,它只检查一个值是否 "Not a NaN",而不是 "Not a Number",“不是数字”的东西很多,而 NaN 只是 NaN这并不是全部,JavaScript 中还有许多为了 backwards compatibility 而保留至今的糟粕设计,如在一众语言中
pow(1, Infinity) == 1,而 JS 确是 NaN;如从 Java 1.0 引进的 Date 至今也仍在使用,而早在 JDK 1.1,Date 中的很多方法就被废弃了---
UPDATED:调整了下叙述逻辑
#屎前考古
两个 delta compression 算法
Bsdiff:https://github.com/mendsley/bsdiff
VCDIFF (RFC 3284):https://github.com/google/open-vcdiff
Bsdiff 基于 suffix array 编码数据,性能上优于 VCDIFF。
#tools
Bsdiff:https://github.com/mendsley/bsdiff
VCDIFF (RFC 3284):https://github.com/google/open-vcdiff
Bsdiff 基于 suffix array 编码数据,性能上优于 VCDIFF。
#tools
HTTP/2 Server Push 细节
HTTP/2 push cache 独立于 HTTP cache,由 server 推送的资源,仅在使用后进入 HTTP cache 并从 push cache 中移除,在推送时也不会检查其 Cache-Control header。
它基于 connection,若 connection closed,则 push cache 消失。由于 HTTP/2 中的一个 connection 除了被多个 requests 复用,还可以在多个 pages 间共享,因此 push cache 也会相应得到共享。这甚至意味着,它可以在具有同一 IP、同一 cert 的跨多个 origin 间共享缓存(即使包含任何应用状态)。
push cache 与其它一众 cache 相比,具有最低的 priority,若 pushed resource 已出现在 HTTP cache 且尚未过期,则使用“较旧的” HTTP cache,若该 resource 仍在传输,则浏览器应该发送一个 CANCEL 或 REFUSED_STREAM frame 以终止 stream。
另外,出于隐私目的,浏览器可能会为带有 credentials 和未带有它的 requests 创建两个独立的 connection,这里的 credentials 可能是 Cookies、HTTP authentication 或 Client certificate。
#learning
HTTP/2 push cache 独立于 HTTP cache,由 server 推送的资源,仅在使用后进入 HTTP cache 并从 push cache 中移除,在推送时也不会检查其 Cache-Control header。
它基于 connection,若 connection closed,则 push cache 消失。由于 HTTP/2 中的一个 connection 除了被多个 requests 复用,还可以在多个 pages 间共享,因此 push cache 也会相应得到共享。这甚至意味着,它可以在具有同一 IP、同一 cert 的跨多个 origin 间共享缓存(即使包含任何应用状态)。
push cache 与其它一众 cache 相比,具有最低的 priority,若 pushed resource 已出现在 HTTP cache 且尚未过期,则使用“较旧的” HTTP cache,若该 resource 仍在传输,则浏览器应该发送一个 CANCEL 或 REFUSED_STREAM frame 以终止 stream。
另外,出于隐私目的,浏览器可能会为带有 credentials 和未带有它的 requests 创建两个独立的 connection,这里的 credentials 可能是 Cookies、HTTP authentication 或 Client certificate。
#learning
刚刚读文章发现两个有意思的写法(但没用!)
上面两个在 non-strict mode 下都可以运行。想了想第二个可以实现匿名函数递归,不过一般不用 arguments.callee。
false.true = ''
(function () { return arguments.callee })()()上面两个在 non-strict mode 下都可以运行。想了想第二个可以实现匿名函数递归,不过一般不用 arguments.callee。
发现不同浏览器对 AudioWorklet 支持都多少有点问题,比如 Chrome/Edge 不会在 DevTools Network 记录 audioWorklet.addModule() 的请求,包括后续 import 的模块,这是个 BUG,Firefox 正常。
Firefox 不支持在 AudioWorkletNode 中 import ES6 modules,Chrome/Edge 支持。不过 AudioWorklet Wasm 支持在 Chrome/Edge、Firefox 都挺好。
至于 Safari,我的几个 demo 都跑不起来,emm准确说是跑起来了,也没报错,但就是没声,我也懒得调了。
Firefox 不支持在 AudioWorkletNode 中 import ES6 modules,Chrome/Edge 支持。不过 AudioWorklet Wasm 支持在 Chrome/Edge、Firefox 都挺好。
至于 Safari,我的几个 demo 都跑不起来,emm准确说是跑起来了,也没报错,但就是没声,我也懒得调了。