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

Posted

技术标签:

【中文标题】在 Chrome 上使用 display:none 重置 GIF 动画的正确方法【英文标题】:Proper way to reset a GIF animation with display:none on Chrome 【发布时间】:2012-05-30 14:34:49 【问题描述】:

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

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

看看这个 JSFiddle 以供参考。 GIF 在 IE、Firefox 和 Chrome 上可以很好地重置。

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

检查这个JSFiddle。 GIF 会在 IE 和 Firefox 上正常重置,然后才会显示在页面中,但 Chrome 只是拒绝重置其动画!

到目前为止我所做的尝试:

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

到目前为止,我发现的唯一解决方法是将图像保持为其默认display,并通过.animate()ing 和.css()ing 处理不透明度、高度和可见性模拟display:none 行为所必需的。

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

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

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

【问题讨论】:

我制定了一个完整的解决方案。见这里:***.com/a/31093916/1520422 【参考方案1】:

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

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

【讨论】:

+1 因为它实际上“修复”了 chrome 中的问题,即使在显示之前请求新图像下载违背了我已经将其缓存在页面中的最初概念。我将把这个问题留得更久,以检查是否有任何其他可能的解决方案,因为现在我会保持我的假 display:none 行为。 这段代码去哪儿了?我很想看到 jsFiddle 的工作版本 感谢 Niet 的出色解决方案。这是我的 background-image 版本(虽然它确实需要 jQuery):img.css('background-image', img.css('background-image').replace(".gif", ".gif" + "?x=" + Math.random())); img.src=img.src 对我来说似乎足够了(我正在使用 Chrome 48)。谁能用其他浏览器确认? 我不赞成这个答案,因为这会导致互联网上最大的图像格式之一被一遍又一遍地下载。【参考方案2】:

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

在 Chrome 中,当您在不带参数的情况下调用 .show() 时,该元素实际上不会立即显示在您调用它的位置。取而代之的是,Chrome 排队将新样式的应用程序评估当前的 javascript 块之后执行;而其他浏览器会立即应用新的样式更改。但是,.attr() 不会排队。因此,当元素根据 Chrome 仍然不可见时,您实际上是在尝试设置 src,而当原始 src 和新 src 相同时,Chrome 将不会做任何事情。

相反,您需要做的是确保 jQuery 设置 src 之后 display:block 被应用。你可以使用setTimeout来实现这个效果:

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();
        
    );
);

这确保src 被设置 display:block 已应用于元素。

这样做的原因是因为 setTimeout 将函数排队等待执行 later(无论 later 有多长),因此该函数不再被视为当前的一部分JavaScript 的“块”,它为 Chrome 提供了一个间隙来首先呈现和应用 display:block,从而在设置其 src 属性之前使元素可见。

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

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


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

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

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

【讨论】:

迟到总比不到好,谢谢!我了解浏览器如何批量更改样式,但从未考虑过它适用于这个用例。我现在做一点实验。 +1 有用的信息 我已编辑您的答案以添加我将在实际场景中使用的替代方案,以便我可以接受您的答案而不是提交我自己的答案。谢谢。 如何使用它来重置设置为 css 背景图像而不是 SRC 的 gif?示例:bit.ly/1XKm8JC - 图像在第一次播放时运行良好,但在动画结束并返回开始后,Seasons Greetings gif 不会重新开始。我正在使用边缘动画 $('img').prop('src',function()return this.src;) 是这个答案的最小版本 似乎需要较大的超时值 (>500ms) 才能使 MS Edge 可靠运行【参考方案3】:

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

<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() 调用。

--妮可

【讨论】:

此功能完美运行。我唯一需要改变的是让它在 chrome 上同样工作:img.src = "#"; 如果第一个 src 完全为空,则重新启动。【参考方案4】:

solution 预加载 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>

【讨论】:

【参考方案5】:

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

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

【讨论】:

【参考方案6】:

这是我的背景图片技巧:

$(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;
);

【讨论】:

嗨,您能否提供一些关于需要更改哪些变量才能使用它的详细信息。我知道 var url 可以,但除此之外呢?【参考方案7】:

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

var asdf = $(".settings-button img").attr("src");
$(".settings-button img").attr("src", "").attr("src", asdf);

【讨论】:

【参考方案8】:

我在使用上述所有解决方案时都遇到了问题。最终奏效的是用透明的 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;
;

【讨论】:

【参考方案9】:

我在搜索了很多其他帖子后发现了这个帖子。 David Bell 的帖子让我找到了我需要的解决方案。

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

目标:

    当用户点击/单击按钮 A 时,会出现一个动画 gif 并播放。 当用户点击/单击按钮 B 时,gif 将消失。 当用户在点击/点击按钮后再次点击/点击按钮 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 设置为空字符串。

解决方案:

看了大卫·贝尔的帖子后,我得出了答案。

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

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

$('#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 时从头开始加载和播放。

【讨论】:

【参考方案10】:

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

我的解决方案在不从网络重新加载图像数据的情况下重新启动动画。

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

【讨论】:

【参考方案11】:

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

my previous answer 的问题在于,它会在您每次想要重新启动它时强制重新下载 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();

【讨论】:

这个问题是不支持data: URL 中的查询字符串,所以您在这里所做的是将无意义的未编码数据添加到 base64 编码的 GIF 数据的末尾,即浏览器呈现。我猜它可以工作,因为浏览器的 GIF 渲染器只是忽略了额外的数据。【参考方案12】:

重置 gif 动画

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

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

已解决的问题:

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

    重置 gif 动画。

    悬停时开始动画

重置 src 属性

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

缺点

重置所有具有相同 src 的图像的动画。 有problems in mobile devices

优点

简单快捷

随机查询修改url

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

缺点

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

优点

简单快捷

修改dataUrl(建议解决方案)

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

本文档中的 dataUrl 结构:

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

这就是specification中的指示方式:

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

如果您仔细查看mediatype 的描述,那么那里会显示一些奇怪的 parameter。但是,还有一个specification:

     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 会与同一张图片不同。

广义算法:

    我们通过常规请求加载图像,并从 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);
);

    使用src 属性"src=data:image/gif;base64;$Math.random()$dataUrl" 创建/编辑img 元素

    就是这样!

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>

示例反应

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
      
    />
  );


export default App;

【讨论】:

以上是关于在 Chrome 上使用 display:none 重置 GIF 动画的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

下一个 jquery 在 chrome 中返回 []

ios在画布上使用display:none绘制图像导致内存泄漏

display: none 是不是会影响导航菜单上的 SEO?

显示 None 正在占用空间

Chrome 65 无法打印隐藏的 iframe

scrollTop 和 scrollLeft 在 display:none 元素上不起作用