如何修复 iOS 上由 CSS 过渡和变换引起的闪烁?

Posted

技术标签:

【中文标题】如何修复 iOS 上由 CSS 过渡和变换引起的闪烁?【英文标题】:How to fix flicker on iOS caused by CSS transition and transform? 【发布时间】:2022-01-10 23:08:55 【问题描述】:

我正在我的网站上开发一个灯箱,并注意到它在我拥有的所有浏览器(Brave、Chrome 和 Safari)的 ios 上都会闪烁。但是,它不会闪烁总是,只是当我将图像向左移动时,它才会离开视口。

那里有dozens of answers 建议属性,例如:

-webkit-backface-visibility: hidden;
-webkit-transform-style: preserve-3d;
-webkit-transform: translate3d(0, 0, 0);
-webkit-perspective: 1000;

我已经尝试了所有这些,但都没有奏效。这是意料之中的,因为这些问题已经很老了,我认为这些问题早已不复存在。然而,情况似乎并非如此。

在测试了 CSS 属性和 DOM 更改的各种组合之后,我将我的代码归结为一个可重现的最小示例:

let box = document.querySelector(".lightbox");
let img = box.querySelector("img");
let rx = 0;
let ry = 0;
let x = 0;
let y = 0;

let render = () => 
  img.style.transform = `translate($rxpx, $rypx) scale(2)`;
;

let onDown = (e) => 
  x = e.clientX;
  y = e.clientY;
  box.addEventListener("pointermove", onMove);
;

let onMove = (e) => 
  let lastX = x;
  let lastY = y;
  x = e.clientX;
  y = e.clientY;
  if (lastX && lastY) 
    rx += x - lastX;
    ry += y - lastY;
  
  render();
;

let onUp = (e) => 
  box.removeEventListener("pointermove", onMove);
;

box.addEventListener("pointerdown", onDown);
box.addEventListener("pointerup", onUp);
box.addEventListener("pointerleave", onUp);
box.addEventListener("pointercancel", onUp);

render();

setInterval(() => 
  box.classList.remove("test");
  setTimeout(() => 
    box.classList.add("test");
  , 100);
, 1e3);
img 
  max-width: 100%;
  height: auto;


.lightbox 
  width: 100%;
  height: 100%;
  touch-action: none;


.lightbox img 
  transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);


.lightbox.test img 
  transition: none;
<div class="lightbox">
  <img   style="background-image: linear-gradient(-45deg, #cc8888, #88cc88)" />
</div>

如果你想知道为什么我有一个 setInterval 来添加和删除一个类,那是因为它会触发错误。例如,如果没有间隔,并且类只是在 pointerup 事件之后添加/删除,则它不会闪烁。 transform 中的 scale(2) 也是问题的一部分。如果我删除它,闪烁就会停止。

我已经创建了一个demo site on Netlify 和一个demo video。

编辑:我已设法进一步简化:

let box = document.querySelector("div");
let rx = 0;
let ry = 0;
let x = 0;
let y = 0;

let render = () => 
  box.style.transform = `translate($rxpx, $rypx) scale(2)`;
;

let onDown = (e) => 
  x = e.clientX;
  y = e.clientY;
  box.addEventListener("pointermove", onMove);
;

let onMove = (e) => 
  let lastX = x;
  let lastY = y;
  x = e.clientX;
  y = e.clientY;
  if (lastX && lastY) 
    rx += x - lastX;
    ry += y - lastY;
  
  render();
;

let onUp = (e) => 
  box.removeEventListener("pointermove", onMove);
;

box.addEventListener("pointerdown", onDown);
box.addEventListener("pointerup", onUp);
box.addEventListener("pointerleave", onUp);
box.addEventListener("pointercancel", onUp);

render();
div 
  width: 200px;
  height: 200px;
  touch-action: none;
  transition: transform 0.1s ease;
  background-image: linear-gradient(-45deg, #cc8888, #88cc88);
&lt;div&gt;&lt;/div&gt;

在这个 sn-p 中,没有 setInterval 和类切换。相反,transition 始终处于活动状态。现在,只要您移动图像并且它位于屏幕的左侧,该元素就会被隐藏。看来只有当我删除转换的 scale() 部分时,问题才会消失,即从这里更改它:

box.style.transform = `translate($rxpx, $rypx) scale(2)`;

...到这个:

box.style.transform = `translate($rxpx, $rypx)`;

但是,在我的原始应用程序中,这个比例是动态变化的,所以我需要它。

【问题讨论】:

【参考方案1】:

使用transform matrix() 似乎可以解决问题。如果我切换这一行:

box.style.transform = `translate($rxpx, $rypx) scale(2)`;

用这个:

box.style.transform = `matrix(2, 0, 0, 2, $rx, $ry)`;

问题消失了。在 iOS Brave、Chrome 和 Safari 上测试。

【讨论】:

以上是关于如何修复 iOS 上由 CSS 过渡和变换引起的闪烁?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 css 变换创建循环过渡动画

这个堆栈跟踪表明了啥? iOS8 上由 Xamarin.Media.MediaPicker.TakePhotoAsync 引起的崩溃

CSS过渡不适用于变换:翻译

过渡和变换之前的 CSS 位置元素

如何修复 CSS“过渡”取消悬停元素?

CSS变化过渡与动画