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 设备物理像素和设备独立像素比,即,window.devicePixelRatio = 物理像素/css像素,是指在理想布局宽度,使用多少个物理像素来渲染一个设备独立像素。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)来进行布局视口设置,视觉视口是指用户当前看到的区域,理想视口是屏幕分辨率的值,通过设置 实现。参考文章
https://www.quirksmode.org/mobile/viewports2.html
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: ['*'],
}),
],
}
}
HTML5 移动端自适应方案与踩坑
使用Flexible实现手淘H5页面的终端适配
lib-flexible
实现原理:
(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))
具体来讲就是
(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以比例布局为主,目的是适应不同的浏览器窗口大小。
存在的问题
https://juejin.im/post/5c8870c1e51d453ce668cc7f
http://dwbbb.com/blog/flexible/
这个方案存在的问题:
viewport
放大为device-width
的dpr倍数,然后缩小1/dpr倍显示;border-image(svg绘制)设为一个一半透明一半显示的图片;媒体查询配合伪元素,为伪元素设置1px的边框,然后缩小1/dpr倍显示;8. 富文本编辑器适配问题
需要注意的是,浏览器有最小字体限制,PC上是12px,移动端是8px。并且大屏小屏隐含的意思是手机屏幕的分辨率不一样,也就是dpr不一样。
移动端H5开发,在不同分辨率,不同手机屏幕下会遇到的问题
tramsform: scaleY(.5)
缩小0.5倍来达到0.5px的效果,同时会导致字体大小和页面布局被缩放拉勾网:文字流式,控件弹性,图片等比缩放
网易新闻:rem+vw+媒体查询(这里可以放一个截图)
font-size需要额外的媒介查询,且font-size不能用rem,当deviceWidth大于设计稿的横向分辨率时,html的font-size始终等于横向分辨率/body元素宽。
淘宝
控件弹性指的是,navigation、cell、bar等适配过程中垂直方向上高度不变;水平方向宽度变化时,通过调整元素间距或元素右对齐的方式实现自适应。这样屏幕越大,在垂直方向上可以显示更多内容,发挥大屏幕的优势。
按照上述默认适配规则,大中小三种屏幕显示效果均相同。有时候想在大屏幕显示更多内容,需要设计出特殊适配效果。比如App store首页焦点图,从iPhone 6适配到iPhone 6 plus时焦点图尺寸和排版做了特殊处理。底下应用列表也从一排3+个变成一排4+个,真正实现了大屏幕显示更多内容的理念。这些就需要设计师给出相应设计稿。
https://www.zhihu.com/question/25308946
进阶部分:
window.devicePixelRatio
获取到当前设备的dpr,所以iphone6给的视觉稿大小是(*2)750✖1334兼容性这一部分
https://3g.163.com/touch/news?version=v_standard
查找各个方法的兼容性网址
视口:浏览器中用于呈现网页的区域叫视口,移动端的视口通常指的是布局视口
视口单位区别于%单位,视口单位是依赖于视口的尺寸,根据视口尺寸的百分比来定义,%单位则是依赖元素的祖先元素。
就主流的响应式布局、弹性布局来说,通过 Media Queries 实现的布局需要配置多个响应断点,而且带来的体验也对用户十分的不友好:布局在响应断点范围内的分辨率下维持不变,而在响应断点切换的瞬间,布局带来断层式的切换变化。
而通过采用rem单位的动态计算的弹性布局,则是需要在头部内嵌一段脚本来进行监听分辨率的变化来动态改变根元素字体大小,使得 CSS 与 JS 耦合了在一起。
通过利用视口单位实现适配的页面,是既能解决响应式断层问题,又能解决脚本依赖的问题。
实现原理:
transform
属性scale实现,物理像素线(也就是普通屏幕下 1px ,高清屏幕下 0.5px 的情况)采用 transform 属性 scale 实现。
width: 10%; padding-top: 20%;
,则出现的是宽高比为1:2的矩形。实现vw实现移动端自适应,由于是利用视口单位实现的布局,依赖于视口大小而自动缩放,无论视口过大还是过小,它也随着视口过大或者过小,失去了最大最小宽度的限制。
结合rem单位来实现布局,rem 弹性布局的核心在于动态改变根元素大小,那么我们可以通过:
第一,做法二相对来说用户视觉体验更好,增加了最大最小宽度的限制;
第二,更重要是,如果选择主流的rem弹性布局方式作为项目开发的适配页面方法,那么做法二更适合于后期项目从 rem 单位过渡到 vw 单位。只需要通过改变根元素大小的计算方式,你就可以不需要其他任何的处理,就无缝过渡到另一种CSS单位,更何况vw单位的使用必然会成为一种更好适配方式,目前它只是碍于兼容性的支持而得不到广泛的应用。
如果要做到弹性布局,当然是推荐vw,如果仍然希望达到更高兼容性,则应该使用post css把它处理成rem。
一个好的弹性布局,应该能够根据实际设计语义,综合运用以下css/html特性:
Media Query正常流排版(display:inline-block、display:block、display:inline)最大最小宽高(min-width、max-width)多栏(column-width等)flexbox(display:flex)相对单位(% em rem vw wh wmax vmin)图片集(srcset、imageset)SVG
rem 在封装组件时会和html字体耦合
使用vm/vh就完全的解耦了
calc也基本可以使用了
https://yanhaijing.com/css/2017/09/29/principle-of-rem-layout/
目前的理解是,rem方案兼容性好,但是不是纯css适配方案,需要引入js文件,要保证加载体验,需要头部内联,为了保证实时性,需要多个浏览器变化事件监测,并且字体的大小需要分别设置。vw方案在屏幕较大或较小时体验不好,无法实现对屏幕最大最小限制,而rem可以通过控制html根元素的font-size最大值,解决这个问题。
width: 100%;
margin: 10px;
用calc()函数解决溢出问题
width: 800px;
width: calc(100% - (10 * 2)px)
margin: 10px;
同时要给出基于iphone6计算的样例,rem/vw等
https://www.zhihu.com/question/25308946
https://aotu.io/notes/2017/04/28/2017-4-28-CSS-viewport-units/index.html
https://yanhaijing.com/css/2017/09/29/principle-of-rem-layout/
响应式布局的常用解决方案对比
方法:使用百分比定义宽度,高度用px固定,根据可视区域和父元素的实时尺寸进行调整,尽可能适应各种分辨率,配合max-width和min-width属性控制尺寸流动范围过大或过小影响阅读。
缺点:
响应式设计是基于流式布局+弹性布局+媒体查询技术使用,使得一个网站同时适配多种设备和多个屏幕,让网站的布局和功能随用户的使用环境(屏幕大小、输出方式、设备/浏览器能力而变化),使其视觉合理,交互方式符合习惯。如使得内容区块可伸缩与自由排布,边距适应页面尺寸,图片适应比例变化,能够自动隐藏/部分显示内容,能自动折叠导航和菜单,放弃使用像素作为尺寸单位,使用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 属性提供描述,描述有助于提高您的网站的可访问性,能提供语境给屏幕阅读器及其他辅助性技术。https://developers.google.com/web/fundamentals/design-and-ux/responsive/images?hl=zh-cn
srcse
和 sizes
来提供更多额外的图像资源,帮助浏览器选择合适的图片
浏览器的查询过程:1.查看设备宽度; 2.检查sizes列表中哪个媒体条件是第一个为真;3.查看给予该媒体查询的槽大小;4.加载srcset列表中引用的最接近所选的槽大小的图像
如果想要针对不同像素密度的屏幕,使用的srcset属性用来指定多张图像,适应不同像素密度的屏幕。它的值是一个逗号分隔的字符串,每个部分都是一张图像的 URL,后面接一个空格,然后是像素密度的描述符。
srcset属性给出了三个图像 URL,适应三种不同的像素密度。图像 URL 后面的像素密度描述符,格式是像素密度倍数 + 字母x。1x表示单倍像素密度,可以省略。浏览器根据当前设备的像素密度,选择需要加载的图像。如果srcset属性都不满足条件,那么就加载src属性指定的默认图像。
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中定义的一个容器标签,内部使用
百分比方案的字体设置如果按照百分比来定义,当屏幕宽度过大时,需要另外设置自适应方案,如根据em设置字体大小
rem方案和vw/vh方案的字体,在屏幕大小发生改变时,可以依据调整后的根元素字体大小和vw/vh值进行适配
响应式设计可以根据媒体查询给不同的分辨率指定不同的字体样式。
https://segmentfault.com/a/1190000006824046
calc()函数
calc()函数在声明css属性值时执行一些计算,可以用在如下场合 length/frequency/angle/time/number/integer,语法 : property: calc(expression)
,用一个表达式作为参数,用这个表达式的结果作为值。例 :
https://segmentfault.com/a/1190000006824046
这个是对字体的处理,后面要分开总结一下.
对图片的处理
移动端高清/多屏适配方案
https://juejin.im/post/5bc07ebf6fb9a05d026119a9