浏览器在简单幻灯片放映时会闪烁
我有一个用于网站的简单轮播案例。基本实现是一组开始时隐藏的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日志,因为在你运行代码时,那里的小视窗很容易被日志信息迅速填满。