网页自适应的实现及方案研究,新手必学的网页程序开发技巧

随着手机的普及程度越来越高,网页开发的需求兼容性就要更广。你的网页页面设计为自适应是非常必要的,那么对于菜手们来说,又是需要来学习一下的 ,这不这就收集整理了一些资料,普及一下,

1.关于自适应布局的一些基本概念

  • px (pixel):像素,是屏幕上显示数据的最基本的点,表示相对大小。不同分辨率下相同长度的px元素显示会不一样,比如同样是14px大小的字,在1366✖768显示屏下会显示的小,在1024×768尺寸的显示器下会相对大点。也称为物理像素(设备像素),是分辨率的尺寸单位。

  • pt是一种固定长度的度量单位,能够使用测量设备测得的长度,等于1/72英寸。

  • css像素,在不同屏幕上,css像素呈现的物理尺寸一致,但css像素对应的物理像素具数不同。标准的显示密度下,1个css像素对应一个物理像素,缩放时,1个css像素对应的物理像素会减增。css像素称为设备独立像素(device independent pixels: DIPs)

  • PPI (pixel per inch),像素密度,每英寸所拥有的像素数。值越高,屏幕越细腻。

  • DPI(dot per inch),打印设备每英寸印刷出来的点有多少个,值越高,图片越细腻。

  • DPR 设备物理像素和设备独立像素比,即,是指在理想布局宽度,使用多少个物理像素来渲染一个设备独立像素。js中通过window.devicePixelRatio获取,css中通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio,-webkit-max-device-pixel-ratio进行媒体查询

  • 屏幕分辨率:若屏幕分辨率是1024×768,即设备屏幕的水平方向上有1024个像素点,垂直方向上有768个像素点。

  • em 相对父元素的font-size

  • rem 相对长度单位,相对于根元素font-size计算值的倍数

  • 视口 viewport 属性值提供有关视口初始大小的信息。PC端视口指的是浏览器可视区域,宽度与浏览器窗口保持一致。移动端涉及布局视口(Layout Viewport)、视觉视口(Visual ViewPort)和理想视口(Ideal ViewPort)。布局视口是指用 用视口元标签(viewport meta)来进行布局视口设置,视觉视口是指用户当前看到的区域,理想视口是屏幕分辨率的值,通过设置 实现。

  • vw(viewport width) 视窗宽度,1vw = 视窗宽度的1%
  • vh(viewport height) 视窗高度,1vh = 视窗高度的1%

方案

viewport方案 配置默认根字号、默认字号、默认设计稿宽度,然后使用postcss插件将代码中的px自动转换成rem,这种方法可以完成使用1080p设计稿再不同手机下的适应方案,所有元素大小均按照1080p下的大小来定义px。配置上述插件后,px单位在编译后会默认转为rem单位,如果不需要被转换,可以将小写的px改成大写的PX或Px。取消使用viewport.js文件,如果遇到不能适配的公共组件,则拷贝一个新的版本,适配新的方案,逐步淘汰老版本。

 

// 引用该mixin,其中包含了默认的变量等
$default-font-size: 100;    // 默认根字号大小,用于换算vw单位
$baseFontSize: 100px;   // 默认字号大小,用于计算rem值
$pageWidth: 1080;       // 默认的设计稿宽度
@import '@oppobrowser/lib-browser-scss/src/mixins.scss';

//项目的vue.config.js中增加如下postcss配置
// 在css配置项中增加postcss配置,用于px转rem单位,后续合到master后就统一配置了
css: {
    loaderOptions: {
      sass: {
        data: '@import "@/common/scss/variables.scss";',
      },
      postcss: {
        plugins: [
          require('postcss-pxtorem')({
            // 把px单位换算成rem单位
            rootValue: 100, // 换算的基数(设计图1080的根字体为100)
            selectorBlackList: [], // 忽略转换正则匹配项
            propList: ['*'],
          }),
        ],
      }
}
  • rem方案 针对Android4.4以下版本,全部使用rem单位兼容方案,并在head标签内添加脚本重写html根字号的大小,1080p下为100px。并且针对受影响的公共组件写出兼容性代码。

 


 

各种适配方案比较

  • rem方案,根据屏幕宽度设置html标签的font-size,再布局时使用rem单位布局,达到自适应的目的。是弹性布局的一种实现方式

 

实现原理:

 

(function flexible (window, document) {
  var docEl = document.documentElement //文档对象根元素的只读属性
  var dpr = window.devicePixelRatio || 1 //获取设备dpr

  // adjust body font size
  function setBodyFontSize () {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px'
      //浏览器有最小字体限制,css在pc上font-size是12,也就是css像素是12,其DPR为1,在移动端dpr有可能为2和3,为了保证字体不变小,需要用12*dpr进行换算。在普通屏幕下1个CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素对应的却是4个物理像素。
    }
    else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
  }
  setBodyFontSize(); //在html文档加载和解析完成后设置body元素字体大小

  // set 1rem = viewWidth / 10
  function setRemUnit () { //将页面元素分为100份,每份为a,1rem=10a
    var rem = docEl.clientWidth / 10 
    docEl.style.fontSize = rem + 'px'
  }

  setRemUnit()

  // reset rem unit on page resize
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function (e) { //当一条会话历史纪录被执行的时候出发事件,包括后退/前进按钮,同时会在onload页面触发后初始化页面时触发
    if (e.persisted) {
      setRemUnit()
    }
  })
//当页面缩放/前进/后退的时候调整rem大小


  // detect 0.5px supports
  if (dpr >= 2) {
    var fakeBody = document.createElement('body')
    var testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) { //offsetHeight返回元素像素高度,高度包含元素边框/内边距和水平滚动条
      docEl.classList.add('hairlines')//只读属性,返回一个类属性的实时DOMTokenList集合

    }
    docEl.removeChild(fakeBody)
  }
}(window, document))

具体来讲就是

  • 获取文档根元素和设备dpr,在html文档加载和解析完成后调整body字体大小
  • 获取元素的内部宽度,不包括垂直滚动条,边框和外边距
  • 在页面缩放/回退/前进的时候重新调整rem大小
    *用css处理器或npm包将页面css样式中的px自动转换成rem
  • 文本字号不建议使用rem,在制作H5的页面中,rem并不适合用到段落文本上,在整个flexible适配方案中,文本使用px作为单位i,使用[data-dpr]属性来区分不同dpr下的文本字号。

 

(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) { //meta名称为viewport的标签设置了scale时,将根据scale手动设置dpr(dpr用于实现flexible其他功能)
        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) {   //meta名称为flexible的标签存在时,手动设置dpr
        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) { //根据js获取到的devicePixelRatio设置dpr及scale,scale是dpr的倒数。(ios系统根据dpr的值设置为1、2、3,Android统一设置dpr为1)
        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) {//添加meta标签,设置name为viewport,content根据scale设置缩放比(默认、最大、最小缩放比)
        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(){//resize事件与pageshow事件延时300毫秒触发fontSize的重置(这里用了防抖函数,防止resize事件被高频触发可能引起的性能问题) 
        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();
   //为body设置fontSize,值为12*dpr+”px” 7.扩展一些方法:获取页面dpr,获取rem基准值,rem与px相互转换,重置dpr,这些方法可以在引入flexible.js之后调用,使用方式参考api文档。
    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'] = {}));

当然针对高清屏,它还会设置“viewport scale”,以缩放页面,解决类似高清屏下无法实现1px边框等问题。

响应式针对的是不同分辨率设备而进行的适配式设计,以利用@media规则为主要手段,而自适应则忽略@media以比例布局为主,目的是适应不同的浏览器窗口大小。

百分比方案

 

  • 子元素height和width的百分比 基于子元素的直接父元素,width相对于父元素的width,height相对于父元素的height。
  • top和bottom 、left和right 相对于直接非static定位(默认定位的)父元素的高度/宽度。
  • padding和margin 不论是垂直方向或者是水平方向,都相对于直接父亲元素的width,而与父元素的height无关。
  • border-radius border-radius不一样,如果设置border-radius为百分比,则是相对于自身的宽度

方法:使用百分比定义宽度,高度用px固定,根据可视区域和父元素的实时尺寸进行调整,尽可能适应各种分辨率,配合max-width和min-width属性控制尺寸流动范围过大或过小影响阅读。

缺点:

  1. 如果屏幕尺度跨度太大,相对设计稿过大或者过小的屏幕不能正常显示,宽度使用百分比定义,高度和文字大小用px固定,在大屏手机下显示效果会变成有些元素被拉长,高度,文字大小还是一样。
  2. 设置盒模型的不同属性,其百分比设置的参考元素不唯一,容易使布局问题变得复杂

基于媒体查询的响应式设计样例

响应式设计是基于流式布局+弹性布局+媒体查询技术使用,使得一个网站同时适配多种设备和多个屏幕,让网站的布局和功能随用户的使用环境(屏幕大小、输出方式、设备/浏览器能力而变化),使其视觉合理,交互方式符合习惯。如使得内容区块可伸缩与自由排布,边距适应页面尺寸,图片适应比例变化,能够自动隐藏/部分显示内容,能自动折叠导航和菜单,放弃使用像素作为尺寸单位,使用dp。

主要实现是通过媒体查询,通过给不同分辨率的设备编写不同的样式实现响应式布局,用于解决不同设备不同分辨率之间兼容问题,一般是指PC、平板、手机设备之间较大的分辨率差异。比如给小屏幕手机设置@2x图,为大屏手机设置@3x图

 

@media only screen and (min-width: 401px){
    样式1
}
@media only screen and (min-width: 428px){
    样式2
}

其中,会应用到flex布局实现宽度自适应。

浏览器页面前端自适应方案中的图片处理

加载网页时,平均60%以上的流量都来自加载图片。指定图像宽度时使用相对单位,防止意外溢出视口。如, width: 50%; 将图片宽度设置为包含元素宽度的 50%。因为css允许内容溢出容器, 需要使用max-width: 100%来保证图像及其他内容不会溢出。

 


使用 img 元素的 alt 属性提供描述,描述有助于提高您的网站的可访问性,能提供语境给屏幕阅读器及其他辅助性技术。

  • 对于img引入的图片,使用属性——srcsesizes 来提供更多额外的图像资源,帮助浏览器选择合适的图片
    针对不同大小屏幕,使用不同分辨率版本的图片,高DPI设备上使用srcset来增强img。
     srcset定义了允许浏览器选择的图像集(文件名),以及每个图像的大小(使用w单位)。
    sizes定义了一组媒体条件(例如屏幕宽度),指明当某些媒体条件为真时,什么样的图片尺寸是最佳选择。

 

Elva dressed as a fairy

浏览器的查询过程:1.查看设备宽度; 2.检查sizes列表中哪个媒体条件是第一个为真;3.查看给予该媒体查询的槽大小;4.加载srcset列表中引用的最接近所选的槽大小的图像

如果想要针对不同像素密度的屏幕,使用的srcset属性用来指定多张图像,适应不同像素密度的屏幕。它的值是一个逗号分隔的字符串,每个部分都是一张图像的 URL,后面接一个空格,然后是像素密度的描述符。

 


srcset属性给出了三个图像 URL,适应三种不同的像素密度。图像 URL 后面的像素密度描述符,格式是像素密度倍数 + 字母x。1x表示单倍像素密度,可以省略。浏览器根据当前设备的像素密度,选择需要加载的图像。如果srcset属性都不满足条件,那么就加载src属性指定的默认图像。

  • 对于引入标签的图片高清解决方案,使用js自带的异步加载图片

 


 

var dpr = window.devicePixelRatio;
if(dpr > 3){
    dpr = 3;
};

var imgSrc = $('#img').data('src'+dpr+'x');
var img = new Image();
img.src = imgSrc;
img.onload = function(imgObj){
    $('#img').remove().prepend(imgObj);//替换img对象
};

如果是img标签引入的图片,可以使用延迟加载的方式来加载,在实际加载图片之前先用js检查窗口宽度,然后加载不同分辨率的图片,比如宽度<=480,就加载80px宽度的图片,480 < 宽度 <= 768,加载120px的图片, 宽度> 768则加载160px的图片,如果宽度是600px怎么办呢,通过百分比来缩放120px的图片达到合适的结果。这样做的好处是对于移动设备来说,下载的图片会小一些,减少网页加载的时间。但是问题是竖屏向横屏切换或者扩大浏览器窗口宽度时图片会由于放大而产生一定的模糊感。

  • 为不同的视口提供不同的图片,使用标签。是html5中定义的一个容器标签,内部使用,浏览器会匹配type,media,srcset等属性,找到最适合当前布局/视口宽度/设备像素密度的图像进行加载.这里的标签是浏览器不支持picture元素,或者支持picture但没有合适的媒体定义时的后备,不能省略。

 


  
  
  cat

  • 对于背景图片,使用image-set根据用户设备的分辨率匹配合适的图像 同时要考虑兼容性问题

 


  • 对于背景图片,使用media query自动切换不同分辨率的版本

 


  • 维护自适应页面中图片宽高比固定比较常用的方法是使用padding设置

对字体的设置

百分比方案的字体设置如果按照百分比来定义,当屏幕宽度过大时,需要另外设置自适应方案,如根据em设置字体大小
rem方案和vw/vh方案的字体,在屏幕大小发生改变时,可以依据调整后的根元素字体大小和vw/vh值进行适配
响应式设计可以根据媒体查询给不同的分辨率指定不同的字体样式。

 

calc()函数

calc()函数在声明css属性值时执行一些计算,可以用在如下场合 length/frequency/angle/time/number/integer,语法 : property: calc(expression),用一个表达式作为参数,用这个表达式的结果作为值。例 :

  • 使用指定的外边距定位一个对象,使用calc()可以为一个对象设置一个左右两边相等的外边距

 


  • 确保表单域的大小适合当前的可用空间,不会在保持合适外边距的同时,因挤压超出容器的边缘

 

个人比较偏向于 百分比方案,对于我这些上手级别的爱好者,这些比较容易接受,借自网上的一些资料,学习一下先,

还有一个方案  CSS  定义    @media screen 实现网页布局的自适应,@media screen and

 

一种是直接在link中判断设备的尺寸,然后引用不同的css文件:

 
1
意思是当屏幕的宽度 大于等于400px的时候,应用styleA.css
————————————————
另一种方式,即是直接写在