Chrome opacity非1时border-radius圆角边框剪裁问题

一、圆角边框显示不全问题出现条件

应该都知道,border-radius:50%可以让元素正方形元素表现为正圆,深入理解其表现机制可参见我之前文章:“CSS3 border-radius知多少?”。

如果元素设置了border边框,则会表现为一个正圆圈圈,类似这样:

正圆圈圈截图示意

但有时候,border边框的这个圈圈会在边缘处发生剪裁,不是一个完美的圈圈了,类似下面这样:

圈圈边缘剪裁

圈圈的左侧不是圆的了,而是像被什么东西一刀切下去,成了直直的了。

如果是在retina屏幕下,则“一刀切”的效果可能还要更加明显,上下左右都有可能发生剪裁。

出现这种渲染问题,需要满足下面两个条件:

  1. 元素的透明度opacity不是1;
  2. 元素的位置并不是完美起止于屏幕的像素点上;

第一个条件很好理解;那第二个条件是什么意思呢?

对于普通的显示器,设备像素比devicePixelRatio1,也就是最小显示单位(渲染单位)是1像素。如果某一个元素的起点是从0.5像素开始的,那这个元素的开始位置就不是正好在屏幕的像素点上,而是中间。

这其实是个悖论,既然最小渲染单位是1像素,何来从0.5像素开始的定位呢?确实,在过去,几乎所有CSS属性设置的小数值的最终表现都是以整数像素体现(如margin: .5px = margin: 1px),在老IE时代更堪称死板(425%宽度浮动元素可能最后一个会掉下来)。但是进入CSS3时代后,情况就开始发生变化,着重要点名的就是transform变换。由于transform变换基于矩阵计算,无论是旋转还是拉伸,其点坐标十有八九一定是N位数的小数。如果此时我们的浏览器按照最小的1像素开始渲染,那图形的边缘那就是满满的锯齿,渲染效果会非常糟糕(一开始的transform变换效果真就这么糟糕)。浏览器厂商一看,我去,这效果挫到我自己都看不下去,于是下功夫开始提升渲染体验。弄了一套算法,对非整数像素点边缘进行柔化,也就是虽然还是占据的1像素的格子,但是边缘像素点通过半透明等特殊处理,我们视觉上看就好像元素开始于0.5像素的位置。

也正是由于这个原因,当我们使用对元素进行非整数位置的translate位移的时候,元素的边缘会有一点模糊的感觉。

对于iMac这种5K显示屏幕,最小像素点实际上可以小到0.2像素,也就是就是translate移动0.2像素,元素边缘也不会柔化模糊,但是,移动0.5像素就不一定了。

因此,如下的HTML和CSS代码就可以让圆角边框不规整:

<span class="ele"></span>
.ele {
    display: inline-block;
    width: 40px; height: 40px;
    border: 1px solid #fff;
    border-radius: 50%;
    transform: translate(.5px, 0);
    opacity: 0.6;
}

根据我的测试,除了Chrome浏览器有圆角边框不规整的问题外,IE浏览器的渲染也同样有问题,表现为右侧边缘明显衰弱:

边缘衰弱

但是,如果opacity设置为1,则无论IE浏览器还是Chrome浏览器,又变成完美的圆圈圈了,如下(截自IE):

IE下截图

Firefox则表现符合预期。

二、如果解决border-radius border部分显示问题?

知道问题出现触发的条件,那问题解决其实已经解决了一半了。

2种方法:

1. 元素尺寸和位置都在都是整数像素

然而很多时候这种方法并不切实际,尤其对于复杂布局,例如我们使用calc计算对元素宽度定位时候,或者line-height使用不容易计算的小数时候,或者基于rem的布局等等,想要强制元素尺寸和位置都是整数并不太容易。

2. 不使用opacity实现半透明

既然我们的问题出现与opacity不是1有关,那我们就不使用opacity实现半透明,采用其他的方法代替。

如果我们想使一个边框变成半透明,除了opacity外,我们还可以使用RGBA或者HSLA颜色。

例如,上面是一个例子的CSS代码换成:

.ele {
    display: inline-block;
    width: 40px; height: 40px;
    border: 1px solid hsla(0,0%,100%,.6);
    border-radius: 50%;
    transform: translate(.5px, 0);
}

则边框不圆润的问题就消失了,有专门的演示页面,您可以狠狠地点击这里:半透明圆角边框剪裁问题示意demo

结果对比效果如下图:

使用HSLA颜色代替opacity边框

Nice!

三、结束语

上面的60%透明度白色边框也可以使用RGBA颜色表示:rgba(255,255,255,.6)。效果是一模一样的,也能让边框完美显示。

那为什么上面我用的是HSLA颜色表示而不是RGBA颜色表示呢?

因为使用HSLA表示白色要比RGBA表示白色可以少2个字母。

这就是我的颜色表示的一个小秘密:白色半透明使用HSLA颜色表示,黑色半透明使用RGBA颜色表示。

(本篇完)

分享到:

标签: , , , , ,

赞助商推荐(我也要赞助)

想学到点真东西? ×
如果你有1~3年前端开发经验,不妨 ×


发表评论(目前17 条评论)

  1. ziven27说道:

    张老师这个问题,尝试之后用transform:translateZ(0); 能够解决chrome的问题,IE没有试过。
    你demo里面的例子用transfrom:translate3d(.5px, 0, 0); 可以解决

  2. dreamerjdw说道:

    刚遇到一个与border-radius有关的坑:包裹iframe的外层元素设置了border-radius值后,iframe中的字会变模糊,值越大越模糊。

    • dreamerjdw说道:

      不断试验,上面的描述有误。

      更准确的描述:
      1. 当为包裹iframe的外层元素设置了border-radius值后,iframe的content与外层元素的外边框相距小于border-radius/2时,iframe内的字会变得模糊。
      2. 关于模糊度,上述描述有误。模糊度一直是固定的。
      3. 当有多个外层元素包裹时,计算最大的border-radius值。

      疑问:
      一直认为父窗口和iframe属于不同document,样式不会相互影响。

  3. 海伦克里克说道:

    请问微码里面《按钮加一个类名自动变菊花loading状态无图片版》中的style样式:’a[class*=-btn].loading, label[class*=-btn].loading { position: relative;}’,这个a[class*=-btn]标签是什么样的,我没见过这种,请大神指教,谢谢。

    • 海伦克里克说道:

      我知道了,原来class*是class中带后面值的标签,但是至于-btn就真的是类名带“-btn”。

  4. joier说道:

    一个网页,body标签上加了transform:scale(0.84256987),页面上有一个百度地图,分块加截的地图之间也会出现间隙,大概是同样的原因吧,搞了一天了还不知道怎么解决

  5. 阿熊说道:

    楼主,我一直用inoreader这类在线rss阅读器订阅您的文章,防盗链一直让我天天看见楼主的文字和楼主的照片,情难自已,恨不得撸一发,为了戒撸,能帮忙把对inoreader.com的防盗链去了么?

  6. Zz说道:

    那个“不使用opacity实现半透明”的例子里面,我使用retina屏幕和非retina屏幕测试,都无法显示不规则的圆形。是不是你的这个例子里面没有“0.5像素起止点”这个先决条件呢?

    • Zz说道:

      我错了,没仔细看例子,里面已经有了“transform: translate(.5px, 0);”。但是我的MBP和MBA,都无法有“不规则圆形”的情况出现。浏览器chrome(61.0.3163.100(正式版本) (64 位))。

  7. 上官说道:

    学习了

  8. 老歌说道:

    看到最后一句我笑了 我也有这种强迫症

  9. GenialX说道:

    学习了,博主的文章会专注一个点,进而层层解析,很有收获

  10. luoky说道:

    又get到新技能了~~

  11. 幻白说道:

    前排占座哈哈

  12. meepo说道:

    最近高产似母猪啊