首页 > 解决方案 > 如何在延迟加载图像时防止 requestAnimationFrame 断断续续?

问题描述

描绘故事:

我有一些视觉内容,它们分为图像集(图块)。

我正在使用一个简单的循环,它被调用requestAnimationFrame以在画布上显示这些图块。

实际上我正在使用 PixiJS 来处理这些东西;虽然它可能是一个与这个问题不太相关的实现细节。

因为可能有很多图块,并且因为我希望用户能够“稍微立即”滚动浏览这些图块集,所以我只预加载了一组图块,然后当用户进一步导航图块集时,另一个瓦片流被加载,直到没有更多瓦片要加载。

当视觉内容完全加载时,requestAnimationFrame循环运行良好,没有任何波动。

但是在延迟加载期间,有时会出现一些波动。

我尝试使用setInterval将延迟加载(以及废物管理或垃圾收集)与requestAnimationFrame循环分开。

但后来我意识到这是因为 JavaScript 只有一个线程,因此加载图像会阻塞其余代码。所以setInterval没有帮助;也不会是requestAnimatonFrame对延迟加载的第二次调用。

然后我读到了 Web Workers,它确实为并发执行事物提供了可能性。但是当我读取 Web Worker 线程中的所有数据时,它会被复制而不是通过引用传递。所以它会占用双倍的内存。

而且我怀疑 Web Worker 是否适合同时加载磁贴,因为 Mozilla 开发人员网络页面大多将其指定为并行(重)数字处理解决方案的帮助。

有特定的 Web Worker 类型;位于网页请求和 Web 服务器之间的 Service Worker。Service Worker 会在执行绘制循环时帮助我加载图像吗?

如果没有,是否有人知道仍然延迟加载但还为已加载的图像集设置动画的另一种方法?有什么明显的我忽略了吗?

编辑3:请仔细看;图像加载时出现口吃;有时比其他时候更多;但它仍然存在;因为根据评论部分的指示,我被指示删除我的模拟,所以不太明显;但口吃仍然存在。更容易查看是否展开了代码段,然后在打开开发者控制台后,转到网络选项卡并选中“禁用缓存”,单击运行按钮。

编辑2:忽略编辑1。因为模拟已经被真实图像加载所取代。首次加载图像或关闭浏览器缓存时,卡顿很明显。

编辑1:根据要求提供示例。延迟加载阻塞是通过循环来模拟的,直到执行了某个随机延迟。颜色应该代表平铺图像,但实际上是使用随机颜色从画布中绘制的。

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>

标签: javascriptmultithreadinglazy-loadingweb-workerrequestanimationframe

解决方案


我对自己的问题有一个答案;这主要是我的愚蠢。

事实证明,它requestAnimationFrame在加载阶段对 ticker 函数(由 调用)进行了额外的调用(旨在确保在新的平铺图像到达时刷新屏幕)。

这些调用引起了额外的运动,让人感觉有些口吃(顺便说一下,比我在我的示例中能够重现的重)。

createImageBitmap但是在我使用Web Worker 加载图像后,部分口吃 - 微口吃 - 消失了。


推荐阅读