Ogi*_*tre 23 javascript safari webkit mobile-safari html5-canvas
我们正在开发一个可视化Web应用程序,它使用d3-force在画布上绘制网络.由于每个节点都包含大量信息,并且由于CPU占用大量资源来重绘每个帧的所有内容,因此我们实现了一种缓存,其中每个节点都在其画布上绘制(未链接到DOM),每个缩放级别一次.然后在节点的位置在主画布(链接到DOM)上绘制那些画布.
我们对速度增益感到满意,即使结果现在是内存密集型(特别是在hidpi显示器(视网膜)上,像素密度可以是2或3).
但现在我们在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
Run Code Online (Sandbox Code Playgroud)
然后在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')
Run Code Online (Sandbox Code Playgroud)
存在,我创建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')
Run Code Online (Sandbox Code Playgroud)
因为它是一个缓存我应该能够删除一些元素,但我不是(因为将ctxs或ctx设置为null应该触发GC,但它不能解决问题).
我在这个问题上找到的唯一相关页面是webkit源代码页:HTMLCanvasElement.cpp.
我怀疑问题可能来自webkit本身,但我想在发布到webkit问题跟踪器之前确定.
有没有其他方法来破坏画布上下文?
提前感谢任何想法,指针,......
EDITS:
为了添加一些信息,我尝试了其他浏览器.Safari 12在macOS上也存在同样的问题,即使限制较高(计算机内存的1/4,如webkit源中所述).我也试过最新的webkit build(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)
Run Code Online (Sandbox Code Playgroud)
我花了一个周末制作了一个简单的网页,可以快速显示问题。我已经向Google和Apple提交了错误报告。该页面将显示一个地图。您可以平移和缩放所有所需的内容,而Safari检查器(在iPad上运行网络,使用MacBook Pro查看画布)看不到画布。
然后,您可以点击一个按钮并绘制一条折线。当您这样做时,您会看到41幅画布。平移或缩放,您将看到更多。每个画布为1MB,因此在您拥有256个孤立的画布之后,由于iPad上的画布内存已满,错误开始出现。
重新加载页面,点击一个按钮以放置一个多边形,然后发生同样的事情。
同样有趣的是,我添加了按钮来为白天和黑夜设置地图样式。您可以仅在地图上来回移动(或仅带有标记的地图,有一个按钮可以在地图上显示一些标记)。没有孤立的画布。但是画一条线,然后在更改样式时,会看到更多的孤立画布。
在Active Monitor中的MacBook上查看Safari时,绘制多边形后平移和缩放地图时,尺寸保持不变
我希望苹果和谷歌能解决这个问题,而不是声称这是另一家公司的问题。IOS12运行的网页已经稳定多年,并且仍然可以在IOS 9和10 iPad上运行,因此,所有这些都发生了变化,我一直进行测试以确保较旧的设备可以显示当前网页。希望这项测试/实验对您有所帮助。
另一个数据点:我发现 Safari Web Inspector (12.1 - 14607.1.40.1.4) 保留在打开时创建的每个 Canvas 对象,即使它们会被垃圾收集。关闭网络检查器并重新打开它,大多数旧画布都会消失。
这并没有解决最初的问题——在不运行 web-inspector 时超出画布内存,但在不知道这个小花絮的情况下,我浪费了大量时间走错路,以为我没有释放任何临时画布.
有人发布了答案,显示了解决方法。想法是在删除画布之前将高度和宽度设置为0。这不是一个真正合适的解决方案,但是它将在我的缓存系统中运行。
我添加了一个小示例,该示例创建画布直到引发异常,然后清空缓存并继续。
感谢现在发布此答案的匿名人士。
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 = nbImage => {
// create i * 1MB images
const canvases = []
for (let i = 0; i < nbImage; i++) {
canvases.push(createImage())
}
console.log(`done for ${canvases.length} MB`)
return canvases
}
const deleteCanvases = canvases => {
canvases.forEach((canvas, i, a) => {
canvas.height = 0
canvas.width = 0
})
}
let canvases = []
const process = (frequency, size) => {
setInterval(() => {
try {
canvases.push(...createImages(size))
counter += size
console.log(`total ${counter}`)
}
catch (e) {
deleteCanvases(canvases)
canvases = []
}
}, frequency)
}
process(2000, 1000)
Run Code Online (Sandbox Code Playgroud)
小智 5
WebKit的最近更改可能会导致这些问题 https://github.com/WebKit/webkit/commit/5d5b478917c685e50d1032ccf761ca53fc8f1b74#diff-b411cd4839e4bbc17b00570536abfa8f
| 归档时间: |
|
| 查看次数: |
6444 次 |
| 最近记录: |