hexo-img-onerror 是一个为 Hexo 网站添加备用图片源的插件。License

故名思义,它的实现原理是为每一个 img 标签添加一个 onerror 事件,当图片加载失败时,会自动替换为备用图片。

如图所示:

hexo-img-onerror is a plugin that adds a fallback image source to Hexo websites. License

As its name suggests, it works by attaching an onerror event to every img tag: when an image fails to load, it is automatically replaced with the fallback image.

As shown below:

安装与配置

运行以下命令安装插件:

1
npm install hexo-img-onerror --save

安装插件后,请在 Hexo 项目中配置它。

  1. 在 Hexo _config.yml 中添加以下配置,请确保 src_prefixonerror_src_prefix 一一对应:
1
2
3
4
5
6
7
8
9
img_onerror:
enable: true
onerror-map: ./onerror-map.json
src_prefix:
- https://example.com/path1
- https://example.com/path2
onerror_src_prefix:
- https://fallback.com/path1
- https://fallback.com/path2

如果配置了 onerror-map,插件会优先从 JSON 文件中查找 /files/ 之后的路径对应的备用链接,并将该链接写入 onerror。如果没有配置 onerror-map,或者在映射表中未找到对应项,则会回退到现有的 src_prefix / onerror_src_prefix 替换逻辑。

在你的文章或页面中,添加图像:

1
2
![Description](https://example.com/path1/image.jpg)
![Description](https://example.com/path2/image.jpg)

渲染出来的 img 标签会被设置 onerror 事件,重新指定 src

1
2
<img src="https://example.com/path1/image.jpg" onerror="this.src='https://fallback.com/path1/image.jpg';">
<img src="https://example.com/path2/image.jpg" onerror="this.src='https://fallback.com/path2/image.jpg';">

此时,如果 image.jpghttps://example.com/path1/image.jpg 加载失败,浏览器将自动从 https://fallback.com/path1/image.jpg 加载备用图像。

源码解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function addOnError(data, config) {
// 从 hexo 的 config 中读取 src_prefix 和 onerror_src_prefix 数组
const srcPrefixes = config.src_prefix || [];
const onErrorSrcPrefixes = config.onerror_src_prefix || [];

if (srcPrefixes.length !== onErrorSrcPrefixes.length) {
console.error('src_prefix and onerror_src_prefix must have the same number of elements.');
return data;
}

// 为每一个 img 标签添加 onerror 事件
// 指定图片加载失败时的备用图片链接
return data.replace(/<img [^>]*src="([^"]+)"[^>]*>/g, (match, src) => {
const srcIndex = srcPrefixes.findIndex(prefix => src.startsWith(prefix));

if (srcIndex !== -1) {
const newSrc = src.replace(srcPrefixes[srcIndex], onErrorSrcPrefixes[srcIndex]);
return match.replace(/(<img [^>]*src="[^"]+")/, `$1 onerror="this.onerror=null;this.src='${newSrc}'"`);
}

return match;
});
}

// 注册 hexo 的 filter,在文章渲染后执行 addOnError 函数
hexo.extend.filter.register('after_post_render', (data) => {
const config = hexo.config.img_onerror;
if (config.enable) {
data.content = addOnError(data.content, config);
// 为了防止 post 被多次处理,添加一个标记
data.img_onerror_processed = true;
}

return data;
}, 10);

// 有些主题可能主页也会有 img 标签,所以需要在渲染 html 后再次处理
hexo.extend.filter.register('after_render:html', (html, data) => {
const config = hexo.config.img_onerror;
// 如果插件开启且 post 未被处理过
if (config.enable && !data.img_onerror_processed) {
return addOnError(html, config);
}

return html;
}, 10);

为什么需要它?

我博客的图片放在了家里服务器的 Alist 里,最终的存储是国内的天翼云盘。 从这个服务器获取图片速度很快,但它的缺点是不稳定,有时会因为各种原因导致图片无法访问。

  • DDNS 失败了
  • 服务器 断网了
  • Alist 挂了

此外,这些博客里的图片在 Sharepoint 上也有备份,Sharepoint 的国内访问速度就比较一般了,但胜在稳定。

于是,一个自然的需求就是,为我的网站的所有图片添加一个备用源,让浏览器优先访问 Alist,如果失败了,再访问 Sharepoint。

经过与 AI 的一番交涉,我最终选择了为图片添加 onerror 事件的方式。

还有一种方式是给图片添加 srcset 属性,但是经过我的测试,浏览器并不会优先加载 src 属性,有时候会直接加载 srcset 属性指定的图片,这样就无法实现我想要的效果。

Installation & Configuration

Install the plugin by running:

1
npm install hexo-img-onerror --save

After installing, configure it in your Hexo project.

  1. Add the following configuration to your Hexo _config.yml. Make sure src_prefix and onerror_src_prefix correspond one-to-one:
1
2
3
4
5
6
7
8
9
img_onerror:
enable: true
onerror-map: ./onerror-map.json
src_prefix:
- https://example.com/path1
- https://example.com/path2
onerror_src_prefix:
- https://fallback.com/path1
- https://fallback.com/path2

If onerror-map is configured, the plugin will first look up the path after /files/ in the JSON file, find the corresponding fallback link, and write it to onerror. If onerror-map is not configured, or no match is found in the mapping table, it falls back to the existing src_prefix / onerror_src_prefix replacement logic.

In your posts or pages, add images:

1
2
![Description](https://example.com/path1/image.jpg)
![Description](https://example.com/path2/image.jpg)

The rendered img tags will have an onerror event that reassigns src:

1
2
<img src="https://example.com/path1/image.jpg" onerror="this.src='https://fallback.com/path1/image.jpg';">
<img src="https://example.com/path2/image.jpg" onerror="this.src='https://fallback.com/path2/image.jpg';">

Now, if image.jpg fails to load from https://example.com/path1/image.jpg, the browser will automatically load the fallback image from https://fallback.com/path1/image.jpg.

Source Code Explained

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function addOnError(data, config) {
// Read the src_prefix and onerror_src_prefix arrays from hexo's config
const srcPrefixes = config.src_prefix || [];
const onErrorSrcPrefixes = config.onerror_src_prefix || [];

if (srcPrefixes.length !== onErrorSrcPrefixes.length) {
console.error('src_prefix and onerror_src_prefix must have the same number of elements.');
return data;
}

// Attach an onerror event to every img tag
// pointing to the fallback image URL when loading fails
return data.replace(/<img [^>]*src="([^"]+)"[^>]*>/g, (match, src) => {
const srcIndex = srcPrefixes.findIndex(prefix => src.startsWith(prefix));

if (srcIndex !== -1) {
const newSrc = src.replace(srcPrefixes[srcIndex], onErrorSrcPrefixes[srcIndex]);
return match.replace(/(<img [^>]*src="[^"]+")/, `$1 onerror="this.onerror=null;this.src='${newSrc}'"`);
}

return match;
});
}

// Register a hexo filter that runs addOnError after a post is rendered
hexo.extend.filter.register('after_post_render', (data) => {
const config = hexo.config.img_onerror;
if (config.enable) {
data.content = addOnError(data.content, config);
// Mark the post as processed to avoid double processing
data.img_onerror_processed = true;
}

return data;
}, 10);

// Some themes may also have img tags on the homepage, so process the HTML again after rendering
hexo.extend.filter.register('after_render:html', (html, data) => {
const config = hexo.config.img_onerror;
// If the plugin is enabled and the post hasn't been processed yet
if (config.enable && !data.img_onerror_processed) {
return addOnError(html, config);
}

return html;
}, 10);

Why do I need it?

The images on my blog are stored in an Alist instance running on my home server, with the actual files on China Telecom’s Tianyi cloud drive. Fetching images from that server is fast, but it has a downside: it’s not very stable, and images can become inaccessible for various reasons.

  • DDNS failed
  • Server lost network connectivity
  • Alist crashed

Additionally, these images also have backups on Sharepoint, which is slower to access from mainland China but far more stable.

So a natural requirement arose: give every image on my site a fallback source, letting the browser try Alist first and fall back to Sharepoint on failure.

After some discussion with AI, I ended up going with attaching onerror events to images.

Another approach is adding a srcset attribute to images, but from my testing, browsers don’t necessarily prefer the src attribute — sometimes they load the image specified by srcset directly, which defeats my purpose.