本页面正在开发中,所以时间回调一年…
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 <script> (function () { var style = document.createElement('style'); style.textContent = 'html,body{margin:0!important;height:100%!important}#app{position:fixed;inset:0;z-index:2147483647;background:#fff;overflow:auto}'; document.head.appendChild(style); var link = document.createElement('link'); link.rel = 'stylesheet'; link.href = 'https://g.alicdn.com/CPO-platform/recruit-portal/1.0.6/css/index.css'; document.head.appendChild(link); var app = document.createElement('div'); app.id = 'app'; document.body.appendChild(app); window.g_config = window.g_config || {}; window.__userconfig = window.__userconfig || {}; window.__sysconfig = window.__sysconfig || {}; var srcs = [ 'https://g.alicdn.com/code/lib/react/17.0.2/umd/react.production.min.js', 'https://g.alicdn.com/code/lib/react-dom/17.0.2/umd/react-dom.production.min.js', 'https://g.alicdn.com/code/lib/prop-types/15.8.1/prop-types.min.js', 'https://g.alicdn.com/CPO-platform/recruit-portal/1.0.6/js/index.js' ]; (function next(i) { if (i >= srcs.length) return; var s = document.createElement('script'); s.src = srcs[i]; s.onload = function () { next(i + 1); }; s.onerror = function () { next(i + 1); }; document.head.appendChild(s); })(0); })(); </script>
阿里招聘门户(g.alicdn.com/CPO-platform/recruit-portal/*/js/index.js)里有一段 three.js 的 WebGL 背景特效。它的渲染器在组件卸载时只删 DOM、不释放 WebGL 上下文 ,属于上下文泄漏;同一页面里反复挂载/卸载就会把浏览器每个页面的 WebGL 上下文额度(Chrome 约 16 个)耗尽,随后浏览器会丢弃最老的上下文,把同页面里别人的 WebGL 渲染 一起搞挂。
出问题的代码 渲染器是每次挂载都重新 new 一个 canvas + context(NW.getRenderer):
1 2 3 4 5 6 7 NW .getRenderer = function (e, t, n = {} ) { var o = document .createElement ("canvas" ); var u = NW .useWebGL2 ? o.getContext ("webgl2" , l) : o.getContext ("webgl" , l) || o.getContext ("experimental-webgl" , l); return new GB ({ canvas : o, context : u, preserveDrawingBuffer : i, antialias : a }, t); };
背景组件 init 调用它(注意 preserveDrawingBuffer: true,上下文更贵):
1 2 3 4 5 6 7 8 this .init = () => { c = NW .getRenderer (p, false , { preserveDrawingBuffer : true }); c.setPixelRatio (u.dpr ); c.setSize (o, a); c.setClearColor (14540253 ); t.appendChild (c.domElement ); };
而卸载时没有 renderer.dispose() / forceContextLoss():
1 2 3 4 5 6 this .destroy = () => { h.dispose (); m.dispose (); let e = t.childNodes ; for (let n = e.length - 1 ; n >= 0 ; n--) t.removeChild (e[n]); };
只要这个组件在同文档里被多次挂载(SPA 路由来回切、多实例、热更新等),上下文就会一直累积。Chrome 达到上限后控制台会出现:
1 WARNING: Too many active WebGL contexts. Oldest context will be lost.
并触发最老那个 canvas 的 webglcontextlost。
最小复现 下面这段就是同一套逻辑:一个 canvas 代表「你自己的 WebGL 渲染」,按钮每 120ms 模拟一次门户背景的挂载/卸载(创建 context,然后把 canvas 从 DOM 移除,但不释放 context)。到第 16 次左右,你的 canvas 会被浏览器丢弃。
修复方向 门户那边正确的做法是在 destroy() 里释放渲染器与上下文,例如:
1 2 3 4 5 6 7 8 9 this .destroy = () => { h.dispose (); m.dispose (); if (c) { c.dispose (); c.forceContextLoss (); c.domElement .remove (); } c = null ; };