移动端Web页面适配方案
macmakeup699
8年前
<p>移动端Web页面,即常说的H5页面、手机页面、webview页面等。</p> <p>手机设备屏幕尺寸不一,这里总结的是针对移动端设备的页面,设计与前端实现怎样做能更好地适配不同屏幕宽度的移动设备。</p> <h2>适配的效果</h2> <p>引用一文章的描述:</p> <p>在不同尺寸的手机设备上,页面“相对性的达到合理的展示(自适应)”或者“保持统一效果的等比缩放(看起来差不多)”。</p> <h2>概念理解</h2> <h3>viewport视口</h3> <p>viewport是严格的等于浏览器的窗口。</p> <p>获取viewport的尺寸:document. documentElement. clientWidth/Height。</p> <p>不缩放的视口设置:</p> <pre> <code class="language-javascript"><meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"></code></pre> <h3>物理像素(physical pixel)</h3> <p>物理像素又被称为设备像素,他是显示设备中一个最微小的物理部件。每个像素可以根据操作系统设置自己的颜色和亮度。所谓的一倍屏、二倍屏(Retina)、三倍屏,指的是设备以多少物理像素来显示一个CSS像素,也就是说,多倍屏以更多更精细的物理像素点来显示一个CSS像素点,在普通屏幕下1个CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素对应的却是4个物理像素。</p> <h3>CSS像素</h3> <p>CSS像素是一个抽像的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。一般情况之下,CSS像素称为与设备无关的像素(device-independent pixel),简称DIPs。</p> <h3>设备像素比dpr(device pixel ratio)</h3> <p>设备像素比简称为dpr,其定义了物理像素和设备独立像素的对应关系。它的值可以按下面的公式计算得到:</p> <p>设备像素比 = 物理像素 / 设备独立像素</p> <p>也就是说,二倍屏的dpr是2, 三倍屏是3。</p> <p>在JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr。而在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)。</p> <p>viewport的scale和dpr是倒数</p> <h3>屏幕密度PPI(pixel per inch)</h3> <p>屏幕密度是指一个设备表面上存在的像素数量,它通常以每英寸有多少像素来计算(PPI)。</p> <h3>设备独立像素dip或dp</h3> <p>dip或dp,(device independent pixels,设备独立像素)与屏幕密度有关。dip可以用来辅助区分视网膜设备还是非视网膜设备。</p> <h3>rem(CSS单位)</h3> <p>font size of the root element.</p> <p>相对于根元素<html>的font-size计算,因此可以通过设置根元素的font-size使得以rem为单位的元素在不同终端上以相对一致的视觉效果呈现。</p> <table> <thead> <tr> <th>设 备</th> <th>设备宽度/pt</th> <th>根元素font-size/px</th> <th>宽度/rem</th> </tr> </thead> <tbody> <tr> <td>iPhone5</td> <td>320</td> <td>16</td> <td>20</td> </tr> <tr> <td>iPhone6</td> <td>375</td> <td>18.75</td> <td>20</td> </tr> <tr> <td>i6 Plus</td> <td>414</td> <td> </td> <td>20</td> </tr> <tr> <td> </td> <td>360</td> <td> </td> <td>20</td> </tr> </tbody> </table> <p>根元素fontSize计算公式:Width/fontSize = baseWidth/baseFontSize</p> <p>其中,baseWidth, baseFontSize是选为基准的设备宽度及其根元素大小。</p> <p>缺点:</p> <ul> <li> <p>某些Android设备会丢掉 rem 小数部分。</p> </li> </ul> <h3>flex布局</h3> <p><a href="/misc/goto?guid=4958970342053652120" rel="nofollow,noindex">flex布局示意图</a></p> <h3>vm/vh(view-width, view-height)</h3> <p>视区宽度/高度为100vw/100vh</p> <p>视区指浏览器内部的可视区域大小:window.innerWidth/Height</p> <h3>upsampling/downsampling</h3> <p>DownSampling: 大图放入比图片尺寸小的容器中时,出现像素分割成就近色</p> <p>不同scale显示同一图片基本无问题;</p> <p>同一sacle,不同倍数图,存在色差(Downsampling)</p> <h2>实现方案</h2> <p>设计与前端协作方案:</p> <p>前端实现方案:淘宝手淘:</p> <ul> <li> <p>动态改写<meta name="viewport">标签</p> </li> <li> <p>给<html>元素添加data-dpr属性,并且动态改写data-dpr的值</p> </li> <li> <p>给<html>元素添加font-size属性,并且动态改写font-size的值</p> </li> </ul> <p>[淘宝手淘团队h5页面终端适配开源库:lib-flexible]()</p> <p>各种元素(文本、图片)处理方案参考:</p> <p>方案说明:</p> <p>通过一段JS代码根据设备的屏幕宽度、dpr设置根元素的data-dpr和font-size, 这段JS代码要在所有资源加载之前执行,建议做内联处理。</p> <h3>动态设置data-dpr和meta:viewport</h3> <pre> <code class="language-javascript">// 对iOS设备进行dpr的判断,对于Android系列,始终认为其dpr为1。 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; }</code></pre> <pre> <code class="language-javascript">// 动态改写meta:viewport标签 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); }</code></pre> <h3>px转rem的计算</h3> <pre> <code class="language-javascript">// 为了方便单位转换,写一个px转换rem的函数 // 淘宝手淘的方案里,i6(375pt)屏幕宽度为10rem,即font-size=75px, scale=0.5 因设计图为二倍图,$base-font-size=75px @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; }</code></pre> <pre> <code class="language-javascript">// 使用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; } }</code></pre> <h3>正文文字</h3> <ol> <li> <p>在所有设备大小一样,</p> </li> <li> <p>在更大的设备可以显示更多文字</p> </li> <li> <p>不希望出现13px,15px这样的尺寸,而是14px, 16px</p> </li> </ol> <pre> <code class="language-javascript">.a{ font-size:12px; } [data-dpr="2"] .a{ font-size: 24px; } [data-dpr="3"] .a{ font-size: 36px; }</code></pre> <p>为什么要设置viewport和dpr?</p> <p>适配高密度屏幕手机的px单位,使得不同设备下的px显示一样的长度。</p> <p>CSS像素和缩放、dpr都有关系:在普通手机上,.a字体设置为12px;</p> <p>在dpr是2的手机上,[data-dpr="2"].a字体为24px,又因为页面缩放50%,字体为还是12px。</p> <pre> <code class="language-javascript">// 不适合用rem适配的字体大小 @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);</code></pre> <h3>其他设置根元素fontSize的代码片段</h3> <pre> <code class="language-javascript">// JS设置viewport和rem, 整个片段也是以i6(750=375*2)为基准,屏幕宽度分为16rem var fixScreen = function() { var metaEl = doc.querySelector('meta[name="viewport"]'), metaCtt = metaEl ? metaEl.content : '', matchScale = metaCtt.match(/initial\-scale=([\d\.]+)/), matchWidth = metaCtt.match(/width=([^,\s]+)/); if ( !metaEl ) { // REM var docEl = doc.documentElement, maxwidth = docEl.dataset.mw || 750, // 每 dpr 最大页面宽度 dpr = isIos ? Math.min(win.devicePixelRatio, 3) : 1, scale = 1 / dpr, tid; docEl.removeAttribute('data-mw'); docEl.dataset.dpr = dpr; metaEl = doc.createElement('meta'); metaEl.name = 'viewport'; metaEl.content = 'initial-scale=' + ratio + ',maximum-scale=' + ratio + ', minimum-scale=' + scale; docEl.firstElementChild.appendChild(metaEl); var refreshRem = function() { var width = docEl.getBoundingClientRect().width; if (width / dpr > maxwidth) { width = maxwidth * dpr; } var rem = width / 16; docEl.style.fontSize = rem + 'px'; }; //... refreshRem(); } }</code></pre> <h2>小结</h2> <ul> <li> <p>对于多倍屏,通过rem为单位,viewport: scale=1/dpr来达到适合的显示;</p> </li> <li> <p>使用iPhone6(375pt, 750px)二倍设计图:750px为基准;</p> </li> <li> <p>切图使用三倍精度图,以适应三倍屏</p> </li> <li> <p>css单位综合使用:适配元素rem比例显示,正文字体宜用px+dpr缩放</p> </li> <li> <p>配合scss函数,简化px2rem转换,且易于维护(若需修改$base-font-size, 无需手动重新计算所有rem单位)</p> </li> </ul> <p> </p> <p>来自:https://segmentfault.com/a/1190000008767416</p> <p> </p>