#30 HTML-in-Canvas 与 Canvas UI

2026-08-17

前端圈有一条隐形的墙:DOM 负责结构、文本、无障碍与交互;Canvas / WebGL 负责像素、Shader、粒子与 3D。两边各自很强,却很难融合。

HTML-in-Canvas 是 WICG 正在孵化的提案,试图把这道墙拆掉;
而 Canvas UI 则是基于它(并带降级方案)第一个走红的"GPU 级视觉组件库"。

#29 浏览器访问本地 HTTPS 网站偶发卡住的排查经验

2026-08-15

我用 Golang 开发了一个本地站点,跑在 Caddy 上(动态注册路径),并使用阿里云 DNS 将域名解析到本地 IP。

今天突然发现网页无法打开,浏览器一直处于 Pending 状态,而 curl 访问正常。关闭网络代理和浏览器插件、清理缓存后问题仍然存在。

怀疑是 DNS 解析的问题,使用 curl -vk --resolve d3v.cn:443:192.168.31.31 https://d3v.cn/markjour/ 访问也正常。

更奇怪的是,发现使用访客模式可以打开。

最后还发现,在关闭一些浏览器 Tab 后,普通模式也恢复正常。暂时推测可能与浏览器 HTTP/2 连接复用有关:

  1. 开机之后我访问相关页面没有成功打开,发现是服务都没有启动,然后启动 Caddy 和后台服务,并动态注册路径。
    可能是这个过程中,有 Tab 没有关闭,一直 Pending。
  2. Caddy 支持 HTTP/2,浏览器会通过 ALPN 协商 HTTP/2;HTTP/2 会在同一个连接上复用多个请求。
    如果底层 HTTP/2 连接本身出现异常,复用该连接的多个请求可能同时表现为 Pending。
    关闭 Tab 后,浏览器可能释放异常连接并重新建立连接,因此问题自动恢复。

以后再次遇到的时候再检查验证。


后续多次遇到这个本地网站访问异常问题,今天抽空继续排查。

发现同一台机器上的 Edge,不同 Profile 对 d3v.cn 的访问结果不同:一个可以正常打开,另一个持续 ERR_TIMED_OUT

进一步查看 Edge 的网络信息发现( / ),异常 Profile 将 d3v.cn 解析为 ::1192.168.31.31,而正常 Profile 解析为 127.0.0.1。随后检查本机 /etc/hosts,发现配置了:

127.0.0.1 localhost
127.0.0.1 eqr.hosts.d3v.cn eqr eqr6
::1       eqr.hosts.d3v.cn

同时公网 DNS 中 eqr.hosts.d3v.cn 也存在地址记录。因此,同一域名实际上同时存在本地 hosts 和公网 DNS 两套解析结果,Edge 不同 Profile 可能缓存或选择了不同地址。

这次基本确认问题核心在域名解析结果不一致,而不是 Caddy、TLS 或 HTTP/2。后续应避免让同一域名同时依赖 hosts 和公网 DNS 提供不同地址,或者至少确保两者解析结果一致。

#28 百度云 ICP 备案记录

2026-07-16
  • 原备案信息(阿里云 · 2019):备案名称「码厩Markjour」,备案号 鄂ICP备15002462号
  • 迁移原因:不再使用虚拟主机,业务迁至百度云低成本主机,需在百度云重新提交接入/备案,备案名称变更为「码厩开发者笔记」。

初审被拒,原因:

  1. 应急联系电话校验:应急手机号不能与负责人手机号码相同,需提供另一位真实可联系人的号码(当年没有这个要求)。
  2. 前台菜单合规审核:初审要求网站不得出现“留言”和“给我写信”菜单,这算页面交互入口(推测当年备案时尚未增加这两个菜单)。
    • 这两个功能从没用过,暂时屏蔽前端入口以通过核验,后面再看如何处理。

初审通过之后,备案进入 “管局审核中-短信校验中” 状态。
几分钟之后就会收到工信部短信验证码,需要进入指定链接输入验证码、手机号、身份证后六位进行核验。
短信核验之后过一会儿,备案进入 “管局审核中-人工审核中” 状态。

#25 F12 网络过滤器

2023-07-16

示例

mime-type:image/png larger-than:1K
# Edge 官方示例,表示过滤出大于 1K 的 PNG 图片

domain:*.csdn.net method:POST
# 过滤出 CSDN 域的 POST 请求。

has-response-header:Content-Type -domain:*.baidu.com
# 过滤出带 Content-Type 头的请求,排除百度域。

清单

属性 详情
domain 仅显示来自指定域的资源。您可以使用通配符(*)来包含多个域。
例如,*.com 会显示所有以 .com 结尾的域名资源。
DevTools 会将所有找到的域名填充到自动完成下拉菜单中。
has-response-header 显示包含指定 HTTP 响应头的资源。
DevTools 会将所有找到的响应头填充到自动完成下拉菜单中。
is 使用 is:running 查找 WebSocket 资源。
larger-than 显示大于指定大小的资源,单位为字节。
设置值为 1000 相当于设置值为 1k
method 显示通过指定的 HTTP 方法类型检索的资源。
DevTools 会将所有找到的 HTTP 方法填充到下拉菜单中。
mime-type 显示指定 MIME 类型的资源。
DevTools 会将所有找到的 MIME 类型填充到下拉菜单中。
mixed-content 显示所有混合内容资源(mixed-content:all),或仅显示当前显示的资源(mixed-content:displayed)。
scheme 显示通过不安全的 HTTP(scheme:http)或安全的 HTTPS(scheme:https)检索的资源。
set-cookie-domain 显示具有与指定值匹配的 Set-Cookie 标头中的 Domain 属性的资源。
DevTools 会将所有找到的 Cookie 域填充到自动完成中。
set-cookie-name 显示具有与指定值匹配的 Set-Cookie 标头中的名称的资源。
DevTools 会将所有找到的 Cookie 名称填充到自动完成中。
set-cookie-value 显示具有与指定值匹配的 Set-Cookie 标头中的值的资源。
DevTools 会将所有找到的 Cookie 值填充到自动完成中。
status-code 显示与特定 HTTP 状态码匹配的资源。
DevTools 会将所有找到的状态码填充到自动完成下拉菜单中。
  1. 多个条件之间是 AND 关系,OR 关系暂时不支持。
  2. 前面加上 - 表示取反,小技巧:可以通过输入 - 之后的自动补全查看所有支持的选项。

参考资料与拓展阅读

#24 Web 分词 API

2023-04-21

看了 How to split JavaScript strings into sentences, words or graphemes with "Intl.Segmenter" 了解到,现在 Web 已经支持分词了:

const text = `我爱北京天安门,天安门上太阳升。伟大领袖毛主席,指引我们向前进。`;
const granularities = ["sentence", "word", "grapheme"];
granularities.forEach(function (granularity) {
  // console.log([granularity, index, self])
  let segmenter = new Intl.Segmenter("zh", { granularity: granularity });
  let seg = segmenter.segment(text);
  // console.log(seg) // Segments{}
  let result = Array.from(seg, (s) => s.segment);
  console.log(result);
});
// ['我爱北京天安门,天安门上太阳升。', '伟大领袖毛主席,指引我们向前进。']
// ['我', '爱', '北京', '天安门', ',', '天安门', '上', '太阳', '升', '。', '伟大', '领袖', '毛主席', ',', '指引', '我们', '向', '前进', '。']
// ['我', '爱', '北', '京', '天', '安', '门', ',', '天', '安', '门', '上', '太', '阳', '升', '。', '伟', '大', '领', '袖', '毛', '主', '席', ',', '指', '引', '我', '们', '向', '前', '进', '。']

#23 基于 Web 技术的跨平台应用解决方案

2023-02-01
  • Web App,纯 Web App,需要用浏览器打开

随着浏览器厂商的持续推进,现在 WebApp 也能实现很多底层交互了

  • Native App,各平台的原生应用
  • Hybird App,在 App 中加入 WebView,实现部分逻辑
    根据 Native 和 Web 部分的比例,可以分成:

  • 交替

  • 混合,同一个窗口中,主体内容采用原生实现,部分组件是 WebView + WebApp(类似浏览器)
  • WebView + WebApp 为主体逻辑,加上 Native 交互

后端和框架核心通信,实现窗口管理,系统调用,WebView。

  • Electron
  • React Native (RN)
  • Tauri,后端开发语言 Rust,使用系统自带浏览器组件
  • Cordova
  • Ionic
  • NW.js
  • Expo
  • Capacitor
  • Quasar
  • NativeScript
  • Gluon

参考资料与拓展阅读

#22 Fetch API

2022-08-02

在很长一段时间里,浏览器发送 Ajax 请求主要依赖 XMLHttpRequest,也就是大家熟悉的 XHR。
但随着 JavaScript 生态的发展,XHR 的一些问题越来越明显:

  • 代码太啰嗦
  • 回调层级复杂
  • 错误处理麻烦
  • 可读性较差

于是浏览器推出了更现代、更简洁的网络请求方案 Fetch API。

XMLHttpRequest 的问题

先看一个传统 Ajax 请求:

var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/user", true);
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4) {
    if (xhr.status == 200) {
      console.log(xhr.responseText);
    }
  }
};
xhr.send();

虽然功能没问题,但存在几个明显缺点:

  1. 状态判断复杂:必须反复判断 readyState 和 status,初学者经常搞混。
  2. 回调代码较多:所有逻辑都写在 onreadystatechange 里面,代码一多就会非常乱。
  3. 服务器返回 JSON 时需要手动转换。
    JSON.parse(xhr.responseText)
    

Fetch 的基本写法

主要是基于 Promise 实现,可以链式调用,不用一层又一层地回调,更符合现代 JavaScript 风格。
注意:这是较新的语法,在某些老版本浏览器上可能支持程度不好,如果需要兼容,可以使用 XHR 或者 Axios、jQuery Ajax 等库。

fetch("/api/user")
  .then(function (response) { // Response 对象
    return response.json();
  })
  .then(function (data) {
    console.log(data);
  })
  .catch(function (error) { // 异常处理
    console.log(error);
  });

相比 XMLHttpRequest:

  • 代码明显更短
  • 结构更清晰
  • 更容易阅读

Fetch 也支持提交数据:

fetch("/api/user", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    name: "Tom",
    age: 18
  })

})
.then(function (response) {
  return response.json();
})
.then(function (data) {
  console.log(data);
});

参考资料与拓展阅读