在Chrome上重置带有display:none的GIF动画的正确方法

Proper way to reset a GIF animation with display:none on Chrome

提问人:Fabrício Matté 提问时间:5/24/2012 最后编辑:Jimbo JonnyFabrício Matté 更新时间:8/30/2021 访问量:58528

问:

标题是不言自明的,但我将提供关于此事的分步视图。希望我不是第一个在 Webkit/Chrome 上注意到这个(显然)错误的人。

我想重置GIF动画。到目前为止,我看到的所有示例要么简单地将图像设置为自身,要么将其设置为空字符串,然后再次将原始字符串设置为原始字符串。srcsrc

请看一下这个 JSFiddle 以供参考。GIF 在 IE、Firefox 和 Chrome 上重置得非常好。

我遇到的问题是图像仅在 Google Chrome 上。display:none

检查此 JSFiddle。GIF 在显示在页面中之前在 IE 和 Firefox 上可以正常重置,但 Chrome 只是拒绝重置其动画!

到目前为止,我尝试过:

  • 像在 Fiddle 中一样将其设置为自身,在 Chrome 中不起作用。src
  • 将 设置为空字符串并将其恢复为默认值也不起作用。src
  • 在图像周围放置一个包装器,清空容器并将图像放回其中,也不起作用。.html('')
  • 在设置之前通过或更改图像也不起作用。display.show().fadeIn()src

到目前为止,我发现的唯一解决方法是将图像保留为默认值,并在必要时通过不透明度、高度和可见性来操纵它以模拟行为。display.animate().css()display:none

这个问题的主要原因(上下文)是我想在页面中淡入淡出之前重置ajax加载器GIF。

所以我的问题是,有没有一种正确的方法可以重置 GIF 图像的动画(避免 Chrome 的“错误”),或者它实际上是一个错误?display:none

(附言。您可以更改小提琴中的 GIF,以获得更合适/更长的动画 gif 以进行测试)

javascript jquery html google-chrome animated-gif

评论

0赞 Frederic Leitenberger 6/28/2015
我制定了一个完整的解决方案。请看这里:stackoverflow.com/a/31093916/1520422

答:

33赞 Niet the Dark Absol 5/24/2012 #1

“重置”GIF 最可靠的方法是附加随机查询字符串。但是,这确实意味着每次都会重新下载 GIF,因此请确保它是一个小文件。

// reset a gif:
img.src = img.src.replace(/\?.*$/,"")+"?x="+Math.random();

评论

4赞 Fabrício Matté 5/24/2012
+1,因为它实际上“修复”了 Chrome 中的问题,尽管在显示图像之前请求下载新图像违背了我最初的想法,即已经将其缓存在页面中。我会把这个问题保持开放一段时间,以检查是否有任何其他可能的解决方案,因为现在我会保留我的虚假行为。display:none
0赞 egr103 2/23/2013
这段代码去哪儿了?我很想看到一个带有 jsFiddle 的工作版本
1赞 Stan 1/18/2016
感谢您的出色解决方案Niet。这是我的版本(虽然它确实需要jQuery):background-imageimg.css('background-image', img.css('background-image').replace(".gif", ".gif" + "?x=" + Math.random()));
0赞 Aizzat Suhardi 3/14/2016
img.src=img.src对我来说似乎已经足够了(我使用的是 Chrome 48)。任何人都可以与其他浏览器确认吗?
1赞 Connor Ross 5/24/2017
我对这个答案投反对票,因为这会导致互联网上最大的图像格式之一一遍又一遍地被下载。
4赞 teebot 11/19/2012 #2

解决方案预加载 gif 并将其从 dom 中取出,然后返回 src(从而避免再次下载)

我刚刚使用 jquery 测试了它以删除该属性,它工作正常。

例:

<html>
<head>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js" type="text/javascript"></script>

<script>

$(function() {

    $('.reset').click(resetGif);

    function resetGif() 
    {
        $('.img1').removeAttr('src', '');
    }
});

</script>

</head>

<body>
    <img class="img1" src="1.gif" />
    <a href="#" class="reset">reset gif</a>
</body>
</html>
32赞 Kal 1/14/2014 #3

Chrome 处理样式更改的方式与其他浏览器不同。

在 Chrome 中,当您在没有参数的情况下调用时,该元素实际上不会立即显示在您调用它的位置。相反,Chrome 会在评估当前的 JavaScript 块,将新样式的应用程序排队等待执行;而其他浏览器会立即应用新的样式更改。但是,不会排队。因此,您实际上是在尝试根据 Chrome 设置元素何时仍然不可见,并且当原始元素和新元素相同时,Chrome 不会对此采取任何措施。.show().attr()srcsrcsrc

相反,您需要做的是确保应用了 jQuery 设置 after。你可以利用 来实现这个效果:srcdisplay:blocksetTimeout

var src = 'http://i.imgur.com/JfkmXjG.gif';
$(document).ready(function(){
    var $img = $('img');
    $('#target').toggle(
        function(){
            var timeout = 0; // no delay
            $img.show();
            setTimeout(function() {
                $img.attr('src', src);
            }, timeout);
        },
        function(){
            $img.hide();
        }
    );
});

这可确保在应用于元素之后进行设置。srcdisplay:block

这样做的原因是 setTimeout 将函数排入队列以供稍后执行(无论以后多久),因此该函数不再被视为当前 JavaScript“块”的一部分,并且它为 Chrome 提供了渲染和应用第一个块的间隙,从而使元素在设置其属性之前可见。display:blocksrc

演示:http://jsfiddle.net/F8Q44/19/

感谢 shoky in #jquery freenode IRC 提供了更简单的答案。


或者,您可以强制重绘以刷新批处理的样式更改。例如,可以通过访问元素的属性来完成此操作:offsetHeight

$('img').show().each(function() {
    this.offsetHeight;
}).prop('src', 'image src');

演示:http://jsfiddle.net/F8Q44/266/

评论

0赞 Fabrício Matté 1/14/2014
迟到总比没有好,谢谢!我了解浏览器如何批量更改样式,但从未考虑过它适用于此用例。我现在要做一点实验。+1 获取有用信息
0赞 Fabrício Matté 1/14/2014
我已经编辑了您的答案以添加我在真实场景中使用的替代方案,因此我可以接受您的答案,而不是提交我自己的答案。谢谢。
1赞 DevKev 12/4/2015
如何使用它来重置设置为 css 背景图像而不是 SRC 的 gif?示例:bit.ly/1XKm8JC - 图像在第一次播放时效果很好,但在动画结束并返回开头后,Seasons Greetings gif 不会重新开始。我正在使用 Edge Animate
0赞 Aizzat Suhardi 3/14/2016
$('img').prop('src',function(){return this.src;})是这个答案的最小版本
2赞 Crazometer 6/15/2016
似乎需要较大的超时值 (>500ms) 才能使 MS Edge 可靠地运行
1赞 ayal gelles 1/25/2014 #4

这是我对背景图片的技巧:

$(document).on('mouseenter', '.logo', function() {
  window.logo = (window.logocount || 0) + 1;
  var img = new Image();
  var url = "/img/mylogimagename.gif?v=" + window.logocount;
var that = this;
  $(img).load(function(){

     $(that ).css('background-image','url(' + url + ')');
  });
  img.src = url;
});

评论

0赞 DevKev 12/4/2015
嗨,您能否提供有关需要更改哪些变量才能使用它的一些详细信息。我知道 var url 确实如此,但是其他呢?
4赞 David Bell 4/14/2014 #5

这似乎在 Chrome 中对我有用,它每次都在我淡入图像之前运行并清除然后重新填充 src,我的动画现在每次都从头开始。

var imgsrc = $('#my_image').attr('src');
$('#my_image').attr('src', '');
$('#my_image').attr('src', imgsrc);
0赞 Banjo Drill 10/30/2014 #6

在搜索了许多其他线程后,我遇到了这个线程。David Bell 的帖子引导我找到了我需要的解决方案。

我想我会发布我的经验,以防它对任何试图完成我所追求的事情的人都有用。这适用于 HTML5/JavaScript/jQuery Web 应用程序,该应用程序将通过 PhoneGap 成为 iPhone 应用程序。在 Chrome 中进行测试。

目标:

  1. 当用户点击/单击按钮 A 时,会出现并播放动画 gif。
  2. 当用户点击/单击按钮 B 时,gif 会消失。
  3. 当用户再次点击/单击按钮 A 时,点击/单击按钮后 B,动画gif应该重新出现并从头开始播放。

问题:

  • 点击/单击按钮 A 时,我将 gif 附加到现有的 div 中。它会玩得很好。
  • 然后,在点击/单击按钮 B 时,我隐藏了容器 div,然后将 gif 的 img src 设置为空字符串 ('')。同样,没有问题(也就是说,问题还不明显。
  • 然后,在点击/单击按钮 A 时,在点击/单击按钮 B 后,我将 gif 的路径重新添加为 src。

    - 这不起作用。gif 将显示在按钮 A 的后续点击/点击中......但是,我点击/单击按钮 A 的次数越多,gif 加载和重新开始的次数就越多。也就是说,如果我来回点击/单击按钮 A 然后点击按钮 B 3 次,gif就会出现,然后开始/停止/启动 3 次......我的整个应用程序开始嘎嘎作响。我猜 gif 被加载了多次,即使我在点击/单击按钮 B 时将 src 设置为空字符串。

解决方案:

看完David Bell的帖子后,我得出了答案。

  1. 我定义了一个全局变量(我们称之为 myVar),它保存了容器 div 和图像(带有源路径)。
  2. 在按钮 A 的点击/单击功能中,我将该容器 div 附加到 dom 中现有的父 div。
  3. 在该函数中,我创建了一个新变量来保存 gif 的 src 路径。

就像 David 建议的那样,我这样做了(加上一个附加):

$('#mainParent').append(myVar);
var imgsrc = $('#my_image').attr('src');
$('#my_image').attr('src', '');
$('#my_image').attr('src', imgsrc);

然后,在按钮 B 的函数中,我将 src 设置为空字符串,然后删除包含 gif 的 div:

$('#my_image').attr('src', '');
$('#mainParent').find('#my_image').remove();

现在,我可以一整天点击/单击按钮 A,然后是按钮 B,然后是按钮 A,依此类推。gif 在点击/单击按钮 A 时加载和播放,然后在点击/单击按钮 B 时隐藏,然后每次在按钮 A 的后续点击中从头开始加载和播放,没有任何问题。

5赞 nico 2/24/2015 #7

仅仅因为我仍然时不时地需要它,我认为我使用的纯 JS 函数可能对其他人有所帮助。这是一种纯粹的 JS 方式,无需重新加载动画 gif。您可以从链接和/或文档加载事件中调用它。

<img id="img3" src="../_Images/animated.gif">

<a onClick="resetGif('img3')">reset gif3</a>

<script type="text/javascript">

// reset an animated gif to start at first image without reloading it from server.
// Note: if you have the same image on the page more than ones, they all reset.
function resetGif(id) {
    var img = document.getElementById(id);
    var imageUrl = img.src;
    img.src = "";
    img.src = imageUrl;
};

</script>

在某些浏览器上,您只需要将 img.src 重置为自身即可正常工作。在IE上,您需要在重置之前清除它。这个 resetGif() 从图像 ID 中选取图像名称。如果您更改给定 id 的实际图像链接,这很方便,因为您不必记得更改 resetGiF() 调用。

--尼科

评论

1赞 vinni 4/14/2016
这个功能工作得很好。我唯一需要更改的是使其在 chrome 上同样工作:如果第一个 src 完全为空,则重新启动。img.src = "#";
0赞 Frederic Leitenberger 6/28/2015 #8

我为这个问题制定了一个完整的解决方案。可以在这里找到它: https://stackoverflow.com/a/31093916/1520422

我的解决方案重新启动动画,而无需从网络重新加载图像数据。

它还强制图像重新绘制,以修复发生的一些绘画伪影(在镀铬中)。

2赞 Pratyush 8/31/2016 #9

我有一个按钮,里面有一个动画无循环图像。我只是用一些jquery重新加载图像,这似乎对我有用。

var asdf = $(".settings-button img").attr("src");
$(".settings-button img").attr("src", "").attr("src", asdf);
1赞 Daniel Howard 6/13/2017 #10

我在使用上述所有解决方案时都遇到了问题。最终起作用的是用透明的 1px gif 暂时替换 src:

var transparent1PxGif = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
var reloadGif = function(img) {
    var src = img.src;
    img.src = transparent1PxGif;
    img.offsetHeight; // triggers browser redraw
    img.src = src;
};
1赞 Niet the Dark Absol 7/5/2017 #11

已经有好几年了,我决定重新审视这个问题,因为我们有许多新的选择可供我们使用。

我之前回答的问题是,每次您想重新启动 GIF 时,它都会强制重新下载 GIF。虽然这对于小文件来说很好,但如果可能的话,它仍然是一个最好避免的开销。

考虑到这一点,我有了一个新的解决方案,它使用 AJAX 下载一次 GIF,然后将其转换为数据 URL(通过 FileReader),并将其用作附加随机查询字符串的源。

这样,浏览器只下载一次图像,甚至可以正确缓存它,并且“重置”从该预下载的资源中提取。

当然,唯一的问题是,您必须确保在使用它之前正确加载它。

演示:http://adamhaskell.net/misc/numbers/numbers.html

相关代码:

var url = "something.gif"; // fallback until the FileReader is done
function setup() {
    var xhr = new XMLHttpRequest();
    xhr.open("GET",url,true);
    xhr.responseType = "blob";
    xhr.onreadystatechange = function() {
        if( this.readyState == 4) {
            var fr = new FileReader();
            fr.onload = function() {
                url = this.result; // overwrite URL with the data one
            };
            fr.readAsDataURL(this.response);
        }
    };
    xhr.send();
}
function getGIF() {
    return url+"?x="+Math.random();
}

评论

0赞 Tim Down 11/26/2018
这样做的问题是 URL 中不支持查询字符串,因此您在这里所做的是将无意义的未编码数据添加到浏览器呈现的 base64 编码 GIF 数据的末尾。我猜它之所以有效,是因为浏览器的 GIF 渲染器只是忽略了额外的数据。data:
3赞 marvinav 8/26/2021 #12

重置 gif 动画

当浏览器渲染时,属性中指定的源将缓存到内存中以备将来重用。这可以提高页面加载/重新加载的速度,并减少网络上的负载。这种行为几乎适合所有人,因为实际上,这是最优化和最需要的选择。imgsrc

但是,与往常一样,也有例外。我想出了一个基于 dataUrl 的动画更新选项,可以解决几个问题。

解决的问题:

  1. 需要显示带有动画的 gif 图像,没有循环 (loop = 1),这可能具有相同的 .但是当出现一张这样的图片时,它有必要播放动画而不改变其他图片的动画。同一张图片只能从服务器加载一次。堆栈溢出srcsrc

  2. 重置 gif 动画。

  3. 悬停时开始动画

重置 src 属性

如果我们使用清除图像属性的解决方案,那么具有相同源的所有图像都将重播其动画。不幸的是,我仍然不完全理解为什么会发生这种情况,但它干扰了正确的工作。src

缺点

  • 使用相同的 src 重置所有图像的动画。
  • 移动设备存在问题

优点

  • 简单快捷

使用随机查询修改网址

此解决方案包括在属性的末尾添加一个随机查询参数,以便所有图像都具有不同的来源,因此它们将彼此独立地进行动画处理。有一个很大的 NO:这将导致不断请求服务器下载图片,因此它们将不再被缓存。如果我们需要显示 100 张相同的图片,那么将有 100 个请求到服务器。粗糙而坚韧,但它总是有效。src

缺点

  • 每张具有唯一查询的图片都将从服务器重新加载。

优点

  • 简单快捷

修改 dataUrl(建议的解决方案)

数据 URL,以 data: 方案为前缀的 URL,允许内容创建者在文档中内嵌小文件。它们以前称为“数据 URI”,直到 WHATWG 停用该名称。MDN网络

本文档中的 dataUrl 结构:

data:[<mediatype>][;base64],<data>

这就是规范中指出的方式:

dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype  := [ type "/" subtype ] *( ";" parameter )
data       := *urlchar
parameter  := attribute "=" value

如果你仔细看一下的描述,那么那里会显示出一些奇怪的东西。但是,还有一个规范mediatype parameter

     attribute := token
                  ; Matching of attributes
                  ; is ALWAYS case-insensitive.

     value := token / quoted-string

     token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, or tspecials>

     tspecials :=  "(" / ")" / "<" / ">" / "@" /
                   "," / ";" / ":" / "\" / <">
                   "/" / "[" / "]" / "?" / "="
                   ; Must be in quoted-string,
                   ; to use within parameter values

可以看出,我们可以指定任何参数,最主要的是它满足上面提出的要求! 因此,我们可以在 mediatype 中嵌入一个额外的属性,这不会以任何方式影响图像,但数据 url 会与同一图像不同。

广义算法:

  1. 我们通过常规请求加载图像,并从 blob 中从创建的 dataUrl 中删除元数据。
fetch("https://cdn140.picsart.com/330970106009201.gif").then(async (res) => {
  const blob = await res.blob();
  const reader = new FileReader();
  reader.onload = (ev) => {
    // It would be reasonable to remove metadata to the point!
    // But for simplicity, I'm using this implementation.
    const dataUrl = ev.currentTarget.result.replace(
      "data:image/gif;base64",
      ""
    );
  };
  reader.readAsDataURL(blob);
});
  1. 创建/编辑具有属性的元素imgsrc"src=data:image/gif;base64;${Math.random()}${dataUrl}"

  2. 仅此而已!

示例:Vanila JS

const url = "https://cdn140.picsart.com/330970106009201.gif";

function loadImage(src) {
  fetch(src)
    .then((res) => res.blob())
    .then(async(blob) => {
      const reader = new FileReader();
      reader.onload = (ev) => {
        const dataUrl = ev.currentTarget.result.replace("data:image/gif;base64", "")
        const container = document.getElementById("container");
        while (container.firstChild) {
          container.firstChild.remove()
        }
        for (let i = 0; i < 6; i++) {
          const img = document.createElement("img");
          img.setAttribute("src", `data:image/gif;base64;gif-id=${Date.now()}${dataUrl}`)
          container.appendChild(img);
          img.addEventListener('click', ev => {
            img.setAttribute("src", `data:image/gif;base64;gif-id=${Date.now()}${dataUrl}`)
          })
        }
      };
      reader.readAsDataURL(blob);
    });
}

loadImage(url);

function updateImage() {
  const newSrc = document.getElementById("image-src");
  loadImage(document.getElementById("image-src").value);
}
#main {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

img {
  width: 128px;
  height: 128px;
  padding: 5px;
}
<div id="main">
  <label>Change gif url if current will become unavailable </label>
  <input id="image-src" value="https://cdn140.picsart.com/330970106009201.gif"></input>
  <button onclick="updateImage()">Update image source attribute</button>
  <span>Click to reset!</span>
  <div id="container">
  </div>
</div>

示例 React

import React, { useState, useRef } from "react";

function App() {
  const [stars, setStars] = useState(0);
  const [data, setData] = useState(null);

  const ref = useRef(null);

  React.useEffect(() => {
    fetch("https://cdn140.picsart.com/330970106009201.gif")
      .then((res) => res.blob())
      .then(async (text) => {
        const reader = new FileReader();
        reader.onload = (ev) => {
          setData(ev.currentTarget.result.replace("data:image/gif;base64", ""));
        };
        reader.readAsDataURL(text);
      });
  }, []);

  return (
    <React.Fragment>
      <p onClick={() => setStars((s) => s + 1)}>+</p>
      {data &&
        new Array(stars).fill().map((s, ind) => {
          return <Star src={data} key={ind}></Star>;
        })}
      <p onClick={() => setStars((s) => (s === 0 ? 0 : s - 1))}>-</p>
    </React.Fragment>
  );
}

export function Star(props) {
  const [id] = useState(Math.random());

  return (
    <img
      className="icon"
      src={`data:image/gif;base64;gif-id=${id}` + props.src}
      alt="animated star"
    />
  );
}

export default App;