移动版Chrome浏览器中动画卡顿

5

我正在尝试在我的网站上添加动画效果。我使用了下面链接的 jsfiddle 代码的类似版本。在桌面端查看时,动画效果良好。但是,在移动端(特别是在我的 Chrome 浏览器中)会出现奇怪的延迟。当我在手机上打开 jsfiddle 时也会显示完全相同的延迟。如果我重新启动 Chrome 应用程序,则延迟会消失,但很快就会再次出现。

这个问题在 Safari 中并不会出现。

我使用的是最新的 IOS 14.6 版本和 Chrome V90,拥有最新的 iPhone。

https://jsfiddle.net/brodriguez98/e2bvwcja/33/

HTML:

<html>
 <p style = 'margin-top: 100vh;'>above</p>
 
 <img class = 'balltest show-on-scroll standard-push' src = 'http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png'/>
 
 <img class = 'balltest show-on-scroll fade-in' src = 'http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png'/>
  
 <p style = 'margin-bottom: 100vh'>below</p>
</html>

CSS:

.balltest {
    width: 50px;
}

.fade-in {
    opacity: 0;
    -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
    will-change: transform, opacity;
}

.standard-push {
    opacity: 0;
    transform: translateY(4em);
    -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out, translateZ(0);
    -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
    transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
    will-change: transform, opacity;
}

.is-visible {
    transform: translateY(0);
    opacity: 1;
}

Javascript:

var elementsToShow = document.querySelectorAll('.show-on-scroll');
$(window).scroll(function() {
    Array.prototype.forEach.call(elementsToShow, function (element) {
        if (isElementInViewport(element)) {
            element.classList.add('is-visible');
        } else {
            element.classList.remove('is-visible');
        }
    });
});


// Helper function from: https://dev59.com/6nVD5IYBdhLWcg3wAGeD#7557433
function isElementInViewport(el) {
    // special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }
    var rect = el.getBoundingClientRect();
    return (
        (rect.top <= 0 &&
            rect.bottom >= 0) ||
        (rect.bottom >= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.top <= (window.innerHeight || document.documentElement.clientHeight)) ||
        (rect.top >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight))
    );
}

非常抱歉,手机上无法在全屏模式下打开JSfiddle, 展示的页面较小:

动画在重新启动手机Chrome后工作正常: https://www.loom.com/share/ac6c843b90d2428bb875572d55e32959

当我关闭/重新加载页面后,动画很快就会出现问题: https://www.loom.com/share/e51cf88aa1a74aed8e4d1ed253e83ea0

这与我在使用移动Chrome浏览器访问我的网站时看到的完全相同。

更新: 以下任何答案都没有对我起作用。我忘记提到这种情况也发生在文本上。谢谢你建议codesandbox,我复制了你的代码并删除了图片,但是在我的iPhone Chrome浏览器上仍然得到了相同的结果。我还尝试使用onload函数包装所有内容,但也不起作用。

目前我已经通过JQuery动画解决了这个问题,但我仍然希望CSS3转换可以在我的网站上正常工作。

https://codesandbox.io/s/animation-test-forked-tqurn?file=/index.html

enter image description here


1
无法在Android 9,Chrome 91.0.44.72.164上重现。我认为这可能是硬件问题,设备仍在忙于初始化转换。作为解决方法,您可以隐藏元素,而不是使用过渡,使用动画来转换元素并使它们再次可见。 - W4G1
1
我也遇到了同样的问题。我必须重新启动Chrome才能使动画正常工作。但是几次点击后,问题又出现了。我的页面只包含通过TailwindCSS实现的CSS3动画(变换和过渡),没有JS动画。 - William Ode
1
我也遇到了在转换上使用过渡的问题,你找到了任何解决方法吗? - llccrr
5个回答

1
这似乎是加载页面时出现了“竞态条件”问题。 JS在IMG请求完成之前运行。
为了理解问题,有必要了解加载顺序:
  1. 服务器在加载/重新加载时,响应文档(*.html)文件。

  2. 浏览器开始解析响应(*.html),并为每个找到的资源发起新请求:

    • CSS
    • JS
    • IMGs
  3. 这些请求以不可预测的顺序完成。例如,大型图像可能需要比 *.css 文件更长时间才能加载,有些资源可能已经被浏览器缓存,并且根本不会发出请求...

    如果 *.js 文件的请求在 IMGs 请求完成之前完成,则该图像没有渲染出 height,新添加的 CSS 类 is-visible 将仍然启动 transition...

  4. 一旦 IMG 请求完成(img 被渲染),将触发 内容回流 以进行 IMG。 需要重绘的元素(IMG)上的正在进行的转换将被“重置”,并从关键帧 0 开始。 这可能可以解释您的问题。


以下是可能解决您问题的3个选项:

A. 保留图像的最终尺寸。

  • Set a fix height in CSS and add class in html:

    .myImg {
      width: 50px;
      height: 50px;
    }
    
  • You could also add width and height as html attributes. The final dimension is now available in JS even if *.css is still loading...

    <img height="50" width="50" class="..." src="...">
    

B. 为图片添加“加载检测”,并在图像完全加载之前阻止转换。

  • 我们检查图片是否已经加载: src 已设置且 height 已检测 -
  • 否则,为该图像设置一个 onload 事件(因为它尚未加载)
  • 可选:您可以使用懒加载来加载该图像,并仅按需加载图像(请参见最终示例)。img 的 src 设置为 data-src 属性,JS 会在图像可用时设置 src

现在,我们可以使用 isLoaded(element) 函数来排除当前未完全加载的图片在 .scroll() 中。

这里是 jsFiddle,或者展开下面的示例...

var elementsToShow = document.querySelectorAll('.show-on-scroll');
$(window).scroll(function() {
    Array.prototype.forEach.call(elementsToShow, function (element) {
        if (isLoaded(element) && isElementInViewport(element)) {
            element.classList.add('is-visible');
        } else {
            element.classList.remove('is-visible');
        }
    });
});

[...elementsToShow].forEach((imgEl, i) => {
    if (
    imgEl.src &&
    imgEl.getBoundingClientRect().height
  ) {
    imgEl.dataset.isLoaded = true;
    console.log(`Img ${i} already loaded`);
  } else {
    console.log(`Img ${i} still loading... or should be lazyloaded`);

    imgEl.onload = function(e) {
      console.log(`Img ${i} finally loaded! onload event`);
        e.target.dataset.isLoaded = true;
        };

    if (imgEl.dataset.src) {
      console.log(`Img ${i} start lazy load...`);
        imgEl.src = imgEl.dataset.src;
    }
  }
})

function isLoaded(el) {
    return el.dataset.isLoaded
}

var elementsToShow = document.querySelectorAll('.show-on-scroll');
$(window).scroll(function() {
  Array.prototype.forEach.call(elementsToShow, function(element) {
    if (isLoaded(element) && isElementInViewport(element)) {
      element.classList.add('is-visible');
    } else {
      element.classList.remove('is-visible');
    }
  });
});

[...elementsToShow].forEach((imgEl, i) => {
  if (
    imgEl.src &&
    imgEl.getBoundingClientRect().height
  ) {
    imgEl.dataset.isLoaded = true;
    console.log(`Img ${i} already loaded`);
  } else {
    console.log(`Img ${i} still loading... or should be lazyloaded`);
    
    imgEl.onload = function(e) {
      console.log(`Img ${i} finally loaded! onload event`);
      e.target.dataset.isLoaded = true;
    };
    
    if (imgEl.dataset.src) {
      console.log(`Img ${i} start lazy load...`);
      imgEl.src = imgEl.dataset.src;
    }
  }
});

function isLoaded(el) {
  return el.dataset.isLoaded
}

// Helper function from: https://dev59.com/6nVD5IYBdhLWcg3wAGeD#7557433
function isElementInViewport(el) {
  // special bonus for those using jQuery
  if (typeof jQuery === "function" && el instanceof jQuery) {
    el = el[0];
  }
  var rect = el.getBoundingClientRect();
  return (
    (rect.top <= 0 &&
      rect.bottom >= 0) ||
    (rect.bottom >= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.top <= (window.innerHeight || document.documentElement.clientHeight)) ||
    (rect.top >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight))
  );
}
.balltest {
  width: 50px;
}

.fade-in {
  opacity: 0;
  -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
  will-change: transform, opacity;
}

.standard-push {
  opacity: 0;
  transform: translateY(4em);
  -webkit-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out, translateZ(0);
  -moz-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  -o-transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 1s 0.25s ease-out;
  transition: transform 4s 0.25s cubic-bezier(0, 1, 0.3, 1), opacity 0.3s 0.25s ease-out;
  will-change: transform, opacity;
}

.is-visible {
  transform: translateY(0);
  opacity: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
<p style='margin-top: 100vh;'>above</p>

<img class='balltest show-on-scroll standard-push' src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />
<img class='balltest show-on-scroll fade-in' src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />

<img class='balltest show-on-scroll standard-push' data-src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />
<img class='balltest show-on-scroll fade-in' data-src='http://www.pngall.com/wp-content/uploads/5/Sports-Ball-Transparent.png' />

<p style='margin-bottom: 100vh'>below</p>

</html>

C. 等待文档的 load 事件

您可以将JS初始化代码包装到整个文档的load事件中。该事件在所有资源(CSS,IMG等)完全加载后触发。

window.addEventListener('load', (event) => {
    // JS init code hier (images are loaded at this point!)
});

0
在IOS的Google Chrome上运行时,这是一个简化的样例,会有卡顿现象。Safari运行顺畅无痛点。
希望这能进一步帮助缩小问题范围并记录差异。
<div class="menu__icon icon-menu">
  <span></span>
  <span></span>
  <span></span>
</div>

.icon-menu {
    cursor: pointer;
    display: block;
    height: 18px;
    position: absolute;
    left: 28px;
    top: 52px;
    width: 28px;
    z-index: 5;
}

.icon-menu span {
    will-change: transform;
    background-color: #018d8d;
    height: 2px;
    left: 0;
    position: absolute;
    top: calc(50% - 1px);
    -webkit-transition: all .3s ease 0s;
    transition: all .3s ease 0s;
    width: 100%;
}

.icon-menu span:first-child {
    top: -8px;
}

.icon-menu span:nth-child(2) {
    top: 0;
}

.icon-menu span:last-child {
    top: 8px;
}

.icon-menu._active span:first-child {
    top: -1px;
    -webkit-transform: rotate(-45deg);
    transform: rotate(-45deg);
}

.icon-menu._active span {
    -webkit-transform: scale(0);
    transform: scale(0);
}

.icon-menu._active span:last-child {
    top: -1px;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
}

const element = document.querySelector('.menu__icon');

element.addEventListener('click', () => {
  console.log('clicked');
  element.classList.toggle('_active');
});

https://codepen.io/dblue71/pen/dyzMWmO


0

我在iPhone的Chrome浏览器上测试了您的代码,但无法重现屏幕录制中显示的错误。

可能是因为尝试在移动Chrome浏览器上运行整个jsfiddle Web应用程序导致延迟?除了您正在测试的任何实际输出之外,这是一个具有许多底层操作的重型Web应用程序,因此可能会出现性能问题。最好只测试输出本身。

我已将您的代码迁移到codesandbox,这将允许您在移动浏览器中查看输出(请参见下文)。您可以自行判断您所见到的问题是否是实际的代码错误。

还应注意,您正在使用的球形图像相当大(文件大小约为200kb),与其显示的大小相比,因此在页面加载时看到它闪烁并不奇怪。

这是球形图像的较小版本(缩小了80%并使用https://tinypng.com/进行了优化),最终大小约为42kb(您肯定可以使其更小):

enter image description here


以下是您的代码在 CodeSandbox 上的副本:

https://codesandbox.io/s/animation-test-ok1dp

这里只是输出(请在您的移动设备上的浏览器中查看):

https://ok1dp.csb.app/

这是我用 Chrome 浏览器在 iPhone 上录制的屏幕视频:

enter image description here


抱歉,我错过了你在问题末尾的“这正是我在使用移动Chrome浏览器访问我的网站时看到的完全相同的行为。” 我仍然无法在我的端上重现它,但我认为@Exodus 4D可能会给你一个有希望的线索。不过,我建议避免在移动设备上测试jsfiddles,并且强烈推荐更好的图像优化。 - Aaron Sarnat

0

我也遇到了这种情况。在其他浏览器上,它很流畅。但在Chrome移动版上,它非常卡顿。这种情况出现在我将iPhone更新到iOS 14后。


嗨,欢迎来到社区。请不要使用答案作为评论的方式(是的,我知道当你是新手时无法发表评论很令人沮丧)。 - Òscar Raya

0

我也遇到了这个问题,所以我进行了一些调查,并找到了一些有用的资源来跟踪这个错误,这确实是一个iOS Chrome的bug。 我希望这些资源可以帮助那些遇到这个问题并经过这里的人。

2018年在chromium上发起的相关主题: https://bugs.chromium.org/p/chromium/issues/detail?id=899130

一个更近期和活跃的主题:

https://bugs.chromium.org/p/chromium/issues/detail?id=1231712

最后,这篇关于 CSS 技巧的文章或许能够帮到你。

https://css-tricks.com/forums/topic/problem-with-transition-of-transform-property-in-chrome-on-ios/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接