前言: 我的总结, 结合各大神的文章, 若有不妥之处, 请指出~css
设备屏幕的物理像素,表示屏幕上能够铺多少个点点,而不是一个绝对长度单位(例如in,mm); 单位是px,好比iPhone6的 (750 x 1334px)html
一个物理概念
对于屏幕,分辨率通常表示屏幕上显示的物理像素总和。好比,咱们说iPhone6屏幕分辨率是(750 x 1334px)
对于图像,概念等同于图像尺寸、图像大小、像素尺寸等等。好比,咱们说(20 x 20px)的iconandroid
是Web编程的概念,指的是CSS样式代码中使用的逻辑像素, 或者称为设备独立像素
, 由于只与设备相关, 例如: iPhone6 375 x 667px
1个CSS像素在不一样设备上可能对应不一样的物理像素数,这个比值是设备的属性(Device Pixel Ratio,设备像素比)ios
经过 document.documentElement.clientWidth/clientHeight / document.documentElement.getBoundingClientRect().width 获取css3
在CSS规范中,长度单位能够分为绝对单位和相对单位。px是一个相对单位,相对的是设备像素(Device Pixels)git
Android设备的特色是屏幕尺寸不少,所以为了显示能尽可能和设备无关,提出了dip,参照的density是160。github
// 当屏幕密度density为160(单位是ppi或者dpi,一个意思)时,px === dip px = dip * density / 160 // 因此 dip = px * 160 / dpi
注: 此处只针对于Android, windows 也有 DIP 概念, 含义不一样, IOS貌似不存在
编程
Device pixel ratio, the ratio between physical pixels and logical pixels used by cascading style sheets (CSS): other names for it are “CSS Pixel Ratio” and “dppx”
表示1个CSS像素(宽度)等于几个物理像素(宽度)
DPR = 物理像素(设备像素) / 逻辑像素(css像素/设备独立像素) // [未缩放]
像素密度也叫显示密度或者屏幕密度,缩写为DPI(Dots Per Inch)或者PPI(Pixel Per Inch)windows
// 屏幕对角线的像素尺寸 / 物理尺寸(inch 英寸) Math.sqrt(750*750 + 1334*1334) / 4.7 = 326ppi
桌面上视口宽度等于浏览器宽度,但在手机上有所不一样。浏览器
手机上为了容纳为桌面浏览器设计的网站,默认布局视口宽度远大于屏幕宽度,为了让用户看到网站全貌,它会缩小网站
document.documentElement.clientWidth
屏幕的可视区域,即物理像素尺寸, 可变, 与当前缩放值和设备的屏幕宽度有关visual viewport宽度 = ideal viewport宽度 / 当前缩放值
能够经过window.innerWidth 来获取,但在Android 2, Oprea mini 和 UC 8中没法正确获取。
ideal viewport是最适合移动设备的viewport,ideal viewport的宽度等于移动设备的屏幕宽度
在移动开发时, 在meta[name='viewport']中, 经过width = device-width
把当前的viewport宽度设置为理想视口, 不然宽度将默认为布局视口
ideal viewport并无一个固定的尺寸,不一样的设备拥有有不一样的ideal viewport。早期全部iPhone理想视口为320x480px
因此,在没有缩放的状况下,屏幕的CSS像素宽度实际上是指理想视口的宽度,而meta标签:
<meta name="viewport" content="width=device-width, inital-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
上面的meta指定了布局视口=理想视口,而且禁止缩放。因此添上width=device-width的viewport meta后页面变大了(一开始页面内容小得看不清),其实是布局视口变小了
initial-scale=1 解决了 iphone、ipad 不管横竖屏都把宽度设为竖屏时 ideal viewport 的宽度
width=device-width 解决了IE 不管横竖屏都把宽度设为竖屏时 ideal viewport 的宽度
visual viewport宽度 vs ideal viewport宽度
visual viewport宽度 = ideal viewport宽度 / 当前缩放值 当前缩放值 = ideal viewport宽度 / visual viewport宽度
参考:
http://www.ayqy.net/blog/%e5%...
https://github.com/jawil/blog...
mate 中的 device-width / width:
能够设置 <meta name="viewport" content="width=device-width"/>
最多见的方式,经过屏幕宽度(用CSS像素描述的宽度)来区分各类设备
前提:
<meta name="viewport" content="width=device-width"/>
// 响应式网站 @media (min-width:320px) { /* smartphones, portrait iPhone, portrait 480x320 phones (Android) */ } @media (min-width:480px) { /* smartphones, Android phones, landscape iPhone */ } @media (min-width:600px) { /* portrait tablets, portrait iPad, e-readers (Nook/Kindle), landscape 800x480 phones (Android) */ } @media (min-width:801px) { /* tablet, landscape iPad, lo-res laptops ands desktops */ } @media (min-width:1025px) { /* big landscape tablets, laptops, and desktops */ } @media (min-width:1281px) { /* hi-res laptops and desktops */ } /* 另外 */ min-width: 480px: Will target mobile devices in landscape mode and up
// 移动端 @media screen and (min-width: 320px) { html { font-size: 50px; } } @media screen and (min-width: 360px) { html { font-size: 56px; } } @media screen and (min-width: 414px) { html { font-size: 63px; } } // 或者, 根据具体需求肯定 @media screen and (max-width: 320px) { font-size: 32px; } @media screen and (min-width: 540px) { font-size: 54px; }
难以覆盖全部的尺寸, 写起来比较繁琐
兼容性良好
rem: 根元素(html)的字体大小. 即 1rem = html中设置的 font-size
获取设备宽度document.documentElement.getBoundingClientRect().width / document.documentElement.clientWidth
原理: 比例问题, 根据设计图比例计算出固定 rem 值
实现: 主要经过修改 html 的 fontSize
实现一 直接引入 参考
(function (doc, win) { var docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function () { var clientWidth = docEl.clientWidth; if (!clientWidth) return; if(clientWidth>=640){ docEl.style.fontSize = '100px'; }else{ docEl.style.fontSize = 100 * (clientWidth / 640) + 'px'; // 640: 可根据设计图来定 } }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window);
实现二 使用flexible.js
flexible,js
lib-flexible 手淘rem方案
移动端适配与dpr
无关, 与dpr
关联是为了处理1px
兼容问题, 具体参考: 真的,移动端尺寸自适应与dpr无关, 从网易适配方案和下面手淘最新版适配方案也能够看出
1, 旧版本
旧版本#17 代码
处理过程以下:
1.先取 dpr (dpr = window.devicePixelRatio) 2.而后设置 scale = 1/dpr 3.而后就有 innerWidth 了, innerWidth = 375 / scale = 750px (device-width = document.documentElement.clientWidth) 4.而后将 innerWidth 分红 10rem, font-size = innerWidth / 10 = 75px
;(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'] = {}));
2, 新版本
新版本 2.0代码
meta 标签固定为<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no”>
代码逻辑为:
取 width 分为 10rem, font-size = width / 10
注: 手机淘宝首页 2019-01-16, 也是这个方案, 可是是 width = 3.75rem, font-size = 100px, 估计是为了方便除
代码,添加部分注释
(function flexible (window, document) { var docEl = document.documentElement var dpr = window.devicePixelRatio || 1 // adjust body font size function setBodyFontSize () { if (document.body) { document.body.style.fontSize = (12 * dpr) + 'px' } else { document.addEventListener('DOMContentLoaded', setBodyFontSize) } } setBodyFontSize(); // set 1rem = viewWidth / 3.6 function setRemUnit () { // var rem = docEl.clientWidth / 10 // 原始代码 var rem = docEl.clientWidth / 3.6 // 修改后代码 docEl.style.fontSize = rem + 'px' } setRemUnit() // reset rem unit on page resize window.addEventListener('resize', setRemUnit) window.addEventListener('pageshow', function (e) { // 参考: https://juejin.im/post/5c807802f265da2de33f4e1f // 页面从浏览器的缓存中读取该属性返回 ture, 优化方案 window.performance.navigation.type // if (e.persisted) { // 原始代码 // setRemUnit() // } if (e.persisted || (window.performance && window.performance.navigation.type === 2)) { // 修改后代码 setRemUnit() } }) // detect 0.5px supports 检测是否支持0.5px, 用于1px问题 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) { docEl.classList.add('hairlines') } docEl.removeChild(fakeBody) } }(window, document))
一、iframe 问题
二、富文本问题
五、系统字体缩放时, 发生变化, 由于和根元素紧密联系 - 解决方案: 缩放还原, 具体以下:
1> https://juejin.im/post/59f678...
2> 基于 flexible.js 添加如下代码: https://juejin.im/post/5b9cb9...
var root = window.document.documentElement; var fontSize = parseFloat(root.style.fontSize); var finalFontSize = parseFloat(window.getComputedStyle(root).getPropertyValue("font-size")); if(finalFontSize !== fontSize) { root.style.fontSize = fontSize * fontSize / finalFontSize + "px"; }
注:window.getComputedStyle(docEl).getPropertyValue('font-size') // html最终的font-size大小
getComputedStyle
Window.getComputedStyle()方法返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的全部CSS属性的值。 私有的CSS属性值能够经过对象提供的API或经过简单地使用CSS属性名称进行索引来访问。
getPropertyValue
此 CSSStyleDeclaration.getPropertyValue() 接口会返回一个 DOMString ,这个返回值将会包含预请求的CSS属性信息。
1vw: 可视窗口宽度1%
1vh: 可视窗口高度1%
vmin/vmax: vw和vh中最小/最大的那个
实现:
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" /> 设计图尺寸:设计图元素尺寸 = 100vw:Y // Y 表示css样式中设计图某元素的大小, 单位vw
缺点:
一、没有最大/最小限制
二、兼容性: ios8/andorid4,4 以上
优势:
(如下实现以[rem与vw关系: rem:vw = 10:1 或者 1rem = 10vw]为依据)
前提: <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
一、 html 的 font-size 等于 1/10 的视口宽度 (即: 1rem = 1 / 10 * 100vw => 等同于lib-flexible 中 document.documentElement.style.fontSize = clientWidth / 10) [此处取1/10, 由于在淘宝方案也是取这个值, 为了更好计算能够去其余值]
// $vw_design: 设计图尺寸 // $vw_fontsize: 设计图尺寸 / 10 假设把设计图分为10份, 每份的大小(设计图的1rem), 并以此为基数 html { font-size: ($vw_fontsize / $vw_design) * 100vw; // 直接写 10vw // 同时,经过Media Queries 限制根元素最大最小值 @media screen and (max-width: 320px) { font-size: 32px; } @media screen and (min-width: 540px) { font-size: 54px; } }
二、计算使用scss函数: 设计图元素尺寸 / (设计图尺寸 / 10) * 1rem
// $basesize: 设计图元素尺寸 @function rem($basesize) { @return ($basesize / $vw_fontsize) * 1rem; } //简化 @function rem($basesize) { @return ($basesize / $vw_design) * 10rem; }
计算原理:
假设设计图元素所占视口大小为 X, 单位为: vw 设计图元素尺寸 / 设计图尺寸 = (Y)rem / 100vw => (X)vw = (设计图元素尺寸 / 设计图尺寸) * 100vw // 转化为以rem单位的数值Y => (Y)rem = (X)vw / 10
参考: https://juejin.im/post/5c0357...
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <!-- <meta name="viewport" content="width=640, user-scalable=no"> --> <!-- <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" /> --> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Test</title> <style> * { padding: 0; margin: 0; } html { /* font-size: 13.33vw; */ } #box { width: 2.667rem; height: 2.667rem; /* width: 1rem; height: 1rem; */ /* width: 13.33vw; height: 13.33vw; */ background: #ccc; border: 1px solid #000; } </style> </head> <body> <script> (function () { var dpr = window.devicePixelRatio; var meta = document.createElement('meta'); var scale = 1 / dpr; meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width, user-scalable=no, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale); document.getElementsByTagName('head')[0].appendChild(meta); // 动态设置的缩放大小会影响布局视口的尺寸 function resize() { var deviceWidth = document.documentElement.clientWidth; // 不设置 meta[name='viewport'], 等于980px document.documentElement.style.fontSize = (deviceWidth / 10) +'px'; } resize(); window.onresize = resize; })() // dpr = 2 // 设备逻辑像素 = 设备物理像素(固定) / (dpr * scale) = 750 / (2 * 0.5) = 750 // 视口放大一倍 // 当前缩放比 = ideal viewport / visual viewport(设备逻辑像素) = 375 / 750 = 0.5 // 页面缩小一倍 </script> <div id="box"> </div> </body> </html>