移动端1px细线解决方案总结

现在的 PM 和 UI 总以看 app 的眼光看 html5,html 页面要做的专业美观,而且必须很精细。

去年的时候 UI 就告诉我 h5 上的边框线太粗,把整站都给拉 low 了。当时工期紧就没太在意 1px 粗细,好在那个版本没上线就迭代掉了,后面的版本针对这个问题做了些尝试,这里总结下1px细线的处理方法。

移动端 1px 变粗的原因

为什么移动端 css 里面写了 1px,实际看起来比 1px 粗,其实原因很好理解:这2个 ’px’ 的含义是不一样的,移动端 html 的 header 总会有一句:

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

这句话定义了本页面的 viewport 的宽度为设备宽度,初始缩放值和最大缩放值都为1,并禁止了用户缩放.,viewport 通俗的讲是浏览器上可用来显示页面的区域,这个区域是可能比屏幕大的。

根据这篇文章http://www.cnblogs.com/2050/p/3877280.html的分析, 手机存在一个能完美适配的理想 viewport,分辨率相差很大的手机的理想 viewport 的宽度可能是一样的,这样做的目的是为了保证同样的 css 在不同屏幕下显示效果是一致的, 上面的 meta 实际上是设置了 ideal viewport 的宽度.

以实际举例:iphone3 和 iphone4 的屏幕宽度分别是 320px、640px,但是它们的 ideal viewport 的宽度都是 320px,设置了设备宽度后,320px 宽的元素都能 100% 的填充满屏幕宽。不同手机的 ideal viewport 宽度是不一样的,常见的有 320px, 360px, 384px。

iphone系列的这个值在6之前都是 320px,控制 viewport 的好处就在于一套 css 可以适配多个机型。

看懂的人应该已经明白 1px 变粗的原因了, viewport 的设置和屏幕物理分辨率是按比例而不是相同的。移动端 window 对象有个 devicePixelRatio 属性,它表示设备物理像素和 css 像素的比例,在 retina 屏的 iphone 手机上,这个值为2或3,css 里写的 1px 长度映射到物理像素上就有 2px 或 3px 那么长。

1px 解决方案

1、用小数来写 px 值

IOS8下已经支持带小数的 px 值, media query 对应 devicePixelRatio 有个查询值 -webkit-min-device-pixel-ratio,那 css 可以写成这样

.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
    .border { border: 0.333333px solid #999 }
}

如果使用 less/sass 的话只是加了1句 mixin;

缺点:安卓与低版本IOS不适用,这个或许是未来的标准写法,现在不做指望了。

2、border-image

866189-20160317143339131-2118326634.png

这样的1张6X6的图片, 9宫格等分填充 border-image,这样元素的4个边框宽度都只有1px

@media screen and (-webkit-min-device-pixel-ratio: 2){ 
    .border{ 
        border: 1px solid transparent;
        border-image: url(border.gif) 2 repeat;
    }
}

图片可以用 gif, png, base64 多种格式,以上是上下左右四条边框的写法,需要单一边框只要定义单一边框的 border,代码比较直观。

border-image 兼容性:

866189-20160317143339959-1764497027.png

缺点:对于圆角样式,将图片放大修改成圆角也能满足需求,但这样无形中增加了 border 的宽度,存在多种边框颜色或者更改时候的麻烦。

3、background渐变

背景渐变,渐变在透明色和边框色中间分割,frozenUI 用的就是这种方法,借用它的上边框写法:

@media screen and (-webkit-min-device-pixel-ratio: 2){
    .ui-border-t {
        background-position: left top;
        background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0.5,transparent),color-stop(0.5,#e0e0e0),to(#e0e0e0));
    }
}

这样更改颜色比 border-image 方便,兼容性如下:

866189-20160317143340818-770376396.png

缺点:

代码量大,而且需要针对不同边框结构,frozenUI 就定义9种基本样式;

而且这只是背景,这样做出来的边框实际是在原本的 border 空间内部的,如果元素背景色有变化的样式,边框线也会消失;

最后不能适应圆角样式。

4、:before, :after 与 transform

之前说的 frozenUI 的圆角边框就是采用这种方式,构建1个伪元素,将它的长宽放大到2倍,边框宽度设置为1px,再以 transform 缩放到50%。

.radius-border{
    position: relative;
}
@media screen and (-webkit-min-device-pixel-ratio: 2){
    .radius-border:before{
        content: "";
        pointer-events: none; /* 防止点击触发 */
        box-sizing: border-box;
        position: absolute;
        width: 200%;
        height: 200%;
        left: 0;
        top: 0;
        border-radius: 8px;
        border:1px solid #999;
        -webkit-transform(scale(0.5));
        -webkit-transform-origin: 0 0;
        transform(scale(0.5));
        transform-origin: 0 0;
    }
}

需要注意 <input type="button"> 是没有 :before, :after 伪元素的;

优点:其实不止是圆角, 其他的边框也可以这样做出来;

缺点:代码量也很大, 占据了伪元素, 容易引起冲突;

5、flexible.js

这是淘宝移动端采取的方案,github 的地址:https://github.com/amfe/lib-flexible. 前面已经说过1px变粗的原因就在于一刀切的设置 viewport 宽度, 如果能把 viewport 宽度设置为实际的设备物理宽度,css 里的1px不就等于实际1px长了么,flexible.js 就是这样干的。

<meta name=”viewport”> 里面的 scale 值指的是对 ideal viewport 的缩放,flexible.js 检测到 IOS 机型,会算出 scale = 1/devicePixelRatio,然后设置 viewport

metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');

devicePixelRatio=2 时输出 meta 如下,这样 viewport 与 ideal viewport 的比是0.5,也就与设备物理像素一致。

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

另外 html 元素上的 font-size 会被设置为屏幕宽的1/10, 这样 css 可以以 rem 为基础长度单位进行改写,比如 rem 是28px,原先的7px就是0.25rem,border 的宽度能直接写1px。

function refreshRem() {
    var width = docEl.getBoundingClientRect().width;
    if (width / dpr > 540) { //大于540px可以不认为是手机屏
        width = 540 * dpr;
    }
    var rem = width / 10; 
    docEl.style.fontSize = rem + 'px';
    flexible.rem = win.rem = rem;
}

px 和 rem 相互转换的计算方法会暴露在 window.lib.flexible中,这样可以为 less/sass 编写宏方法,具体的 css 改写方法参照大漠的文章:http://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html

项目中特别指出了为了防止字体模糊,出现奇数字号的字体,字体的实际单位还是要以 px 为单位。

缺点:

不适用安卓,flexible 内部做了检测,非 iOS 机型还是采用传统的 scale=1.0,原因在于安卓手机不一定有 devicePixelRatio 属性,就算有也不一定能响应 scale 小于1的 viewport 缩放设置,例如我的手机设置了 scale=0.33333333,显示的结果也与 scale=1 无异。

综合使用

对于IOS:flexible.js 处理的已经很好了;

对于 Android:方法2,3,4结合起来大体可以满足要求;

flexible.js虽然不适用于安卓, 但它里面的这一段代码可以用来做对安卓机的部署。

if (!dpr && !scale) {
    var isAndroid = win.navigator.appVersion.match(/android/gi);
    var isIPhone = win.navigator.appVersion.match(/iphone/gi);
    var devicePixelRatio = win.devicePixelRatio;
    if (isIPhone) {
        // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
        if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {                
            dpr = 3;
        } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
            dpr = 2;
        } else {
            dpr = 1;
        }
    } else {
        // 其他设备下,仍旧使用1倍的方案
        dpr = 1;
    }
    scale = 1 / dpr;
}

这里对安卓做检测,如果是安卓,js动态加载css。

var link = document.createElement('link');
link.setAttribute("rel","stylesheet");
link.setAttribute("type","text/css");
link.setAttribute("href",".......Android.css");
document.querySelector('head').appendChild(link);


作者:lunarorbitx

原文链接:HelloWeb前端网 » 移动端1px细线解决方案总结 » 感谢您的浏览,希望能有所帮助。

欢迎您加入“Helloweb” 学习交流群:HelloWeb-学习交流群 196291215 共同交流并结识同行,在这里说出您的收获与感想或有什么不同的观点,我们期待您的留言,分享,让我们一起进步!

喜欢 ()or分享