Skip to content
New issue

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

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

Already on GitHub? Sign in 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
前端相关 前端相关
Projects
None yet
Development

No branches or pull requests

1 participant