动态加载 OpenCV.js:createElement 与油猴 @require 的区别

createElement 动态加载

把原来的 <script> 标签写法:

<script async src="https://docs.opencv.org/4.x/opencv.js"
  onload="onOpenCvReady();" type="text/javascript"></script>

转成 JS 动态创建:

const script = document.createElement("script");
script.src = "https://docs.opencv.org/4.x/opencv.js";
script.async = true;
script.onload = () => {
  onOpenCvReady();
};
document.head.appendChild(script);

避免重复加载

if (!window.cv) {
  const script = document.createElement("script");
  script.src = "https://docs.opencv.org/4.x/opencv.js";
  script.async = true;
  script.onload = () => {
    console.log("OpenCV loaded");
    onOpenCvReady();
  };
  document.head.appendChild(script);
} else {
  onOpenCvReady();
}

onload 不等于 OpenCV 初始化完成

script.onload 只表示 JS 文件下载解析完毕,但 OpenCV.js 内部有一个 WebAssembly 初始化过程,cv 对象此时可能还未就绪。

如果在 onload 里立刻调用 new cv.Mat() 会报错。正确做法是等 cv.onRuntimeInitialized

script.onload = () => {
  cv["onRuntimeInitialized"] = () => {
    // 这里才能安全使用 cv API
    const mat = new cv.Mat();
    console.log("OpenCV Ready");
  };
};

封装成 Promise

function loadOpenCV() {
  return new Promise((resolve, reject) => {
    if (window.cv) {
      resolve(window.cv);
      return;
    }

    const script = document.createElement("script");
    script.src = "https://docs.opencv.org/4.x/opencv.js";
    script.async = true;

    script.onload = () => {
      cv["onRuntimeInitialized"] = () => {
        resolve(cv);
      };
    };

    script.onerror = reject;
    document.head.appendChild(script);
  });
}

loadOpenCV().then((cv) => {
  console.log("Ready");
  const mat = new cv.Mat(100, 100, cv.CV_8UC3);
  console.log(mat);
});

油猴脚本:@require 绕过 CSP

有些网页设置了 Content-Security-Policy(CSP),会阻止加载非白名单来源的外部脚本。这时用 createElement 注入 script 会直接被浏览器拦截。

油猴的 @require 是在油猴自身的沙箱里加载,不经过页面的 CSP,可以绕过这个限制:

// ==UserScript==
// @name         Load OpenCV
// @namespace    http://tampermonkey.net/
// @version      1.0
// @match        *://*/*
// @require      https://docs.opencv.org/4.x/opencv.js
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  cv.onRuntimeInitialized = () => {
    console.log('OpenCV Ready');

    const mat = new cv.Mat(100, 100, cv.CV_8UC3);
    console.log(mat.rows, mat.cols);
  };
})();

@require 里的脚本在油猴加载阶段执行,cv 全局变量在脚本运行时已经存在。

两种方式对比

方式适用场景CSP 限制
createElement + onload普通页面、自己控制的网页受页面 CSP 限制
@require油猴脚本不受页面 CSP 影响

如果网页没有 CSP,两种都能用,@require 写法更简洁;有 CSP 时只能用 @require