浏览器在简单幻灯片放映时会闪烁

前端开发 2026-07-09

我有一个用于网站的简单轮播案例。基本实现是一组开始时隐藏的html IMG对象。按顺序,每个IMG对象都加载一张图片并显示,同时前一个逐步被隐藏。

我的需求是在不使用任何外部代码、库或框架的前提下实现最小化。我也不希望有任何过渡效果,目标是让图片切换足够平滑,接近视频(但帧率不超过大约8 FPS),同时在客户端用最少的CPU。

我现在的实现能在Chrome(及相关浏览器)中工作,但在Firefox上不起作用。所谓“不起作用”,是指图片按顺序显示时,显示会闪烁(速度快于大约1200 ms时眼睛就感到难受)。我认为正确的方法是设置src属性,并注册onload处理程序,使图片在成功加载后再切换。作为对比,我也用过“朴素”策略:先设置src,然后立刻切换可见性。如果使用错的“朴素”策略,显示就会闪烁,这在所有浏览器中都会发生,这是可预测的。然而,使用更正确的策略(挂钩onload),在Chrome上可以正确工作,但在Firefox上却不行。Firefox的表现就像我使用了“朴素”策略一样闪烁。我也尝试过使用display样式(“inline” vs “none”)来替代visibility,一切都能在某种程度上工作,但表现并不相同。

我猜测这也许是Firefox的一个bug,或者是我在做某些事情,Chrome帟修正了。问题就是这样。

以下是一些代码片段,以便清楚地说明我在做什么:

var ix = 2;

function thing()
{
    let previous = document.getElementById("pic" + (ix - 1));
    let img = document.getElementById("pic" + ix);
    let url = "out" + ix + ".jpg";

    // this is the naive way of doing things, and it's horribly wrong and doesn't work
    if (true)
    {
        // start loading the image, switch to it immediately, and hope the browser gods will untangle it all
        img.src = url;
        img.style.visibility = "visible";
        previous.style.visibility = "hidden";
    }

    ix += 1;
    window.setTimeout(thing, 150);
}

thing();


// alternative 1.  this is where I started, and I expected it to work
if (true)
{
    // start loading the new image, but don't switch until it's loaded
    img.onload = function()
    {
        img.onload = null;
        img.style.visibility = "visible";
        previous.style.visibility = "hidden";
    };
    img.src = url;
}


// alternative 2.  I found a reference to the decode method, so I thought I'd try it
if (true)
{
    // start loading the new image, but don't switch until it's decoded
    img.src = url;
    // decode is supposed to return a promise for completion
    img.decode().then(function()
    {
        img.style.visibility = "visible";
        previous.style.visibility = "hidden";
    });
}

如果你想在实际运行中更容易看到效果,我在这里设置了一个最小演示: https://halo.hamstertoad.org/public/wwinton/specific/slideshow_vs_firefox/index.html?mode=onload&delay=333 。同时,这里是同样的内容,使用“错误”模式:index.html?mode=wrong 。这段代码在jpg引用后面添加了一些查询字符串垃圾,以防止浏览器缓存把事情混淆在一起(我也测试了不带可能污染结果的情况)。

编辑1:这个示例甚至不需要Web服务器。把所有图像文件和index.html放在任意本地目录,然后让Firefox指向它,就会出现同样的闪烁。

我发现有一个decode方法返回Promise,我原本希望它会在某些方面有所不同,但看起来它的行为与onload处理程序一样。

那到底是Firefox的一个bug,还是我在做的事有愚蠢之处,还是有某种变通方案?(无论如何,除了使用Chrome之外)

解决方案

你说得对,等待图片加载完成并没有帮助。因此,我对你的代码进行了某种改写,只使用一张图片。它仍然要等到图片加载完成,因为加载时间可能远长于150 ms。

现在的代码看起来像这样:

window.onload = main;

function main()
{
  let delay = 150;
  let imageNo = 1;
  let imageNameElement = document.getElementById("imagename");
  let imageElement = document.getElementById("image");

  async function nextImage()
  {
    let imageUrl = "https://halo.hamstertoad.org/public/wwinton/specific/slideshow_vs_firefox/out" + imageNo + ".jpg";
    imageNameElement.innerHTML = "showing: " + imageUrl;

    const loadImagePromise = new Promise(resolve => {
      imageElement.onload = resolve;
      imageElement.src = imageUrl;
    });

    await loadImagePromise;

    imageNo += 1;
    if (imageNo <= 70)
    {
      window.setTimeout(nextImage, delay);
    }
  }

  nextImage();

}
<div><span id="imagename">- - -</span></div>
<img id="image">

我保留了原本的代码结构,但其实可以进一步简化。我忍不住为变量命名用上了更易于理解的名称。

我移除了console日志,因为在你运行代码时,那里的小视窗很容易被日志信息迅速填满。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章