zoukankan      html  css  js  c++  java
  • flexible.js如何实现rem自适应

    flexible.js正是利用rem单位相对根元素<html>的font-size来做计算,而我们需要做的就是根据不同的屏幕算出html的font-size,而页面内的大小单位都根据rem来写,从而实现了自适应。 CSS单位rem 在W3C规范中是这样描述rem的:

    font size of the root element.

    简单的理解,rem就是相对于根元素<html>font-size来做计算。而我们的方案中使用rem单位,是能轻易的根据<html>font-size计算出元素的盒模型大小。而这个特色对我们来说是特别的有益处。

    前端实现方案

    了解了前面一些相关概念之后,接下来我们来看实际解决方案。在整个手淘团队,我们有一个名叫lib-flexible的库,而这个库就是用来解决H5页面终端适配的。 lib-flexible是什么? lib-flexible是一个制作H5适配的开源库,源码如下。

    ;(function(win, lib) {
        var doc = win.document;
        var docEl = doc.documentElement;
        var metaEl = doc.querySelector('meta[name="viewport"]');
        var flexibleEl = doc.querySelector('meta[name="flexible"]');
        var dpr = 0;
        var scale = 0;
        var tid;
        var flexible = lib.flexible || (lib.flexible = {});
        
        if (metaEl) {
            console.warn('将根据已有的meta标签来设置缩放比例');
            var match = metaEl.getAttribute('content').match(/initial-scale=([d.]+)/);
            if (match) {
                scale = parseFloat(match[1]);
                dpr = parseInt(1 / scale);
            }
        } else if (flexibleEl) {
            var content = flexibleEl.getAttribute('content');
            if (content) {
                var initialDpr = content.match(/initial-dpr=([d.]+)/);
                var maximumDpr = content.match(/maximum-dpr=([d.]+)/);
                if (initialDpr) {
                    dpr = parseFloat(initialDpr[1]);
                    scale = parseFloat((1 / dpr).toFixed(2));    
                }
                if (maximumDpr) {
                    dpr = parseFloat(maximumDpr[1]);
                    scale = parseFloat((1 / dpr).toFixed(2));    
                }
            }
        }
    
        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;
        }
    
        docEl.setAttribute('data-dpr', dpr);
        if (!metaEl) {
            metaEl = doc.createElement('meta');
            metaEl.setAttribute('name', 'viewport');
            metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
            if (docEl.firstElementChild) {
                docEl.firstElementChild.appendChild(metaEl);
            } else {
                var wrap = doc.createElement('div');
                wrap.appendChild(metaEl);
                doc.write(wrap.innerHTML);
            }
        }
    
        function refreshRem(){
            var width = docEl.getBoundingClientRect().width;
            if (width / dpr > 540) {
                width = 540 * dpr;
            }
            var rem = width / 10;
            docEl.style.fontSize = rem + 'px';
            flexible.rem = win.rem = rem;
        }
    
        win.addEventListener('resize', function() {
            clearTimeout(tid);
            tid = setTimeout(refreshRem, 300);
        }, false);
        win.addEventListener('pageshow', function(e) {
            if (e.persisted) {
                clearTimeout(tid);
                tid = setTimeout(refreshRem, 300);
            }
        }, false);
    
        if (doc.readyState === 'complete') {
            doc.body.style.fontSize = 12 * dpr + 'px';
        } else {
            doc.addEventListener('DOMContentLoaded', function(e) {
                doc.body.style.fontSize = 12 * dpr + 'px';
            }, false);
        }
        
    
        refreshRem();
    
        flexible.dpr = win.dpr = dpr;
        flexible.refreshRem = refreshRem;
        flexible.rem2px = function(d) {
            var val = parseFloat(d) * this.rem;
            if (typeof d === 'string' && d.match(/rem$/)) {
                val += 'px';
            }
            return val;
        }
        flexible.px2rem = function(d) {
            var val = parseFloat(d) / this.rem;
            if (typeof d === 'string' && d.match(/px$/)) {
                val += 'rem';
            }
            return val;
        }
    
    })(window, window['lib'] || (window['lib'] = {}));

    另外强烈建议对JS做内联处理,在所有资源加载之前执行这个JS。执行这个JS后,会在<html>元素上增加一个data-dpr属性,以及一个font-size样式。JS会根据不同的设备添加不同的data-dpr值,比如说2或者3,同时会给html加上对应的font-size的值,比如说75px。 如此一来,页面中的元素,都可以通过rem单位来设置。他们会根据html元素的font-size值做相应的计算,从而实现屏幕的适配效果。 除此之外,在引入lib-flexible需要执行的JS之前,可以手动设置meta来控制dpr值,如:

    <meta name="flexible" content="initial-dpr=2" />
    

    其中initial-dpr会把dpr强制设置为给定的值。如果手动设置了dpr之后,不管设备是多少的dpr,都会强制认为其dpr是你设置的值。在此不建议手动强制设置dpr,因为在Flexible中,只对iOS设备进行dpr的判断,对于Android系列,始终认为其dpr1

    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;
    }
    

    flexible的实质 flexible实际上就是能过JS来动态改写meta标签,代码类似这样:

    var metaEl = doc.createElement('meta');
    var scale = isRetina ? 0.5:1;
    metaEl.setAttribute('name', 'viewport');
    metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
    if (docEl.firstElementChild) {
        document.documentElement.firstElementChild.appendChild(metaEl);
    } else {
        var wrap = doc.createElement('div');
        wrap.appendChild(metaEl);
        documen.write(wrap.innerHTML);
    }
    

    事实上他做了这几样事情:

    • 动态改写<meta>标签
    • <html>元素添加data-dpr属性,并且动态改写data-dpr的值
    • <html>元素添加font-size属性,并且动态改写font-size的值

    把视觉稿中的px转换成rem 读到这里,大家应该都知道,我们接下来要做的事情,就是如何把视觉稿中的px转换成rem。在此花点时间解释一下。 首先,目前日常工作当中,视觉设计师给到前端开发人员手中的视觉稿尺寸一般是基于640px750px以及1125px宽度为准。甚至为什么?大家应该懂的(考虑Retina屏)。 正如文章开头显示的示例设计稿,他就是一张以750px为基础设计的。那么问题来了,我们如何将设计稿中的各元素的px转换成rem。 我厂的视觉设计师想得还是很周到的,会帮你把相关的信息在视觉稿上标注出来。 目前Flexible会将视觉稿分成100份(主要为了以后能更好的兼容vhvw),而每一份被称为一个单位a。同时1rem单位被认定为10a。针对我们这份视觉稿可以计算出:

    1a   = 7.5px
    1rem = 75px 
    

    那么我们这个示例的稿子就分成了10a,也就是整个宽度为10rem<html>对应的font-size75px: rem-2 这样一来,对于视觉稿上的元素尺寸换算,只需要原始的px值除以rem基准值即可。例如此例视觉稿中的图片,其尺寸是176px * 176px,转换成为2.346667rem * 2.346667rem

    如何快速计算

    在实际生产当中,如果每一次计算px转换rem,或许会觉得非常麻烦,或许直接影响大家平时的开发效率。为了能让大家更快进行转换,我们团队内的同学各施所长,为px转换rem写了各式各样的小工具。 CSSREM CSSREM是一个CSS的px值转rem值的Sublime Text3自动完成插件。这个插件是由@正霖编写。先来看看插件的效果: cssrem 有关于CSSREM如何安装、配置教程可以点击这里查阅。 CSS处理器 除了使用编辑器的插件之外,还可以使用CSS的处理器来帮助大家处理。比如说Sass、LESS以及PostCSS这样的处理器。我们简单来看两个示例。 Sass 使用Sass的同学,可以使用Sass的函数、混合宏这些功能来实现:

    @function px2em($px, $base-font-size: 16px) {
        @if (unitless($px)) {
            @warn "Assuming #{$px} to be in pixels, attempting to convert it into pixels for you";
            @return px2em($px + 0px); // That may fail.
        } @else if (unit($px) == em) {
            @return $px;
        }
        @return ($px / $base-font-size) * 1em;
    }
    

    除了使用Sass函数外,还可以使用Sass的混合宏:

    @mixin px2rem($property,$px-values,$baseline-px:16px,$support-for-ie:false){
        //Conver the baseline into rems
        $baseline-rem: $baseline-px / 1rem * 1;
        //Print the first line in pixel values
        @if $support-for-ie {
            #{$property}: $px-values;
        }
        //if there is only one (numeric) value, return the property/value line for it.
        @if type-of($px-values) == "number"{
            #{$property}: $px-values / $baseline-rem;
        }
        @else {
            //Create an empty list that we can dump values into
            $rem-values:();
            @each $value in $px-values{
                // If the value is zero or not a number, return it
                @if $value == 0 or type-of($value) != "number"{
                    $rem-values: append($rem-values, $value / $baseline-rem);
                }
            }
            // Return the property and its list of converted values
            #{$property}: $rem-values;
        }
    }
    

    有关于更多的介绍,可以点击这里进行了解。 PostCSS(px2rem) 除了Sass这样的CSS处理器这外,我们团队的@颂奇同学还开发了一款npm的工具px2rem。安装好px2rem之后,可以在项目中直接使用。也可以使用PostCSS。使用PostCSS插件postcss-px2rem:

    var gulp = require('gulp');
    var postcss = require('gulp-postcss');
    var px2rem = require('postcss-px2rem');
    
    gulp.task('default', function() {
        var processors = [px2rem({remUnit: 75})];
        return gulp.src('./src/*.css')
            .pipe(postcss(processors))
            .pipe(gulp.dest('./dest'));
    });
    

    除了在Gulp中配置外,还可以使用其他的配置方式,详细的介绍可以点击这里进行了解。 配置完成之后,在实际使用时,你只要像下面这样使用:

    .selector {
        width: 150px;
        height: 64px; /*px*/
        font-size: 28px; /*px*/
        border: 1px solid #ddd; /*no*/
    }
    

    px2rem处理之后将会变成:

    .selector {
        width: 2rem;
        border: 1px solid #ddd;
    }
    [data-dpr="1"] .selector {
        height: 32px;
        font-size: 14px;
    }
    [data-dpr="2"] .selector {
        height: 64px;
        font-size: 28px;
    }
    [data-dpr="3"] .selector {
        height: 96px;
        font-size: 42px;
    }
    

    在整个开发中有了这些工具之后,完全不用担心px值转rem值影响开发效率。 字号不使用rem 前面大家都见证了如何使用rem来完成H5适配。那么文本又将如何处理适配。是不是也通过rem来做自动适配。 显然,我们在iPhone3G和iPhone4的Retina屏下面,希望看到的文本字号是相同的。也就是说,我们不希望文本在Retina屏幕下变小,另外,我们希望在大屏手机上看到更多文本,以及,现在绝大多数的字体文件都自带一些点阵尺寸,通常是16px24px,所以我们不希望出现13px15px这样的奇葩尺寸。 如此一来,就决定了在制作H5的页面中,rem并不适合用到段落文本上。所以在Flexible整个适配方案中,考虑文本还是使用px作为单位。只不过使用[data-dpr]属性来区分不同dpr下的文本字号大小。

    div {
         1rem; 
        height: 0.4rem;
        font-size: 12px; // 默认写上dpr为1的fontSize
    }
    [data-dpr="2"] div {
        font-size: 24px;
    }
    [data-dpr="3"] div {
        font-size: 36px;
    }
    

    为了能更好的利于开发,在实际开发中,我们可以定制一个font-dpr()这样的Sass混合宏:

    @mixin font-dpr($font-size){
        font-size: $font-size;
    
        [data-dpr="2"] & {
            font-size: $font-size * 2;
        }
    
        [data-dpr="3"] & {
            font-size: $font-size * 3;
        }
    }
    

    有了这样的混合宏之后,在开发中可以直接这样使用:

    @include font-dpr(16px);
    

    当然这只是针对于描述性的文本,比如说段落文本。但有的时候文本的字号也需要分场景的,比如在项目中有一个slogan,业务方希望这个slogan能根据不同的终端适配。针对这样的场景,完全可以使用rem给slogan做计量单位。 CSS 本来想把这个页面的用到的CSS(或SCSS)贴出来,但考虑篇幅过长,而且这么简单的页面,我想大家也能轻而易举搞定。所以就省略了。权当是给大家留的一个作业吧,感兴趣的可以试试Flexible能否帮你快速完成H5页面终端适配。

    总结

    其实H5适配的方案有很多种,网上有关于这方面的教程也非常的多。不管哪种方法,都有其自己的优势和劣势。而本文主要介绍的是如何使用Flexible这样的一库来完成H5页面的终端适配。为什么推荐使用Flexible库来做H5页面的终端设备适配呢?主要因为这个库在手淘已经使用了近一年,而且已达到了较为稳定的状态。除此之外,你不需要考虑如何对元素进行折算,可以根据对应的视觉稿,直接切入。 当然,如果您有更好的H5页面终端适配方案,欢迎在下面的评论中与我们一起分享。如果您在使用这个库时,碰到任何问题,都可以在Github给我们提Issue。我们团队会努力解决相关需Issues。

    更新

    同学们反馈需要一个在线演示的DEMO。那么花了点时间写了个demo,希望对有需要的同学有所帮助。友情提示:DEMO未经过所有设备测试,可能在部分设备上有细节上的差异 DEMO 请用手机扫下面的二维码 yiboqr 参考文章: http://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html

    I'm clj, I have one dream.
  • 相关阅读:
    vue中的 computed 和 watch 的区别
    mysql8.0 初始化数据库及表名大小写问题
    sql server alwayson 调整数据文件路径
    zabbix 自定义监控 SQL Server
    mysql 创建用户及授权
    mysql 设置从库只读模式
    mysql8.0 主从复制安装及配置
    centos8.0安装mysql8.0
    centos8替换阿里数据源
    npm publish 报错 【you or one of your dependencies are requesting a package version that is forbidden by your security policy】
  • 原文地址:https://www.cnblogs.com/clj2017/p/14807567.html
Copyright © 2011-2022 走看看