为什么我的 JS 程序在有/没有“问题行”的情况下表现不同?

Ste*_*man 2 javascript google-chrome canvas getimagedata html5-canvas

问题出在handleLineEnd函数中。我撕掉了所有不需要显示问题的线条。

没有这条线:我可以在 ctx 上绘制多个矩形。

使用该线:每个矩形都会被下一个矩形覆盖。

该代码在旧版 Chrome (118.0.5993.71) 中运行良好,而在版本 119.0.6045.106 中则无法运行

function handleLineEnd(evt) {
  {
    startDrawing = false;
    var dummy = ctx.getImageData(3, 3, 5, 5); // problem line
    ctx.drawImage(canvaslayer, 0, 0);
    ctxlayer.clearRect(0, 0, el.width, el.height);
  }
}
Run Code Online (Sandbox Code Playgroud)

function handleLineEnd(evt) {
  {
    startDrawing = false;
    var dummy = ctx.getImageData(3, 3, 5, 5); // problem line
    ctx.drawImage(canvaslayer, 0, 0);
    ctxlayer.clearRect(0, 0, el.width, el.height);
  }
}
Run Code Online (Sandbox Code Playgroud)
var el = document.getElementById("canvasID");
var elcontainer = document.getElementById("containerID");
var ctx = el.getContext('2d');
var startpuntX, startpuntY, eindpuntX, eindpuntY;
var startDrawing = false;
var container = el.parentNode; // Add the temporary canvas.
var containerHandletje = document.getElementById('containerID');
var ongoingTouches = new Array();
var huidigeKleur = 'black';
var huidigeDikte = 2;
var selectie = document.getElementById('drawingtoolID');
elcontainer.setAttribute("style", 'width: ' + (window.innerWidth - 12) + 'px; height: ' + (window.innerHeight - 80) + 'px; overflow: auto;');
el.height = window.innerHeight - 90;
el.width = window.innerWidth - 42;
canvaslayer = document.createElement('canvas');
canvaslayer.id = 'imageTemp';
canvaslayer.width = el.width;
canvaslayer.height = el.height;
canvaslayer.style = 'touch-action:none;';
container.appendChild(canvaslayer);
ctxlayer = canvaslayer.getContext('2d');
ctxlayer.lineCap = 'round';
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, el.width, el.height);
ctx.lineWidth = huidigeDikte;
canvaslayer.addEventListener('pointerdown', handleLineStart, false);
canvaslayer.addEventListener('pointerup', handleLineEnd, false);
canvaslayer.addEventListener('pointermove', handleLineMove, false);

function handleLineStart(evt) {
  startpuntX = evt.clientX - el.offsetLeft + pageXOffset + containerHandletje.scrollLeft;
  startpuntY = evt.clientY - container.offsetTop + pageYOffset + containerHandletje.scrollTop;
  ctxlayer.lineWidth = huidigeDikte;
  ctxlayer.strokeStyle = huidigeKleur;
  ctxlayer.fillStyle = huidigeKleur;
  startDrawing = true;
  ctxlayer.stroke();
  ctxlayer.beginPath();
  handleLineMove(evt);
}

function handleLineMove(evt) {
  var x, y, w, h, hokDx, xi, yi, asX, asY, asXeind, asYeind, fontsize, dzx, dzy, situatie;
  if (startDrawing) {
    eindpuntX = evt.clientX - el.offsetLeft + pageXOffset + containerHandletje.scrollLeft;
    eindpuntY = evt.clientY - container.offsetTop + pageYOffset + containerHandletje.scrollTop;
    ctxlayer.clearRect(0, 0, el.width, el.height);
    ctxlayer.beginPath();
    ctxlayer.moveTo(startpuntX, startpuntY);
    x = Math.min(startpuntX, eindpuntX);
    y = Math.min(startpuntY, eindpuntY);
    w = Math.abs(startpuntX - eindpuntX);
    h = Math.abs(startpuntY - eindpuntY);
    ctxlayer.strokeRect(x, y, w, h);
    ctxlayer.stroke();
    ctxlayer.beginPath();
  }
}

function handleLineEnd(evt) {
  {
    startDrawing = false;
    var dummy = ctx.getImageData(3, 3, 5, 5); // problem line
    ctx.drawImage(canvaslayer, 0, 0);
    ctxlayer.clearRect(0, 0, el.width, el.height);
  }
}
Run Code Online (Sandbox Code Playgroud)
#containerID {
  position: relative;
}

#canvasID {
  border: 1px solid #000;
}

#imageTemp {
  position: absolute;
  top: 1px;
  left: 11px;
}
Run Code Online (Sandbox Code Playgroud)

Kai*_*ido 5

这是由该 CL引起的已知且已修复的 Chrome 错误(CRBUG 1500272CRBUG 1499539 ) 。基本上他们正在重写他们的画布层逻辑,当这些逻辑被刷新时,这就会变得混乱。粗略地说,调用确实会将层从 GPU 移动到 CPU,并且该更改使桥无法正确处理该移动,从而使 GPU 端没有资源提供者。getImageData()

请注意,我们可以将错误重现减少到:

const el = document.querySelector("canvas");
const ctx = el.getContext('2d');
const canvaslayer = document.createElement('canvas');
const ctxlayer = canvaslayer.getContext('2d');

const draw = (pos) => {
  ctxlayer.fillRect(pos, pos, 30, 30);
  ctx.getImageData(3, 3, 5, 5); // problem line
  ctx.drawImage(canvaslayer, 0, 0);
  ctxlayer.clearRect(0, 0, el.width, el.height);
};

draw(30);
// We need to wait at least  after the next painting frame
// But 1s makes it clearer somethign is wrong.
setTimeout(() => draw(60), 1000);
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)

可能的解决方法:

不要使用加速画布。通过使用该willReadFrequently选项请求 2D 上下文,您将避免画布移动。如果您要在画布上进行多次读回,您可能确实需要考虑此选项,即使没有错误。移动画布非常慢,并且您通过硬件加速可能获得的一点点(根据您绘制的内容,甚至可能不会太多)将在这些调用中丢失。

const el = document.querySelector("canvas");
// Since we're going to do read-backs on this context
// let's deaccelerate it.
const ctx = el.getContext('2d', { willReadFrequently: true });
const canvaslayer = document.createElement('canvas');
const ctxlayer = canvaslayer.getContext('2d');

const draw = (pos) => {
  ctxlayer.fillRect(pos, pos, 30, 30);
  ctx.getImageData(3, 3, 5, 5); // problem line
  ctx.drawImage(canvaslayer, 0, 0);
  ctxlayer.clearRect(0, 0, el.width, el.height);
};

draw(30);
setTimeout(() => draw(60), 1000);
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)

如果您确实认为必须使用加速画布,那么您可以使用OffscreenCanvas代替canvaslayer <canvas>元素。OffscreenCanvas不会导致该错误(无论出于何种原因......)。

const el = document.querySelector("canvas");
const ctx = el.getContext('2d');
const canvaslayer = new OffscreenCanvas(300, 300);
const ctxlayer = canvaslayer.getContext('2d');

let pos = false;
const draw = (pos) => {
  ctxlayer.fillRect(pos, pos, 30, 30);
  ctx.getImageData(3, 3, 5, 5); // problem line
  ctx.drawImage(canvaslayer, 0, 0);
  ctxlayer.clearRect(0, 0, el.width, el.height);
};

draw(30);
setTimeout(() => draw(60), 1000);
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)

不过,从您的代码来看,由于您需要layercanvas可见,因此您将使用layercanvas.transferControlToOffscreen()而不是构造函数,以便该<canvas>元素成为占位符画布并仍然将其内容绘制到屏幕上。

var el = document.getElementById("canvasID");
var elcontainer = document.getElementById("containerID");
var ctx = el.getContext('2d');
var startpuntX, startpuntY, eindpuntX, eindpuntY;
var startDrawing = false;
var container = el.parentNode; // Add the temporary canvas.
var containerHandletje = document.getElementById('containerID');
var ongoingTouches = new Array();
var huidigeKleur = 'black';
var huidigeDikte = 2;
var selectie = document.getElementById('drawingtoolID');
elcontainer.setAttribute("style", 'width: ' + (window.innerWidth - 12) + 'px; height: ' + (window.innerHeight - 80) + 'px; overflow: auto;');
el.height = window.innerHeight - 90;
el.width = window.innerWidth - 42;
const canvaslayerEl = document.createElement('canvas');
canvaslayerEl.id = 'imageTemp';
canvaslayerEl.width = el.width;
canvaslayerEl.height = el.height;
canvaslayerEl.style = 'touch-action:none;';
container.appendChild(canvaslayerEl);
// Use an OffscreenCanvas to avoid Chrome bug 1500272
const canvaslayer = canvaslayerEl.transferControlToOffscreen();
const ctxlayer = canvaslayer.getContext('2d');
ctxlayer.lineCap = 'round';
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, el.width, el.height);
ctx.lineWidth = huidigeDikte;
canvaslayerEl.addEventListener('pointerdown', handleLineStart, false);
canvaslayerEl.addEventListener('pointerup', handleLineEnd, false);
canvaslayerEl.addEventListener('pointermove', handleLineMove, false);

function handleLineStart(evt) {
  startpuntX = evt.clientX - el.offsetLeft + pageXOffset + containerHandletje.scrollLeft;
  startpuntY = evt.clientY - container.offsetTop + pageYOffset + containerHandletje.scrollTop;
  ctxlayer.lineWidth = huidigeDikte;
  ctxlayer.strokeStyle = huidigeKleur;
  ctxlayer.fillStyle = huidigeKleur;
  startDrawing = true;
  ctxlayer.stroke();
  ctxlayer.beginPath();
  handleLineMove(evt);
}

function handleLineMove(evt) {
  var x, y, w, h, hokDx, xi, yi, asX, asY, asXeind, asYeind, fontsize, dzx, dzy, situatie;
  if (startDrawing) {
    eindpuntX = evt.clientX - el.offsetLeft + pageXOffset + containerHandletje.scrollLeft;
    eindpuntY = evt.clientY - container.offsetTop + pageYOffset + containerHandletje.scrollTop;
    ctxlayer.clearRect(0, 0, el.width, el.height);
    ctxlayer.beginPath();
    ctxlayer.moveTo(startpuntX, startpuntY);
    x = Math.min(startpuntX, eindpuntX);
    y = Math.min(startpuntY, eindpuntY);
    w = Math.abs(startpuntX - eindpuntX);
    h = Math.abs(startpuntY - eindpuntY);
    ctxlayer.strokeRect(x, y, w, h);
    ctxlayer.stroke();
    ctxlayer.beginPath();
  }
}

function handleLineEnd(evt) {
  {
    startDrawing = false;
    var dummy = ctx.getImageData(3, 3, 5, 5); // problem line
    ctx.drawImage(canvaslayer, 0, 0);
    ctxlayer.clearRect(0, 0, el.width, el.height);
  }
}
Run Code Online (Sandbox Code Playgroud)
#containerID {
  position: relative;
}

#canvasID {
  border: 1px solid #000;
}

#imageTemp {
  position: absolute;
  top: 1px;
  left: 11px;
}
Run Code Online (Sandbox Code Playgroud)
<div id="containerID" style="touch-action:none; width: 100px; height: 500px; overflow: auto;">
  <canvas id='canvasID' width='80' height='80' style='margin-left:10px;background:white;border:solid black 1px; touch-action:none; '>
    Your browser does not support canvas element.
  </canvas>
</div>
Run Code Online (Sandbox Code Playgroud)

但这是一个丑陋的黑客行为,因为这个错误已经被修复,并且应该在下一个 Chrome 版本中消失(当前的 Chrome Beta 已经修复了)。