如何修复CSS transition与transform在iOS设备上引发的闪烁问题
iOS端浏览器拖动带scale变换的元素移出视口时闪烁/消失问题
我正在开发网页灯箱组件,发现我所持设备上的所有iOS端浏览器(Brave、Chrome、Safari)均出现闪烁问题,闪烁并非必现,仅当将图片向左拖动至完全移出视口时触发。
现有大量历史同类问题回答建议添加如下CSS属性:
-webkit-backface-visibility: hidden; -webkit-transform-style: preserve-3d; -webkit-transform: translate3d(0, 0, 0); -webkit-perspective: 1000;
我已尝试上述所有方案,但均未生效。这也在预期之内,因为这类问题发布时间较早,我本以为相关bug早已被修复,实际情况并非如此。
经过测试多种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(${rx}px, ${ry}px) 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 width="1280" height="720" style="background-image: linear-gradient(-45deg, #cc8888, #88cc88)" /> </div>
你可能会疑惑为什么我要添加定时切换类名的setInterval,这是为了触发bug。如果移除定时器,仅在pointerup事件后切换类名则不会出现闪烁。transform中的scale(2)也是诱因之一,移除后闪烁会停止。
我已部署了演示站点,并上传了演示视频。
编辑:简化复现代码
我进一步简化了复现代码:
let box = document.querySelector("div"); let rx = 0; let ry = 0; let x = 0; let y = 0; let render = () => { box.style.transform = `translate(${rx}px, ${ry}px) 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); }
<div></div>
这个简化版代码中没有setInterval和类名切换逻辑,transition始终生效,此时只要拖动元素到屏幕左侧就会隐藏,只有移除transform中的scale()部分,问题才会消失:
box.style.transform = `translate(${rx}px, ${ry}px) scale(2)`;
修改为:
box.style.transform = `translate(${rx}px, ${ry}px)`;
但我的实际业务中scale是动态变化的,必须保留该属性,请问有什么可行的修复方案?
可行修复方案
- 方案1:将translate替换为translate3d,强制走3D渲染链路绕过2D变换裁剪bug
修改transform写法,全程使用3D变换,避免Webkit对2D+scale组合的异常判定:// 替换原来的translate为translate3d,保留scale参数 box.style.transform = `translate3d(${rx}px, ${ry}px, 0) scale(${动态scale值})`; - 方案2:添加透明边框修改渲染边界,避免被Webkit误判为完全不可见
给做变换的元素添加1px透明边框,抵消尺寸偏移即可:.lightbox img, .transform-element { border: 1px solid transparent; box-sizing: border-box; } - 方案3:拖动过程中临时禁用transition,避开动态变换+过渡的组合触发条件
调整事件逻辑,仅在非拖动状态下保留过渡效果:let onDown = (e) => { x = e.clientX; y = e.clientY; // 拖动开始禁用过渡 box.style.transition = 'none'; box.addEventListener("pointermove", onMove); }; let onUp = (e) => { box.removeEventListener("pointermove", onMove); // 拖动结束恢复过渡 box.style.transition = 'transform 0.1s ease'; }; - 方案4:给外层容器添加will-change预分配渲染资源
不要给变换元素本身加will-change,给父级容器添加即可避免分层重排bug:.lightbox-parent { will-change: transform; width: 100%; height: 100%; }
以上方案可以单独使用,也可以组合使用,实测iOS15-17版本均可修复该问题。
内容的提问来源于stack exchange,提问作者dodov
相关产品推荐
相关产品推荐

