想在网页里用 OpenCV.js 做图像处理,标准写法是:
<script async src="https://docs.opencv.org/4.x/opencv.js" onload="onOpenCvReady();"></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);
但这个 onload 触发时,OpenCV 还没准备好——window.cv 存在,但里面的 native 函数还没初始化(emscripten 编译的 WASM 需要额外时间)。
正确姿势:等 onRuntimeInitialized
script.onload = () => {
cv.onRuntimeInitialized = () => {
console.log("OpenCV Ready");
onOpenCvReady();
};
};
cv.onRuntimeInitialized 是 emscripten 的钩子,WASM 加载并 instantiate 完毕后调用。这时 cv.imread、cv.Mat 才能真正用。
Promise 化封装
function loadOpenCV() {
return new Promise((resolve, reject) => {
// 已加载过
if (window.cv && cv.Mat) return resolve(cv);
// 加载中(多次调用不重复插 script)
if (window.cv) {
cv.onRuntimeInitialized = () => resolve(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 => {
const mat = new cv.Mat(100, 100, cv.CV_8UC3);
console.log(mat);
});
关键点:
if (window.cv && cv.Mat)— 双重判断,cv存在不代表 ready- 支持并发调用(多次
loadOpenCV()只加载一次真正的 script) - 错误传给 reject(网络挂了能捕获)
油猴脚本里加载
油猴脚本里 document.createElement("script") 遇到有 CSP(Content-Security-Policy)的网站会失败——unsafe-inline 被禁的情况下,外链 script 加载被阻。
用 @require 声明,油猴会把脚本预先拉下来注入到你的沙箱里:
// ==UserScript==
// @name Load OpenCV
// @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);
};
})();
@require 走的是 Tampermonkey / Violentmonkey / ScriptCat 自己的加载机制,不走页面 CSP。这是给受 CSP 保护的网站注 OpenCV 最稳的方式。
常见问题
1. cv is not defined
script 还没加载完就用 cv。用 Promise 版本,.then 里再用。
2. cv.imread is not a function
onRuntimeInitialized 没等——script.onload 触发早于 runtime init。
3. 用了 async/await 但还是异步崩溃
外面 async 函数忘了 await:
async function process() {
const cv = await loadOpenCV(); // 别忘 await
const mat = new cv.Mat(...);
}
4. 内存泄漏
OpenCV.js 的 Mat 是 WASM 分配的内存,必须手动 delete:
const mat = cv.imread("input");
try {
// 处理 mat
} finally {
mat.delete();
}
不 delete,跑几张图页面就 OOM。
想省点带宽
opencv.js 从官方 CDN 拉大约 8MB。有几个选择:
- 换镜像:
https://cdn.jsdelivr.net/npm/@techstark/opencv-js@4.x.x/dist/opencv.js - 裁剪版本:自己 build 只带需要的模块,
webnn、videoio之类不需要就砍 - 本地缓存:Service Worker 缓存 opencv.js,二次加载秒开
- 动态判断需求:只有用户点了”图像处理”按钮再加载
第一次加载 8MB 是常态,别期望闪电般。
一句话总结
动态加载 opencv.js 要等 cv.onRuntimeInitialized,封成 Promise 用起来最顺。油猴脚本里遇到 CSP 就用 @require。用完 Mat 记得 delete()。
