经过前几天写的两篇博客(浅谈移动端三大viewport和移动端em和rem区别),咱们如今来总结一下如何实现一个最佳方案。css
以前在第二篇博客中提到过咱们可使用媒体查询来针对不一样设备及作适配,以下图html
可是这个可能不会太精准,好比个人设备布局viewport多是370px,这样只能使用320这一版本。而事实上,他们的viewport并不相同,因此他们的布局也得不同。也就是说咱们若是用css媒体查询只能说是能够用,但不是最佳实践。其实经过看某些牛逼的移动端网站,能够看到他们的共同点,他们都是使用js方式来实现的。android
能够看到,慕课网以及咱们如今作的今日十大H5页面,他们共同的地方就是在根html上设置data-dpr以及font-size。git
为何要设置这两个东西呢?github
咱们能够回归移动端的本质,就是根据不一样的设备规划不一样的布局,这里面有一点须要注意,就是任何分辨率下咱们的字体要保持一致。因此data-dpr是针对字体设置的,font-size是咱们不一样设备上用rem的基准值。其实有一个规范的js文件能够帮咱们解决以上问题,flexible.js(能够到github上copy)基本知足咱们的需求。浏览器
;(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'] = {}));
简单的说一下这里面的重点吧,咱们先设置页面viewport中的scale,这个用来解决border 1px问题,下面是个人一个css demosass
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <title>Document</title> <style> ul li { width: 100px; padding: 8px 0 8px 15px; color: #7c7c7c; position: relative; } ul li + li:before { position: absolute; top: -1px; left: 0; content: ''; width: 100%; height: 1px; border-top: 1px solid #ddd; /*transform: scaleY(0.5);*/ } </style> </head> <body> <ul> <li></li> <li></li> <li></li> </ul> </body> </html>
而后咱们下面展现的样式第一个是注释掉 transform: scaleY(0.5)的,第二个是打开的。app
为何会这样子呢,我在浅谈移动端三大viewport说过,dpr为2的高清屏下,1个css像素=4个物理像素(1px = dpr²*dp)ssh
因此,设计师想要的retina下border: 1px;
其实就是1物理像素宽,对于css而言,能够认为是border: 0.5px;
,这是高清屏下(dpr=2)下能显示的最小单位,但并非全部的浏览器都支持border:0.5px,因此咱们须要scale(0.5),也就是上面flexible.js提到的scale,经过不一样的dpr(devicePixelRatio)来设置不一样的scale。iphone
而后咱们的主角rem是经过refreshRem函数中的docEl.getBoundingClientRect().width来获取的,这个值跟document.body.clientWidth是相同的,也就是布局viewport,以后的width/10纯属是为了便于计算。等下,当咱们在iphone5下查看布局viewport时,为何是640,而不是320,
这是由于咱们在前面设置了scale,在iphone5下,经过获取dpr(devicePixelRatio)是2,而后计算出initial-scale是0.5,由于在initial-scale为1状况下,咱们知道布局viewport等于设备宽度,也就是等于理想视口(screen.width),iphone5下的理想视口是320,而后缩放比是0.5,因此最终的布局viewport是640。移动端大神PPK曾经说initial-scale是理想视口与视觉视口之比,但我感受既然设置了initial-scale,咱们的视觉视口与布局视口就相等了,因此咱们在计算时用的document.body.clientWidth,用window.innerWidth(视觉视口)其结果也同样,只不过是为了给你们演示。最后根据咱们计算出来的html的基值能够在css编码中还原视觉稿的真实宽高。
好比咱们拿到一个iphone6的高清设计稿(750×1334),在psd中量出一个div宽为750,如何转换为rem单位呢?
公式—— rem = px / 基值
经过flexible.js计算根html的font-size为75px,因此咱们能够这样写css
div {
width: 10rem;
}
转换成html就是这样
width: 10rem; // -> 750px
最后由于dpr为2,scale了0.5,因此手机屏幕上的真实宽度为375px,就刚恰好。试想一下,假如咱们不用initial-scale=0.5,那样咱们得在这个设计稿上量完div后,再口算除以2,才会写width=375px,想一想就累。
最后说一下字体的设置吧,这个比较特殊。如下分别是咱们如今作的项目(今日十大)中不一样设备的字体展现,能够清楚地看到任何手机屏幕上字体大小都要统一。
固然为了方便,咱们使用sass作了一次遍历。在不一样的data-dpr中遍历font-size(这里咱们就看到了以前html中data-dpr的做用了)