如何在延迟加载图像时防止 requestAnimationFrame 断断续续?
How to prevent requestAnimationFrame choppiness whilst lazy loading images?
画故事:
我有一些视觉内容被分成图像集(图块)。
我正在使用一个简单的循环,它被 requestAnimationFrame
调用以在 canvas 上显示这些图块。
实际上我正在使用 PixiJS 来处理这些东西;虽然它可能是一个与这个问题不太相关的实现细节。
因为可能有很多图块,并且因为我希望用户能够 'somewhat immediately' 滚动浏览这些图块集,所以我只预加载了一组特定的图块,然后当用户进一步导航 tileset,另一个 tiles 流被加载,直到没有更多的 tiles 加载。
视觉内容完全加载后,requestAnimationFrame
循环运行良好,没有任何断断续续的情况。
但是在延迟加载过程中,偶尔会出现一些不稳定的情况。
我尝试使用 setInterval
将延迟加载(以及垃圾管理或垃圾收集)与 requestAnimationFrame
循环分开。
但后来我意识到这是因为 JavaScript 只有一个线程,因此图像加载阻塞了其余代码。所以 setInterval
没有帮助;也不会是延迟加载的第二个 requestAnimatonFrame
调用。
然后我阅读了有关 Web Workers 的内容,它确实设置了并发执行事物的可能性。但是当我读取 Web Worker 线程中的所有数据时,它们都被复制而不是通过引用传递。所以它会占用双倍的内存。
而且我怀疑 Web Worker 是否适合同时加载图块,因为 Mozilla 开发者网络页面大多指定它作为并行(重)数字运算解决方案的帮助。
有特定的Web Worker类型;位于网页请求和 Web 服务器之间的 Service Worker。 Service Worker 会在执行绘制循环时帮助我加载所有图像吗?
如果没有,是否有人知道另一种方法既可以延迟加载又可以对已加载的图像集进行动画处理?有什么明显的我忽略了吗?
编辑3:请仔细看;图像加载时出现卡顿;有时比其他时候更多;但它仍然存在;因为我被指示按照评论部分删除我的模拟,所以它不太明显;但口吃仍然存在。更容易看到是否有人展开代码段,然后在打开开发人员控制台后,转到“网络”选项卡并选中“禁用缓存”,单击 运行-按钮。
Edit 2:忽略edit 1。因为模拟已经被真实图像所取代,现在正在加载。第一次加载图像或关闭浏览器缓存时,卡顿很明显。
编辑 1: 作为示例要求。延迟加载阻塞是通过循环模拟的,直到执行了某个随机延迟。这些颜色应该代表图块图像,但实际上是使用随机颜色从 canvas 中绘制的。
var tileImageURLs = [
"https://i.postimg.cc/FdFCY0RL/tile1.png",
"https://i.postimg.cc/v1tS80Qy/tile2.png",
"https://i.postimg.cc/3yTcHsQ9/tile3.png",
"https://i.postimg.cc/Hr5hRpRV/tile4.png",
"https://i.postimg.cc/fJ1P83HG/tile5.png",
"https://i.postimg.cc/LnJ7kkVk/tile6.png",
];
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var y = 0;
var NORMALSPEED = 5;
var currentSpeed = NORMALSPEED;
var TILEWIDTH = 768;
var TILEHEIGHT = 1024;
var TILECOUNT = 6;
var tileImages = [];
var lastTileLoad = -1;
var tileLoadStart = 0;
var PRELOADEDTILESCOUNT = 2;
// For offscreen generation of tiles.
//var tileCanvas = document.createElement('canvas');
//tileCanvas.width = TILEWIDTH;
//tileCanvas.height = TILEHEIGHT;
//var tileCtx = tileCanvas.getContext('2d');
//var tileColors = ['red', 'green', 'blue', 'orange', 'yellow'];
for (var tileIdx = 0; tileIdx < TILECOUNT; tileIdx++) {
tileImages[tileIdx] = document.createElement('img');
tileImages[tileIdx].width = TILEWIDTH;
tileImages[tileIdx].height = TILEHEIGHT;
}
function loadTileImages(tileStart, tileEnd) {
if (tileImages[tileStart]) {
tileImages[tileStart].onload = function() {
// Image loaded; go to next tile if applicable.
if (tileStart + 1 <= tileEnd) {
loadTileImages(++tileStart, tileEnd);
}
}
tileImages[tileStart].src = tileImageURLs[tileStart];
}
}
function loadTiles() {
var tileLoadCount;
if (lastTileLoad < Math.round(y / (canvas.height * 1))) {
/**
* Load checkpoint which lies past previous load checkpoint found;
* so load some stuff.
*/
tileLoadCount = Math.min(tileLoadStart + PRELOADEDTILESCOUNT - 1, TILECOUNT);
loadTileImages(tileLoadStart, tileLoadCount);
tileLoadStart += PRELOADEDTILESCOUNT;
if (tileLoadStart > TILECOUNT - 1) {
/**
* Stop the loading; Infinity is always bigger than a non-infinite number.
*/
console.log('Loading has finished.');
lastTileLoad = Infinity;
} else {
/**
* Store this 'load checkpoint'.
*/
//this.needToDrawFrame = true;
lastTileLoad = Math.round(y / (canvas.height * 1));
}
}
}
function tick() {
var tileImgY;
if (currentSpeed > 0 && (y >= (TILECOUNT * TILEHEIGHT) - canvas.height)) {
currentSpeed = -NORMALSPEED;
} else if (currentSpeed < 0 && (y <= 0)) {
currentSpeed = NORMALSPEED;
}
y += currentSpeed;
loadTiles();
var tileStart = Math.max(Math.floor(y / TILEHEIGHT) - 1, 0);
var tileCount = Math.min(tileStart + Math.ceil(canvas.height / TILEHEIGHT) + 2, TILECOUNT);
//console.log(y, tileStart, tileCount);
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var tileIndex = tileStart; tileIndex < tileCount; tileIndex++) {
var tileImg = tileImages[tileIndex];
tileImgY = (tileIndex * TILEHEIGHT) - y;
ctx.drawImage(tileImg, 0, tileImgY, TILEWIDTH, TILEHEIGHT);
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
#canvas {
width: 500px;
height: 500px;
border: solid 1px black;
}
<canvas id="canvas" width="768" height="1024"></canvas>
我自己的问题有了答案;这主要是我的愚蠢。
事实证明,它在加载阶段额外调用了代码函数(由 requestAnimationFrame
调用)(旨在确保在新的图块图像到达时刷新屏幕)。
这些调用导致了额外运动的突然爆发,让人感觉有些口吃(顺便说一句,比我在我的示例中能够重现的更重)。
但在我使用 Web Worker 中的 createImageBitmap
加载图像后,部分卡顿 - 微卡顿 - 消失了。
画故事:
我有一些视觉内容被分成图像集(图块)。
我正在使用一个简单的循环,它被 requestAnimationFrame
调用以在 canvas 上显示这些图块。
实际上我正在使用 PixiJS 来处理这些东西;虽然它可能是一个与这个问题不太相关的实现细节。
因为可能有很多图块,并且因为我希望用户能够 'somewhat immediately' 滚动浏览这些图块集,所以我只预加载了一组特定的图块,然后当用户进一步导航 tileset,另一个 tiles 流被加载,直到没有更多的 tiles 加载。
视觉内容完全加载后,requestAnimationFrame
循环运行良好,没有任何断断续续的情况。
但是在延迟加载过程中,偶尔会出现一些不稳定的情况。
我尝试使用 setInterval
将延迟加载(以及垃圾管理或垃圾收集)与 requestAnimationFrame
循环分开。
但后来我意识到这是因为 JavaScript 只有一个线程,因此图像加载阻塞了其余代码。所以 setInterval
没有帮助;也不会是延迟加载的第二个 requestAnimatonFrame
调用。
然后我阅读了有关 Web Workers 的内容,它确实设置了并发执行事物的可能性。但是当我读取 Web Worker 线程中的所有数据时,它们都被复制而不是通过引用传递。所以它会占用双倍的内存。
而且我怀疑 Web Worker 是否适合同时加载图块,因为 Mozilla 开发者网络页面大多指定它作为并行(重)数字运算解决方案的帮助。
有特定的Web Worker类型;位于网页请求和 Web 服务器之间的 Service Worker。 Service Worker 会在执行绘制循环时帮助我加载所有图像吗?
如果没有,是否有人知道另一种方法既可以延迟加载又可以对已加载的图像集进行动画处理?有什么明显的我忽略了吗?
编辑3:请仔细看;图像加载时出现卡顿;有时比其他时候更多;但它仍然存在;因为我被指示按照评论部分删除我的模拟,所以它不太明显;但口吃仍然存在。更容易看到是否有人展开代码段,然后在打开开发人员控制台后,转到“网络”选项卡并选中“禁用缓存”,单击 运行-按钮。
Edit 2:忽略edit 1。因为模拟已经被真实图像所取代,现在正在加载。第一次加载图像或关闭浏览器缓存时,卡顿很明显。
编辑 1: 作为示例要求。延迟加载阻塞是通过循环模拟的,直到执行了某个随机延迟。这些颜色应该代表图块图像,但实际上是使用随机颜色从 canvas 中绘制的。
var tileImageURLs = [
"https://i.postimg.cc/FdFCY0RL/tile1.png",
"https://i.postimg.cc/v1tS80Qy/tile2.png",
"https://i.postimg.cc/3yTcHsQ9/tile3.png",
"https://i.postimg.cc/Hr5hRpRV/tile4.png",
"https://i.postimg.cc/fJ1P83HG/tile5.png",
"https://i.postimg.cc/LnJ7kkVk/tile6.png",
];
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var y = 0;
var NORMALSPEED = 5;
var currentSpeed = NORMALSPEED;
var TILEWIDTH = 768;
var TILEHEIGHT = 1024;
var TILECOUNT = 6;
var tileImages = [];
var lastTileLoad = -1;
var tileLoadStart = 0;
var PRELOADEDTILESCOUNT = 2;
// For offscreen generation of tiles.
//var tileCanvas = document.createElement('canvas');
//tileCanvas.width = TILEWIDTH;
//tileCanvas.height = TILEHEIGHT;
//var tileCtx = tileCanvas.getContext('2d');
//var tileColors = ['red', 'green', 'blue', 'orange', 'yellow'];
for (var tileIdx = 0; tileIdx < TILECOUNT; tileIdx++) {
tileImages[tileIdx] = document.createElement('img');
tileImages[tileIdx].width = TILEWIDTH;
tileImages[tileIdx].height = TILEHEIGHT;
}
function loadTileImages(tileStart, tileEnd) {
if (tileImages[tileStart]) {
tileImages[tileStart].onload = function() {
// Image loaded; go to next tile if applicable.
if (tileStart + 1 <= tileEnd) {
loadTileImages(++tileStart, tileEnd);
}
}
tileImages[tileStart].src = tileImageURLs[tileStart];
}
}
function loadTiles() {
var tileLoadCount;
if (lastTileLoad < Math.round(y / (canvas.height * 1))) {
/**
* Load checkpoint which lies past previous load checkpoint found;
* so load some stuff.
*/
tileLoadCount = Math.min(tileLoadStart + PRELOADEDTILESCOUNT - 1, TILECOUNT);
loadTileImages(tileLoadStart, tileLoadCount);
tileLoadStart += PRELOADEDTILESCOUNT;
if (tileLoadStart > TILECOUNT - 1) {
/**
* Stop the loading; Infinity is always bigger than a non-infinite number.
*/
console.log('Loading has finished.');
lastTileLoad = Infinity;
} else {
/**
* Store this 'load checkpoint'.
*/
//this.needToDrawFrame = true;
lastTileLoad = Math.round(y / (canvas.height * 1));
}
}
}
function tick() {
var tileImgY;
if (currentSpeed > 0 && (y >= (TILECOUNT * TILEHEIGHT) - canvas.height)) {
currentSpeed = -NORMALSPEED;
} else if (currentSpeed < 0 && (y <= 0)) {
currentSpeed = NORMALSPEED;
}
y += currentSpeed;
loadTiles();
var tileStart = Math.max(Math.floor(y / TILEHEIGHT) - 1, 0);
var tileCount = Math.min(tileStart + Math.ceil(canvas.height / TILEHEIGHT) + 2, TILECOUNT);
//console.log(y, tileStart, tileCount);
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var tileIndex = tileStart; tileIndex < tileCount; tileIndex++) {
var tileImg = tileImages[tileIndex];
tileImgY = (tileIndex * TILEHEIGHT) - y;
ctx.drawImage(tileImg, 0, tileImgY, TILEWIDTH, TILEHEIGHT);
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
#canvas {
width: 500px;
height: 500px;
border: solid 1px black;
}
<canvas id="canvas" width="768" height="1024"></canvas>
我自己的问题有了答案;这主要是我的愚蠢。
事实证明,它在加载阶段额外调用了代码函数(由 requestAnimationFrame
调用)(旨在确保在新的图块图像到达时刷新屏幕)。
这些调用导致了额外运动的突然爆发,让人感觉有些口吃(顺便说一句,比我在我的示例中能够重现的更重)。
但在我使用 Web Worker 中的 createImageBitmap
加载图像后,部分卡顿 - 微卡顿 - 消失了。