• 欢迎访问web前端中文站,JavaScript,CSS3,HTML5,web前端demo
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏web前端中文站吧

再谈Retina下1px的解决方案

CSS3 web前端中文站 2年前 (2017-07-25) 923次浏览 已收录 0个评论

CSS

在互联网上有关于1px边框的解决方案已经有很多种了,自从使用 Flexible 库之后,再也没有纠结有关于1px相关的问题。由于最近在考虑新的移动端适配方案,也就是放弃 Flexible 库,我不得不考虑重新处理1px的方案。为此为我自己也重撸了一些1px的解决方案,整理出来,希望对有需要的同学有帮助。

更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏

Flexible 方案

Flexible 方案已不是什么神秘的方案了,借助 JavaScript 来动态修改meta标签中viewport中的initial-scale的值,然后根据dpr修改html中的font-size值,再使用rem来处理。有关于这方面的详细使用可以阅读早期整理的文章《使用 Flexible 实现手淘 H5 页面的终端适配》。

但是话说回来,这个方案目前只处理了 iOS 的dpr2的情况,其他的都没有处理,也就是说不支持 Android 和drp=3的情况。对于追求完美的同学来说,这是无法接受的。

有问题,总是有解决方案的,有同学做过方面的详细探索。那么跟着其思路也重新撸了一回。先回到 Fleible 中,其实现原理,大家都知道的。让viewport放大为device-widthdpr倍数,然后缩小1/dpr倍显示。

对于viewport的计算理论上是这样的:

viewportwidth没设置的话,默认是980px,这方面的详细介绍可以阅读《Configuring the Viewport》一文;但如果设置了initial-scaleviewport=device-width/scale;同时还设置了widthinitial-scale,则会取min-width,即应用这两个较小的值。详细的介绍可以阅读《Preliminary meta viewport research》一文。

接下来看看各种设备下的场景。首先使用 JavaScript 计算出scale的值:

var scale = 1 / window.devicePixelRation; 

head中的meta标签设备:

<meta name="viewport" content="initial-scale=scale,maximum-scale=scale,minimum-scale=scale,user-scalable=no"> 

iPhone5viewportwidth=640px,得到的meta值:

<meta name="viewport" content="initial-scale=scale,maximum-scale=scale,minimum-scale=scale,user-scalable=no"> 

符合我们预期所需的结果。

iPhone6 Plus也是完美的:

<meta name="viewport" content="initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333,user-scalable=no"> 

再来看几个 Android 的设备。比如米 3,它的dpr=3viewportwidth=1080,得到的值也是我们期待的:

<meta name="viewport" content="initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333,user-scalable=no"> 

在米 2 中,它的dpr=2viewportwidth=720,效果也是 OK 的。

<meta name="viewport" content="initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no"> 

看到这里时,大家可能都会觉得完美,无需纠结啥,事实上在米 2 和米 3 中,看到的都是设置默认的浏览器、UC 和 Chrome 浏览器的结果。回过头来再看 WebView,那就出问题了。当 Webview 为360时,线依然也是粗的,这里测试,发现user-scalable=no会使viewport的值等于device-width。那么我们进一步去掉user-scalable=no或者设置user-scalable=yes

<meta name="viewport" content="initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5"> <meta name="viewport" content="initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333"> 

这样设置,在 iOS、米 3 的 Webview 下都能得到预期效果,但是在米 2 中的 Webview 还是有问题,页面会被放大。问题是出在于米 2 的 Webview 的viewportwidth=490px,是由默认的980px缩放0.5后的值。而米 2 的device-width=360,所以就会出现撑开放不下的现象。

米 2 的 Webview 怎么办? 想起还有个被 webkit 在 2013 年 3 月抛弃的属性target-densitydpi=device-dpi,此属性是之前 Android 对viewport标签的扩展,arget-densitydpi的值有: device-dpi, high-dpi, medium-dpi, low-dpi四个。对于小米 2 的 Webview 才出现的问题估计只能非标准的属性来 hack 试试,densitydpi=device-dpi会让页面按照设备本身的dpi来渲染。

<meta name="viewport" content="densitydpi=device-dpi,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5"> 

测试其他都正常,就小米 2 的 Webview 会出现有些边框偶尔出现若隐若现,原来是此时页面的viewport=980,densitydpi=device-dpi以设备真实的dpi显示后,scale的倍数变为360/980,这种情况压缩下去也许就这么残了~~

想办法让小米 2 的缩放比为小米的dprviewport如何能变为2*360=720呢,试试user-scalable=no重新加回去试试,终于,小米 2 的 Webview 下出现了纤细的线条。

<meta name="viewport" content="densitydpi=device-dpi,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no"> 

测试了下对 iPhone 系列、三星系列、华为等主流机型的影响,正常!

别高兴的太早,在大天朝下,不仅仅有这些设备。还有 VIVO 之类的手机,他们的dpr=3,他们的viewport=980px,缩小为原来的1/3后,效果就不是我们所要的了。除此之外,还有一些设备,它的dpr很变态,比如 VIVO 的 Android4.1.2,它的dpr=1.5,而其viewport也等于980,缩小为原来的1/1.5 = 2 / 3,宽度就变成了980 * 2 / 3 = 653.333,得到的效果也是无法直视的。当然还有一些我们所不知道的设备呢?这些可以通过 Device Metrics 网站来查阅出设备相关的参数:

再谈 Retina 下 1px 的解决方案

这也是当初 Fleible 放弃治疗 Android 的原因。

但总的而言,其根本原因是一样的,viewport的默认宽度依然是980initial-scale等的设置无法改变viewport的基准计算。看来这些非主流机型上只能通过width来改变了。不出所料,设置如下即可

<meta name="viewport" content="target-densitydpi=device-dpi,width=device-width,user-scalable=no"/> 

进一步测试发现绝大部分 Android 机器用下面的vieport设置也完全可以实现1px的真实效果。但是新 webkit 下已经移除了对target-densitydpi=device-dpi的支持。所以主流 Android 还是用标准的设置上述initscale=scale,因此最后的方案是主流的设备设置viewport

<meta name="viewport" content="densitydpi=device-dpi,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5"> 

设置以上viewport还是无法改变默认980为宽度的viewport的非主流设备(如 vivo,云 os 等),设置如下:

<meta name="viewport" content="target-densitydpi=device-dpi,width=device-width,user-scalable=no"/> 

因此,最后的实现代码如下:

metaEl.setAttribute('content', 'target-densitydpi=device-dpi,user-scalable=no,initial-scale=' + scale + ',maximum-scale=' + scale + ', minimum-scale=' + scale); //不通过加入具体设备的白名单,通过此特征检测 docEl.clientWidth == 980 //initial-scale=1 不能省,因为上面设置为其他的 scale 了,需要重置回来 if(docEl.clientWidth == 980) {     metaEl.setAttribute('content', 'target-densitydpi=device-dpi,width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1'); } 

压缩的代码可以点击这里下载。这个也可以说是 Flexible 的升级版本吧(另外感兴趣的可以看看 npm 上的 postcss-adaptive)。但也不是我所需要的方案,我的最终方案是放弃 Flexible。

如果你对上面的方案不是很满意,你可以根据这篇文章《Mobile Web: Logical Pixel vs Physical Pixel》提供的解决方案,整理出适合自己的方案。原理和前面介绍的一样。

iPhone系列的viewport

<meta name="viewport" content="width=device-width initial-scale=0.5 maximum-scale=0.5 user-scalable=no"> 

Android系列的viewport:

<meta name="viewport" content="width=device-width target-densityDpi=device-dpi initial-scale=0.5 maximum-scale=0.5 user-scalable=no"> 

同样为了达到上述的需求,通过 JavaScript 来处理:

if (window.devicePixelRatio === 1) {     if (window.innerWidth === 2 * screen.width ||         window.innerWidth === 2 * screen.height) {         el = document.getElementById('viewport');         el.setAttribute('content', 'width=device-width target-densityDpi=device-dpi ' +             'initial-scale=1 maximum-scale=1 user-scalable=no');         document.head.appendChild(el);         width = window.innerWidth;         height = window.innerHeight;         if (width === 2 * screen.width) {             width /= 2;             height /= 2;         }     } } 

是不是感觉他们非常类似。感兴趣不仿试试。

.5px 方案

2014 年的 WWDC 大会中,Ted O’Conor 在分享“设计响应的 Web 体验” 主题时提到关于Retina Hairlines一词,也就是Retina极细的线:

Retina屏上仅仅显示1物理像素的边框,开发者应该如何处理呢?

实际上其想表达的是 iOS8 下1px边框的解决方案。1px的边框在devicePixelRatio = 2Retina屏下会显示成2px,在 iPhone6 Plus 下甚至会显示成3px

还好,时代总是进步的,在 iOS8 下,苹果系列都已经支持0.5px了,那么意味着在devicePixelRatio = 2时,我们可以借助媒体查询来处理:

.border {     border: 1px solid black; }  @media (-webkit-min-device-pixel-ratio: 2) {     .border {         border-width: 0.5px     } } 

但在 iOS7 以下和 Android 等其他系统里,0.5px将会被显示为0px,那么我们就需要想出办法解决,说实在一点就是找到 Hack。

首先我们可以通过 JavaScript 来判断 UA,如果是 iOS8+,则输出类名hairlines,为了防止重绘,把这段代码添加在</head>之前:

if (/iP(hone|od|ad)/.test(navigator.userAgent)) {     var v = (navigator.appVersion).match(/OS (/d+)_(/d+)_?(/d+)?/),         version = parseInt(v[1], 10);     if(version >= 8){         document.documentElement.classList.add('hairlines')     } } 

除了判读 UA 之外,还可以通过 JavaScript 来判断是否支持0.5px边框,如果支持的话,同样输出类名hairlines

if (window.devicePixelRatio && devicePixelRatio >= 2) {     var testElem = document.createElement('div');     testElem.style.border = '.5px solid transparent';     document.body.appendChild(testElem);     if (testElem.offsetHeight == 1){         document.querySelector('html').classList.add('hairlines');     }     document.body.removeChild(testElem); } 

相比于第一种方法,这种方法的可靠性更高一些,但是需要把 JavaScript 放在body标签内,相对来说会有一些重绘,个人建议是用第一种方法。

这个方案无法兼容 iOS8 以下和 Android 的设备。如果需要完美的兼容,可以考虑和方案一结合在一起处理。只是比较蛋疼。当然除了和 Flexible 方案结合在一起之外,还可以考虑和下面的方案结合在一起使用。

border-image

border-image是一个很神奇的属性,Web 开发人员借助border-image的九宫格特性,可以很好的运用到解决1px边框中。使用border-image解决1px咱们需要一个特定的图片,这张图片要符合你的要求,不过它长得像下图:

再谈 Retina 下 1px 的解决方案

实际使用的时候:

border-width: 0 0 1px 0; border-image: url(linenew.png) 0 0 2 0 stretch; 

上面的效果也仅实现了底部边框border-bottom1px的效果。之所以使用的图片是2px的高,上部分的1px颜色为透明,下部分的1px使用的视觉规定的border颜色。但如果我们边框底部和顶部都需要border时,需要做一下图片的调整:

再谈 Retina 下 1px 的解决方案

border-width: 1px 0; border-image: url(linenew.png) 2 0 stretch; 

到目前为止,我们已经能在 iPhone 上展现1px边框的效果。但是我们也发现这样的方法在非视网膜屏幕上会出现border不显示的现象。为了解决这个问题,可以借助媒体查询来处理:

.border-image-1px {     border-bottom: 1px solid #666; } @media only screen and (-webkit-min-device-pixel-ratio: 2) {     .border-image-1px {         border-bottom: none;         border-width: 0 0 1px 0;         border-image: url(../img/linenew.png) 0 0 2 0 stretch;     } } 

不管是只有一边的边框(比如示例中的底部边框),还是上下都有边框,我们都需要对图片做相应的处理,除些之外,如果边框的颜色做了变化,那么也需要对图片做处理。这样也不是一个很好的解决方案。

PostCSS Write SVG

使用border-image每次都要去调整图片,总是需要成本的。基于上述的原因,我们可以借助于 PostCSS 的插件 postcss-write-svg 来帮助我们。如果你的项目中已经有使用 PostCSS,那么只需要在项目中安装这个插件。然后在你的代码中使用:

@svg 1px-border {     height: 2px;     @rect {         fill: var(--color, black);         width: 100%;         height: 50%;     } }  .example {     border: 1px solid transparent;     border-image: svg(1px-border param(--color #00b1ff)) 2 2 stretch; } 

这样 PostCSS 会自动帮你把 CSS 编译出来:

.example {     border: 1px solid transparent;     border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch; } 

使用 PostCSS 的插件是不是比我们修改图片要来得简单与方便。

使用 PostCSS 的postcss-write-svg插件,除了可以使用border-image来实现1px的边框效果之外,还可以使用background-image来实现。比如:

@svg square {     @rect {         fill: var(--color, black);         width: 100%;         height: 100%;     } }  #example {     background: white svg(square param(--color #00b1ff)); } 

编译出来就是:

#example {     background: white url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%2300b1ff' width='100%25' height='100%25'/%3E%3C/svg%3E"); } 

这个方案简单易用,是我所需要的。目前测试下来,基本能达到我所需要的需求,在最新的适配方案中,我也采用了这个插件来处理1px边框的问题。

除此之外网友还整理了一些其他的方案,比如说:background-imagebox-shadowtransform之类的。

其中box-shadow不推荐使用,而background-image和上面的 PostCSS 方案有点类似,只不过 PostCSS 更为方便,实在无耐之下,transform和伪元素或者伪类的配合还是可以值得一用的。比如:

.hairlines li{     position: relative;     border:none; } .hairlines li:after{     content: '';     position: absolute;     left: 0;     background: #000;     width: 100%;     height: 1px;     transform: scaleY(0.5);     transform-origin: 0 0; } 

使用的时候,也需要结合 JavaScript 代码,用来判断是否是Retina屏。当然除了 JavaScript 来判断之外,你还可以借助于媒体查询来处理。

总结

不管是哪种方案,对于解决同样的问题,只要是能解决都是好方案。俗话说:“不管是白猫还是黑猫,能捉到老鼠都是好猫”。上面罗列了众多1px边框的解决方案,可以说没有最好的,只有最适合的。大家可以根据自己的需求来处理,个人更建议大家使用 PostCSS 的插件。能让你省不少的事情。

【注:本文源自网络文章资源,由站长整理发布】


web 前端中文站 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:再谈 Retina 下 1px 的解决方案
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址