【发布时间】:2018-09-27 08:26:53
【问题描述】:
我们正在开发一个visualization web application,它使用 d3-force 在画布上绘制网络。
但现在我们遇到了 iOS 浏览器的问题,即进程在与界面进行几次交互后崩溃。 在我的记忆中,这不是旧版本(iOS12 之前)的问题,但我没有任何未更新的设备来确认这一点。
我认为这段代码总结了问题:
const { range } = require('d3-array')
// create a 1MB image
const createImage = () => {
const size = 512
const canvas = document.createElement('canvas')
canvas.height = size
canvas.width = size
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, size, size)
return canvas
}
const createImages = i => {
// create i * 1MB images
let ctxs = range(i).map(() => {
return createImage()
})
console.log(`done for ${ctxs.length} MB`)
ctxs = null
}
window.cis = createImages
然后在 iPad 和检查器中:
> cis(256)
[Log] done for 256 MB (main-a9168dc888c2e24bbaf3.bundle.js, line 11317)
< undefined
> cis(1)
[Warning] Total canvas memory use exceeds the maximum limit (256 MB). (main-a9168dc888c2e24bbaf3.bundle.js, line 11307)
< TypeError: null is not an object (evaluating 'ctx.strokeRect')
我创建了 256 x 1MB 的画布,一切顺利,但我又创建了一个,canvas.getContext 返回一个空指针。 这样就不可能再创建一个画布了。
限制似乎与设备相关,因为在 iPad 上为 256MB,在 iPhone X 上为 288MB。
> cis(288)
[Log] done for 288 MB (main-a9168dc888c2e24bbaf3.bundle.js, line 11317)
< undefined
> cis(1)
[Warning] Total canvas memory use exceeds the maximum limit (288 MB). (main-a9168dc888c2e24bbaf3.bundle.js, line 11307)
< TypeError: null is not an object (evaluating 'ctx.strokeRect')
因为它是一个缓存,我应该可以删除一些元素,但我不能(因为将 ctxs 或 ctx 设置为 null 应该会触发 GC,但这并不能解决问题)。
我在这个问题上找到的唯一相关页面是 webkit 源代码页面:HTMLCanvasElement.cpp。
我怀疑问题可能来自 webkit 本身,但我想在发布到 webkit 问题跟踪器之前确定。
还有其他方法可以破坏画布上下文吗?
提前感谢您的任何想法,指针,...
更新
我发现这个 Webkit 问题(可能)是对这个错误的描述: https://bugs.webkit.org/show_bug.cgi?id=195325
为了添加一些信息,我尝试了其他浏览器。 Safari 12 在 macOS 上也有同样的问题,即使限制更高(计算机内存的 1/4,如 webkit 资源中所述)。我还尝试了最新的 webkit 构建(236590),但运气不佳。 但该代码适用于 Firefox 62 和 Chrome 69。
我改进了测试代码,因此可以直接从调试器控制台执行。如果有人可以在较旧的 safari(如 11)上测试代码,那将非常有帮助。
let counter = 0
// create a 1MB image
const createImage = () => {
const size = 512
const canvas = document.createElement('canvas')
canvas.height = size
canvas.width = size
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, size, size)
return canvas
}
const createImages = n => {
// create n * 1MB images
const ctxs = []
for( let i=0 ; i<n ; i++ ){
ctxs.push(createImage())
}
console.log(`done for ${ctxs.length} MB`)
}
const process = (frequency,size) => {
setInterval(()=>{
createImages(size)
counter+=size
console.log(`total ${counter}`)
},frequency)
}
process(2000,1000)
【问题讨论】:
-
你打算为每个矩形创建一个画布元素吗?
-
对于这样的可视化,您可能需要查看 webgl... 无论如何,在低端设备上获取如此多的明文 + 缩放 + 平移对于 2d 上下文来说将变得很困难。您可以尝试仅将 sankey 链接保存在画布上,并在每一帧重绘所有节点,这是两个世界的混合,但每个节点的画布确实太多了。 Ps:我怀疑这是硬件限制而不是软件限制。
-
感谢您的这些想法。在 webgl 中绘制这些东西(例如字体)有点像噩梦。该项目最初使用的是svg,后来修改为使用canvas。所以对我来说,实现这种“缓存”机制而不是 WebGL 更有效。我也不确定硬件限制,因为我认为它之前(在 iOS10 上)工作过,更重要的是,画布没有加载到 DOM 中,所以(据我了解)它们只不过是数据数组。
-
@enxaneta 这段代码几乎只是为了显示我的主要问题。在应用程序中,每个画布都包含一个节点。
-
@OgierMaitre 你有没有找到解决这个问题的办法?
标签: javascript safari html5-canvas webkit mobile-safari