Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[前端]移动端Retina视网膜屏1px解决方案(H5) #22

Open
cookiepool opened this issue Nov 15, 2019 · 0 comments
Open

[前端]移动端Retina视网膜屏1px解决方案(H5) #22

cookiepool opened this issue Nov 15, 2019 · 0 comments
Labels
前端相关 前端相关

Comments

@cookiepool
Copy link
Owner

cookiepool commented Nov 15, 2019

开始

在这里开始前先记录几个概念

  • 设备物理像素

设备本身的像素分辨率,比如我的S9的分辨率为2960*1440。

  • 设备独立像素

设备独立像素也称为密度无关像素,比如我的S9它1个设备独立像素里面要展示4个物理像素。

  • CSS像素

CSS像素是一个抽像的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。一般情况之下,CSS像素称为与设备无关的像素

  • 设备像素比

devicePixelRatio,改值等于设备物理像素除以设备独立像素,在浏览器上我们可以通过打印window.devicePixelRatio来获取该值。而在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)

1、直接写0.5px(限iOS 8及以上设备)

没错,iOS 8以上直接写0.5px可以识别,安卓设备太杂,目前我在我的S9上测试不行,还是按照1px来显示的。

2、伪类 + transform 的实现

先说方案的原理:把原先元素的 border 去掉,然后利用 ::before 或者 ::after 伪元素重做 border ,并 transform 的 scale 缩小到原来的一半,原先的元素相对定位,新做的 border 绝对定位。

这个方案是我查找的的许多方案里面比较简单的方案了,这里放在首位,而且这个方案在四条边框都存在的情况下支撑圆角,首先这儿先贴出完整的演示代码(这里我先贴只有元素底部设置边框的情况):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Event</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        html, body {
            width: 100%;
            height: 100%;
            background-color: grey;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .outter {
            width: 300px;
            height: 300px;
            background-color: white;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .inner {
            width: 150px;
            height: 150px;
            margin: 10px;
            padding: 10px;
            background-color: violet;
            position: relative;
            border: none;
        }
        /* 一条边设置边框 - 底部 */
        .inner::after { 
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            background: #000;
            width: 100%;
            height: 1px;
            transform: scaleY(0.5);
            transform-origin: 0 0;
        }
    </style>
</head>
<body>
    <div class="outter">
        <div id="inner" class="inner"></div>
    </div>

    <script>
        console.log(window.devicePixelRatio)
    </script>
</body>
</html>

这里有个额外的知识点,transform-origin,参考MDN上的资料就可以了,很好理解,它主要是来规定transform的rotate、translate、scale等这些属性应该在哪个基础点来进行变换。

上面的代码演示了元素底部设置边框,接下来我们修改代码,来分别展示上,左,右的设置方法。

  • 上方
.inner::after { 
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    background: #000;
    width: 100%;
    height: 1px;
    transform: scaleY(0.5);
    transform-origin: 0 0;
}
  • 左方
.inner::after { 
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    background: #000;
    width: 1px;
    height: 100%;
    transform: scaleX(0.5);
    transform-origin: 0 0;
}
  • 右方
.inner::after { 
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    background: #000;
    width: 1px;
    height: 100%;
    transform: scaleX(0.5);
    transform-origin: 0 0;
}
  • 四个边都需要边框的情况
.inner::after { 
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    border: 1px solid #000;
    box-sizing: border-box;
    width: 200%;
    height: 200%;
    transform: scale(0.5);
    transform-origin: 0 0;
}

当你需要设置圆角是(四个边框的情况),在 .inner.inner::after里面都跟上 border-radius即可(注意dpr为2时.inner::after里面的 border-radius 的值为 .inner 里面两倍才行,其它dpr值的情况暂未测试)。

上面演示的是devicePixelRatio为2的情况,目前包括iphone和Android的很多设备都到了3或者4,比如iPhone X的dpr为3,Galaxy S9为4,所以可以用媒体查询来解决。

2.1 使用媒体查询实现多设备适配

这里我先贴出阿里的一套方案:

/*Retian 1px border start */
.retinabt,
.retinabb,
.retinabl,
.retinabr,
.retinab {
    position: relative;
}

.retinabt:before,
.retinabb:after {
    pointer-events: none;
    position: absolute;
    content: "";
    height: 1px;
    background: rgba(32, 35, 37, .14);
    left: 0;
    right: 0;
    z-index: 26;
}

.retinabt:before {
    top: 0;
    z-index: 26;
}

.retinabb:after {
    bottom: 0;
    z-index: 26;
}

.retinabl:before,
.retinabr:after {
    pointer-events: none;
    position: absolute;
    content: "";
    width: 1px;
    background: rgba(32, 35, 37, .14);
    top: 0;
    bottom: 0
}

.retinabl:before {
    left: 0;
    z-index: 26;
}

.retinabr:after {
    right: 0;
    z-index: 26;
}

.retinab:after {
    position: absolute;
    content: "";
    top: 0;
    left: 0;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    border: 1px solid rgba(32, 35, 37, .14);
    pointer-events: none;
    z-index: 26;
}

@media (-webkit-min-device-pixel-ratio:1.5),
(min-device-pixel-ratio:1.5),
(min-resolution:144dpi),
(min-resolution:1.5dppx) {

    .retinabt:before,
    .retinabb:after {
        -webkit-transform: scaleY(.5);
        transform: scaleY(.5)
    }

    .retinabl:before,
    .retinabr:after {
        -webkit-transform: scaleX(.5);
        transform: scaleX(.5)
    }

    .retinab:after {
        width: 200%;
        height: 200%;
        -webkit-transform: scale(.5);
        transform: scale(.5)
    }

    .retinabt:before,
    .retinabl:before,
    .retinab:after {
        -webkit-transform-origin: 0 0;
        transform-origin: 0 0
    }

    .retinabb:after,
    .retinabr:after {
        -webkit-transform-origin: 100% 100%;
        transform-origin: 100% 100%
    }
}

@media (-webkit-device-pixel-ratio:1.5) {

    .retinabt:before,
    .retinabb:after {
        -webkit-transform: scaleY(.6666);
        transform: scaleY(.6666)
    }

    .retinabl:before,
    .retinabr:after {
        -webkit-transform: scaleX(.6666);
        transform: scaleX(.6666)
    }

    .retinab:after {
        width: 150%;
        height: 150%;
        -webkit-transform: scale(.6666);
        transform: scale(.6666)
    }
}

@media (-webkit-device-pixel-ratio:3) {

    .retinabt:before,
    .retinabb:after {
        -webkit-transform: scaleY(.3333);
        transform: scaleY(.3333)
    }

    .retinabl:before,
    .retinabr:after {
        -webkit-transform: scaleX(.3333);
        transform: scaleX(.3333)
    }

    .retinab:after {
        width: 300%;
        height: 300%;
        -webkit-transform: scale(.3333);
        transform: scale(.3333)
    }
}

@media (-webkit-min-device-pixel-ratio:4),
(min-device-pixel-ratio:4) {

    .retinabt:before,
    .retinabb:after {
        -webkit-transform: scaleY(.25);
        transform: scaleY(.25)
    }

    .retinabl:before,
    .retinabr:after {
        -webkit-transform: scaleX(.25);
        transform: scaleX(.25)
    }

    .retinab:after {
        width: 400%;
        height: 400%;
        -webkit-transform: scale(.25);
        transform: scale(.25)
    }
}

/*Retina 1px border end */

接下来我贴出自己写的一个,首先是媒体查询部分,这部分可以放到项目的公共部分:

.retina-border-all,
.retina-border-top,
.retina-border-right,
.retina-border-bottom,
.retina-border-left {
    position: relative;
}
.retina-border-all::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    box-sizing: border-box;
    transform-origin: 0 0;
}
.retina-border-top::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 1px;
    transform-origin: 0 0;
}
.retina-border-right::after {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    width: 1px;
    height: 100%;
    transform-origin: 0 0;
}
.retina-border-bottom::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 1px;
    transform-origin: 0 0;
}
.retina-border-left::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 1px;
    height: 100%;
    transform-origin: 0 0;
}
@media (-webkit-device-pixel-ratio: 2) {
    .retina-border-all::after {               
        width: 200%;
        height: 200%;
        transform: scale(0.5);
    }
    .retina-border-top::after {
        transform: scaleY(0.5);
    }
    .retina-border-right::after {
        transform: scaleX(0.5);
    }
    .retina-border-bottom::after {
        transform: scaleY(0.5);
    }
    .retina-border-left::after {
        transform: scaleX(0.5);
    }
}
@media (-webkit-device-pixel-ratio: 3) {
    .retina-border-all::after {
        width: 300%;
        height: 300%;
        transform: scale(0.333333);
    }
    .retina-border-top::after {
        transform: scaleY(0.333333);
    }
    .retina-border-right::after {
        transform: scaleX(0.333333);
    }
    .retina-border-bottom::after {
        transform: scaleY(0.333333);
    }
    .retina-border-left::after {
        transform: scaleX(0.333333);
    }
}
@media (-webkit-device-pixel-ratio: 4) {
    .retina-border-all::after {
        width: 400%;
        height: 400%;
        transform: scale(0.25);
    }
    .retina-border-top::after {
        transform: scaleY(0.25);
    }
    .retina-border-right::after {
        transform: scaleX(0.25);
    }
    .retina-border-bottom::after {
        transform: scaleY(0.25);
    }
    .retina-border-left::after {
        transform: scaleX(0.25);
    }
}

然后是你要设置边框的元素部分的CSS:

.inner {
    width: 150px;
    height: 150px;
    margin: 10px;
    padding: 10px;
    background-color: violet;
    /* 四个边都要边框时并且需要圆角 */
    /* border-radius: 10px; */
}
.inner::after {
    /* 四个边都要边框时 */
    border: 1px solid #000;

    /* 四个边都要边框时并且需要圆角 */
    /* border-radius: 20px; */

    /* 上、右、下、左方需要边框时 */
    /* background-color: #000; */
}

HTML部分:

<div class="outter">
    <div id="inner" class="inner retina-border-all"></div>
</div>

3、手淘的方案(这儿直接给链接,这个方案是五年前的了)

使用Flexible实现手淘H5页面的终端适配

4、另一种rem + viewport的方案(等比例放大)

先放代码:

(function(win,doc) {//根据屏幕大小及dpi调整缩放和大小
    var scale = 1.0, ratio = 1, dc=doc, viewporttexts='';
    if (win.devicePixelRatio && devicePixelRatio >= 1.5) {
        ratio = devicePixelRatio;
        scale = scale/(devicePixelRatio);  
    }
    viewporttexts = ' width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale +', minimum-scale=' + scale + ',user-scalable=no';
    doc.querySelector('meta[name="viewport"]').setAttribute("content",viewporttexts);
    
    dc.documentElement.style.fontSize =doc.getElementsByTagName("html")[0].style.fontSize=Math.ceil(50*ratio) + "px";
})(window,document);

这个方案写1px的时候不转换rem,然后其他都要转换rem(字体也要写rem单位),上面的基础大小为50px,也就是说,ratio为1时,你的1rem相当于50px,这个在你的转换插件里面把倍率设置为50即可

然后我说一下我测试的结果,目前手头只有S9和iPhone 7(系统为iOS 11),S9的浏览器用的UC,测试我发现,上面这个方案在S9上很完美,看不出异常,然后在IP7上测试四边都设置边框的情况下,左右边框没起到作用,它是按照2px显示的,上下边框没问题,是按1px显示,当我单独设置一边的边框时,显示没有问题(补充:我发现当我左边的不设置边框或者不设置左右边框时,右边的四个边框都显示正常,无解-_-)。下面我贴几张图:

  • S9
    S9

  • IP7
    ip7

-IP7-左边不设置边框
ip7-noleft

参考资料

7种方法解决移动端Retina屏幕1px边框问题

移动端 Retina屏 各大主流网站1px的解决方案

css中引入svg来兼容部分安卓机显示0.5px边框的示例

@cookiepool cookiepool changed the title [前端]移动端Retina视网膜屏1px解决方案(H5端) [前端]移动端Retina视网膜屏1px解决方案(H5) Nov 15, 2019
@cookiepool cookiepool added the 前端相关 前端相关 label Nov 17, 2019
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
前端相关 前端相关
Projects
None yet
Development

No branches or pull requests

1 participant