o2team / H5Skills

移动端开发技巧集合
831 stars 80 forks source link

如何抗rotate剧齿 #53

Open leeenx opened 7 years ago

leeenx commented 7 years ago

有时候,我们需要transform的rotate方法来使元素倾斜,但是角度倾斜会给元素带来意料之外的效果--锯齿。

如何在IOS下对抗rotate造成的锯齿

在IOS中,搞锯齿的方法很简单,只要开启元素的3d属性既开启了GPU加速后,能有效的实现抗锯齿。如果在开启了GPU后,ios页面仍出现锯齿现象,使用安卓的最终解决方案可以起最后的搞锯齿作用

而安卓就比较复杂了,下面就如何在安卓下有效抗做一次梳理。

以下都是针对简单的方块模块(即一块div)进行研究

这里研究的安卓系统都是4.0以上的,2.3不做研究(因为份额几乎快没了)

如何在安卓下对抗rotate造成的锯齿

为了方便描述,把被rotate的元素简单地划分成两类:

  1. 图片(即标签的元素)
  2. 带背景的div

本人已经通过验证了,以纯色作背景的div和以图片作背影的div在锯齿的表现上是一样的,所以把二者统一成一类。

图片rotate后抗锯齿方法

保证img在一个容器(如div)内,并使rotate作用于容器上,此时只要开启容器的GPU,和为容器添加一个透明border即可

如下: rotate-blur2.html:

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="black" name="apple-mobile-web-app-status-bar-style">
    <meta content="telephone=no" name="format-detection">
    <meta content="email=no" name="format-detection">
    <title>rotate锯齿 - 图片</title>
    <style type="text/css">
        html,body{width: 100%; height: 100%; margin: 0; padding: 30px 0 0 0; background-color: #000;}
        .box{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: -80px 0 0 -120px; color: #666; border:2px solid rgba(255,255,255,0);}
        .box2{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: -80px 0 0 20px; color: #666;}
        .box3{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: 50px 0 0 -120px; color: #666; border:2px solid rgba(255,255,255,0);}
        .box4{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: 50px 0 0 20px; color: #666;}
    </style>
</head>
<body ontouchstart>
    <input id="deg" value="0"/>
    <div class="box" id="box">
        <img src="http://smartpro.sinaapp.com//test/white.jpg">
    </div>
    <div class="box2" id="box2">
        <img src="http://smartpro.sinaapp.com//test/white.jpg">
    </div>
    <div class="box3" id="box3">
        <img src="http://smartpro.sinaapp.com//test/white.jpg">
    </div>
    <div class="box4" id="box4">
        <img src="http://smartpro.sinaapp.com//test/white.jpg">
    </div>
</body>
<script type="text/javascript">
    deg.addEventListener("change",function(){
        box.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg) translateZ(0)";
        box2.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg) translateZ(0)";
        box3.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg)";
        box4.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg)";
    });
    //安卓结论抗锯齿的关键,加上3D属性translateZ(0)开启gpu,但是此时仍然会有锯齿问题,需要添加非0的border且颜色为rgba(255,255,255,0)
</script>
</html>

扫一扫:

图片rotate体验地址

以下是截图:

小米2
小米2

小米3
小米3

红米2
小米2

华为3c 华为3c

魅蓝note 华为3c

通过五部手机可以清楚地知道,华为3c对 rotate 锯齿的处理结果就很理想,加不加GPU和border值都没关系。而对于小米系列手机和魅蓝来说,只有加了border:1px solid rgba(255,255,255,0)和translateZ(0) 搞锯齿才起作用。

小结: 解决安卓下rotate图片锯齿的解决方法是为其容器添加"translateZ(0)"和"border:1px solid rgba(255,255,255,0)"两个属性

非图片元素rotate后搞锯齿的方法

对于纯颜色DIV来说,上述的方法是无法解决锯齿问题的。至于原因,这里涉及到border与容器的background之前的关系。

简略的分析如下: 容器可以细分为:外边距,边框,内边距和内容区。在内外边距都为0边框不为0的情况下,可以认为容器就只有边框和内容区,此时边框和内容区是紧挨在一起的,容器的尺寸等边框尺寸,内容区的尺寸略小于容器的尺寸。如下:
容器的边框与内容区示意图

如果容器有填充(即有背景)的元素,那么它是怎么渲染过程如下:
容器的边框与内容区示意图

也就是说此时的边框和容器的填充是有重叠的。 在图片rotate的例子中,图片和外部容器的border它却是不重叠的。

透明border去锯齿的原理:透明边框会在边缘处产生羽化的效果。

有扣图经验的小伙伴都知道羽化是去锯齿的利器。由于背景填充与边框重叠,羽化的区域被困死在重叠区中,起不了抗锯齿的作用。不过知道,搞锯齿的原理,就有办法解决锯齿问题。只要我不把羽化区困死,锯齿就不会有了。css3有一个叫background-clip的属性,用这个属性可以强制容器的填充是填充整个容器还是只填充内容区。现在我只需要填充内容区,于是用background-clip:content-box;那么容器的渲染就会变成:

容器的边框与内容区示意图

根据上述原理,写了另一个demo

rotate-blur.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="black" name="apple-mobile-web-app-status-bar-style">
    <meta content="telephone=no" name="format-detection">
    <meta content="email=no" name="format-detection">
    <title>rotate锯齿</title>
    <style type="text/css">
        html,body{width: 100%; height: 100%; margin: 0; padding: 30px 0 0 0; background-color: #000;}
        .box{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: -160px 0 0 -120px; background:url(http://smartpro.sinaapp.com//test/white.jpg); color: #666; border:1px solid rgba(255,255,255,0);}
        .box2{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: -160px 0 0 20px; background:url(http://smartpro.sinaapp.com//test/white.jpg); color: #666;}
        .box3{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: -20px 0 0 -120px; background:url(http://smartpro.sinaapp.com//test/white.jpg); color: #666; border:1px solid rgba(255,255,255,0);}
        .box4{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: -20px 0 0 20px; background:url(http://smartpro.sinaapp.com//test/white.jpg); color: #666;}
        .box5{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: 120px 0 0 -120px; background:url(http://smartpro.sinaapp.com//test/white.jpg); color: #666; border:1px solid rgba(255,255,255,0); background-clip: content-box;}
        .box6{position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin: 120px 0 0 20px; background:url(http://smartpro.sinaapp.com//test/white.jpg); color: #666; border:1px solid rgba(255,255,255,0); background-clip: content-box;}
    </style>
</head>
<body ontouchstart>
    <input id="deg" value="0"/>
    <div class="box" id="box">
        有border有translateZ
    </div>
    <div class="box2" id="box2">
        无border有translateZ
    </div>
    <div class="box3" id="box3">
        有border无translateZ
    </div>
    <div class="box4" id="box4">
        无border无translateZ
    </div>
    <div class="box5" id="box5">
        有border有translateZ加background-clip:content-box;
    </div>
    <div class="box6" id="box6">
        有border无translateZ加background-clip:content-box;
    </div>
</body>
<script type="text/javascript">
    deg.addEventListener("change",function(){
        box.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg) translateZ(0)";
        box2.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg) translateZ(0)";
        box3.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg)";
        box4.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg)";
        box5.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg) translateZ(0)";
        box6.style["-webkit-transform"]="rotate("+(parseFloat(this.value)||0)+"deg) translateZ(0)";
    });
    //安卓结论抗锯齿的关键,加上3D属性translateZ(0)开启gpu,但是此时仍然会有锯齿问题,需要添加非0的border且颜色为rgba(255,255,255,0)
</script>
</html>

扫一扫体验:

图片rotate体验地址

以下是实测截图:

小米2
小米2

小米3
小米3

红米2
小米2

华为3c 华为3c

魅蓝note 华为3c

通过上面的测试,可以发现原来处理锯齿很友好的华为3c也出现锯齿了,不过,使用了我们的抗锯样式后明显好多了。其它的四部手机也是。不过总的说来,抗锯的效果相对于图片来说差了一点。

安卓抗锯总结

综合上述两个解决方案,可以统一得出一个抗锯方案:

.border-blur{-webkit-transform:translateZ(0); border:1px solid rgba(255,255,255,0); background-clip:content-box;}
h5m1007 commented 6 years ago

锯齿写错了 是锯齿

leeenx commented 6 years ago

锯齿写错了 是锯齿

@h5m1007 谢谢指正