You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用node-canvas与jsdom添加图片时出现资源重复调用如何解决?

jsdom与node-canvas图片资源重复请求解决方案

重复请求根因

jsdom的资源加载逻辑与node-canvas的图片解码逻辑相互独立,没有共享缓存机制:

  • 第一次请求:jsdom配置resources: "usable"时,会自动解析DOM中的<img>元素src属性,主动拉取图片资源
  • 第二次请求:node-canvas侧渲染图片时,会独立发起资源请求读取图片二进制数据,不会复用jsdom已加载的资源

解决方法

方案1:复用jsdom缓存(推荐,适配需要jsdom提前处理图片的场景)

自定义jsdom资源加载器并开启全局内存缓存,同时重写canvas的Image构造函数优先从缓存中取资源,修改后的完整代码如下:

const jsdom = require('jsdom');
const { JSDOM, ResourceLoader } = jsdom;
const { createCanvas, Image } = require('canvas');

// 自定义带缓存的资源加载器
class CachedResourceLoader extends ResourceLoader {
  constructor() {
    super();
    this.cache = new Map();
  }
  async fetch(url, options) {
    if (this.cache.has(url)) {
      // 命中缓存直接返回
      return Buffer.from(this.cache.get(url));
    }
    const res = await super.fetch(url, options);
    // 加载成功后存入缓存
    if (res) {
      const buffer = await res.arrayBuffer();
      this.cache.set(url, buffer);
    }
    return res;
  }
}
const cachedLoader = new CachedResourceLoader();

const options = { 
  runScripts: "dangerously", 
  url: `https://localhost:8800`,
  resources: "usable",
  // 注入自定义缓存加载器
  resourcesLoader: cachedLoader,
  // 重写全局Image构造函数,复用缓存
  beforeParse(window) {
    const OriginalImage = window.Image;
    window.Image = function() {
      const img = new OriginalImage();
      // 监听src赋值,优先从缓存拿数据
      Object.defineProperty(img, 'src', {
        set(val) {
          if (cachedLoader.cache.has(val)) {
            // 直接给canvas Image喂缓存数据
            const canvasImg = new Image();
            canvasImg.src = Buffer.from(cachedLoader.cache.get(val));
            canvasImg.onload = () => {
              img.naturalWidth = canvasImg.naturalWidth;
              img.naturalHeight = canvasImg.naturalHeight;
              img.dispatchEvent(new window.Event('load'));
            };
            canvasImg.onerror = () => img.dispatchEvent(new window.Event('error'));
          } else {
            img.setAttribute('src', val);
          }
        },
        get() {
          return img.getAttribute('src');
        }
      });
      return img;
    }
  }
};

new JSDOM(`
  <html>
    <head><script src="https://code.jquery.com/jquery-3.5.1.min.js"></script></head>
    <body>
      <div id="my_img"></div>
      <script>
        var src_img = document.getElementById("my_img");
        var testimg = document.createElement("img"); 
        testimg.src = "https://revistahsm.com/wp-content/uploads/2018/08/Fiestas.png"; // example img
        testimg.width = 0;
        testimg.height = 0;
        src_img.appendChild(testimg);
      </script>
    </body>
  </html>
`, options);

方案2:关闭jsdom自动资源加载(轻量方案,适配仅canvas侧需要图片的场景)

如果你的业务逻辑不需要jsdom提前处理图片宽高、onload事件等属性,可直接关闭jsdom的自动资源加载,仅由canvas侧发起一次请求:
修改jsdom配置中的resources参数即可:

const options = { 
  runScripts: "dangerously", 
  url: `https://localhost:8800`,
  // 关闭jsdom自动加载外部资源,避免重复请求
  resources: "none" 
};

注意事项

  • 跨域资源需要配置正确的CORS响应头,否则会触发缓存失效
  • 若处理大量大体积图片,可给缓存增加过期淘汰逻辑,避免内存占用过高

内容的提问来源于stack exchange,提问作者soni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 14:06:03