关于移动端适配(必须要知道的,亲测有效)
- 一、各种单位概念理解
- 二、移动,web开发
- 三、移动端适配
- 1、视口(viewport)概念
- 2、视口(viewport)适配(代码)
- 3、rem单位适配
- flexible方案
- 竖屏、横屏、ipad、PC最全的适配JS
- 4、长屏短屏布局适配
- 5、横竖屏适配
- css媒体查询(CSS Media Queries)
- window.orientation旋转事件
- window.innerHeight/window.innerWidth
- 软键盘弹出-影响横竖屏判断的坑
- 强制横竖屏时遇到的坑
- 横竖屏切换,全屏背景的坑
- 在游戏接入中-H5隐藏某些模块
- 6、iphone适配
- 概念
- h5、小程序适配代码
- 7、常见适配问题
- 1px问题
- 图片模糊问题
移动端适配,在开发中经常会遇到的问题:
一、各种单位概念理解
英寸
:屏幕的物理大小,如电脑显示器的17、22,手机显示器的4.8、5.7等使用的单位都是英寸。一般说的屏幕的尺寸都是屏幕对角线的长度。1英寸 = 2.54 厘米
像素
:像素即一个小方块,它具有特定的位置和颜色。图片、电子屏幕(手机、电脑)就是由无数个具有特定颜色和特定位置的小方块拼接而成。像素可以作为图片或电子屏幕的最小组成单位。
分辨率
:屏幕分辨率(一个屏幕具体由多少个像素点组成。),图像分辨率(指图片含有的像素数。)。同一尺寸的屏幕、图片,分辨率越高,越清晰。
PPI
(Pixel Per Inch):每英寸包括的像素数。PPI越高,越清晰。
DPI
(Dot Per Inch):即每英寸包括的点数。DPI和PPI是等价的。
DIP或DP 设备独立像素
:用来告诉不同分辨率的手机,它们在界面上显示元素的大小是多少
dpr(device pixel ratio)设备像素比
:物理像素和设备独立像素的比值
//web
let dpr = window.devicePixelRatio;//css 使用媒体查询min-device-pixel-ratio区分
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){ }//React Native
PixelRatio.get()
web前端长度单位详解(px、em、rem、%、vw/vh、vmin/vmax、vm、calc())
二、移动,web开发
移动端开发
1、在iOS、Android和React Native开发中样式单位其实都使用的是设备独立像素。
2、移动端开发中,UI给我们的原型图一般是基于iphone6(750*1334)的像素给定的。
3、为了适配所有机型,我们在写样式时需要把物理像素转换为设备独立像素。
web开发
web端用到最多的单位是px,即CSS像素。当页面缩放比例为100%时,一个CSS像素等于一个设备独立像素
页面的缩放系数 = 理想视口宽度 / 视觉视口宽度
三、移动端适配
1、视口(viewport)概念
视口共包括三种:布局视口
、视觉视口
和理想视口
,它们在屏幕适配中起着非常重要的作用。
布局视口
:在PC浏览器上,布局视口就等于当前浏览器的窗口大小(不包括borders 、margins、滚动条)。在移动端,布局视口被赋予一个默认值,大部分为980px
// 获取布局视口大小
document.documentElement.clientWidth / clientHeight
视觉视口
:用户通过屏幕真实看到的区域。默认等于当前浏览器的窗口大小(包括滚动条宽度)
// 获取视觉视口大小
window.innerWidth / innerHeight
理想视口
:网站页面在移动端展示的理想大小。在浏览器调试移动端时页面上给定的像素大小就是理想视口大小,它的单位正是设备独立像素。
screen.width / height // 获取理想视口大小
获取浏览器各种视口大小:
2、视口(viewport)适配(代码)
<meta> 元素 元数据信息。 告诉浏览器如何解析页面。可以借助<meta>元素的viewport来帮助我们设置视口、缩放等,从而让移动端得到更好的展示效果
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
为了在移动端让页面获得更好的显示效果,我们必须让布局视口、视觉视口都尽可能等于理想视口。
设置width=device-width就相当于让布局视口等于理想视口
设置initial-scale=1;就相当于让视觉视口等于理想视口
这时,1个CSS像素就等于1个设备独立像素,而且我们也是基于理想视口来进行布局的,所以呈现出来的页面布局在各种设备上都能大致相似。
3、rem单位适配
flexible方案
核心代码:
// set 1rem = viewWidth / 100function setRemUnit() {var rem = docEl.clientWidth / 7.5docEl.style.fontSize = rem + 'px'}setRemUnit()
竖屏、横屏、ipad、PC最全的适配JS
<script type="text/javascript">// 设备区分 (安卓、火狐、平板、PC)var os = function() {var ua = navigator.userAgent,isAndroid = /(?:Android)/.test(ua),isFireFox = /(?:Firefox)/.test(ua),isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),isPC = !(/Android|webOS|iPhone|iPod|BlackBerry/i.test(ua));return {isTablet: isTablet,};}();(function (window, document) {function resize() {var docEl = document.documentElement;var clientWidth = docEl.clientWidth;var clientHeight = docEl.clientHeight;if (clientWidth < clientHeight) { // 手机竖屏docEl.style.fontSize = clientWidth / 7.5 + "px";} else { // 手机横屏 docEl.style.fontSize = clientHeight / 7.5 + "px";}if(os.isTablet) { //ipad if(clientWidth >= 1366) {docEl.style.fontSize = clientWidth / 1366 * 100 + "px";} else {docEl.style.fontSize = clientWidth / 1024 * 100 + "px";}}if (os.isPC) { // pcif(clientWidth >= 1920) {docEl.style.fontSize = 1920 / 19.20 + "px";} else {docEl.style.fontSize = clientWidth / 19.20 + "px";}} }resize();// reset rem unit on page resizewindow.addEventListener("resize", function () {resize()});window.addEventListener('pageshow', function (e) {if (e.persisted) {resize()}});}(window, document));
</script>
4、长屏短屏布局适配
rem(font size of the root element)是指相对于根元素的字体大小的单位。虽然不同手机的宽高尺寸不一样,但是设置了root font-size之后,每个手机的宽度都是等量的rem,可以理解为每个手机的宽度都是一样的,区别在于高度不一样。
有些单屏页面内容很少,在短屏上超出一屏、刚好,在长屏上只占页面顶上一小部分,布局会显得不协调。
长屏短屏布局适配方案:
1、css媒体查询,适当扩展间距让内容撑满更多高度
2、垂直定位尽量用%,不要用固定单位
5、横竖屏适配
css媒体查询(CSS Media Queries)
@media screen and (orientation: portrait) {/* 竖屏 */
}
@media screen and (orientation: landscape) {/*横屏 css*/
}
window.orientation旋转事件
通过绑定orientationchange旋转事件来判断横竖屏。
在 iOS 平台以及大部分 Android 手机都有支持这个属性,它返回一个与默认屏幕方向偏离的角度值:
0:代表此时是默认屏幕方向
90:代表顺时针偏离默认屏幕方向90度
-90:代表逆时针偏离默认屏幕方向90度
180:代表偏离默认屏幕方向180度
如下图所示:
在实际应用中,对于 iPhone 和大部分 Android 是没有180度的手机竖屏翻转的情况的,但是 iPad 是存在的。
function recordOrient() {if (window.orientation === 180 || window.orientation === 0) {//竖屏} else if (window.orientation === 90 || window.orientation === -90) {// 横屏}
}recordOrient();
window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function () {recordOrient();
}, false);
弹框型js完美方案:
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title></title>
<script type="text/javascript">(function (window, document) {function resize() {var docEl = document.documentElement;let clientWidth = docEl.clientWidth;let clientHeight = docEl.clientHeight;var k = 375;if (window.orientation === 180 || window.orientation === 0) {//竖屏} else if (window.orientation === 90 || window.orientation === -90) {//横屏// k = 320;}if (clientWidth <= k) { //竖屏docEl.style.fontSize = clientWidth / 750 * 100 + "px";} else { //横屏docEl.style.fontSize = k / 750 * 100 + "px";}}resize();// reset rem unit on page resizewindow.addEventListener("resize", function () {resize()});window.addEventListener('pageshow', function (e) {if (e.persisted) {resize()}});}(window, document));</script>
页面型完美解决方案:
(function (win, doc) {if (!win.addEventListener) return;var html = document.documentElement;function setFont() {var html = document.documentElement;var k = 750;html.style.fontSize = html.clientWidth / k * 100 + "px";}setFont();setTimeout(function () {setFont();}, 300);doc.addEventListener('DOMContentLoaded', setFont, false);win.addEventListener('resize', setFont, false);win.addEventListener('load', setFont, false);})(window, document);
window.innerHeight/window.innerWidth
通过比较页面的宽高,当页面的高大于等于宽时则认为是竖屏,反之则为横屏。
function detectOrient(){if(window.innerHeight >= window.innerWidth) {// 竖屏}else {// 横屏 }
}
detectOrient();
window.addEventListener('resize',detectOrient);
软键盘弹出-影响横竖屏判断的坑
在 Android 下,如果页面中出现软键盘弹出的情况(存在有 Input 的元素)时,页面有时会因为软键盘的弹出而导致页面回缩,即页面的宽度(竖屏时)或者高度(横屏时)被改变。
所以通过 CSS多媒体查询(CSS Media Queries) 、 window.matchMedia() 方法、window.innerWidth /window.innerHeight的页面宽高比对方法来实现的横竖屏判断方法,都会因此受到影响,出现判断失误的情况(vivo x9、华为p9、Samsung SCH-i699 机型,在竖屏时由于软键盘弹出导致页面高度小于宽度,被错误地判定为横屏)。
在这样的情况下,这几种方式也变得不可靠。
解决方案:类似的,在横屏判断中,给横屏一个最小判断宽度,低于那个宽度就还是判断为竖屏
CSS 媒体查询:给一个横屏的最小宽度,即使高度很小,通过宽度还是可以区分横竖屏
@media screen and (orientation: portrait) {/* 竖屏 */
}
@media screen and (orientation: landscape) and (min-width: 560px) {/* 横屏 */
}
vue 软件盘弹出 css解决方案:
<template><div ref="app" id="app"><keep-alive><router-view v-if="$route.meta.keepAlive"></router-view></keep-alive><router-view v-if="!$route.meta.keepAlive"></router-view><shine-landscape></shine-landscape> // 组件</div>
</template>
<style lang="less" scoped>
// 横屏 && 软键盘弹出横屏
@media screen and (orientation: landscape) and (min-width: 560px) {/deep/ .shine-landscape{display: block;}
}/style>
js 解决方案:
let sw = window.screen.width;let sh = window.screen.height;let cw = document.documentElement.clientWidth;if(cw == sw) {// 竖屏this.showShineLandscape = false}if(cw == sh) {// 横屏this.showShineLandscape = true}
强制横竖屏时遇到的坑
【问题】在 iOS 锁屏情况下,强制竖屏不成功
【方案】做一个横屏适配页,内容可以是提示开启锁屏并自动旋转手机屏幕
【注意】做适配时,如果发现在 iOS平台,横屏时页面左右会有默认安全区域空白,我们需要定义页面宽高等于手机视口宽高
@media screen and (orientation: landscape) and (min-width: 560px) {.page{/* 定义页面宽高等于手机视口宽高 */width: 100vw;height: 100vh;background: #fff;position: relative;}
}
【问题】做了强制横竖屏,绝对不能使用window.orientation判断来做横竖屏适配,因为 ios平台 旋转屏幕会改变横竖屏的值从而切换样式,但视图布局为始终保持强制的状态,这就会导致旋转屏幕时页面样式混乱。
【方案】:还是建议CSS多媒体查询,比较简单,解决缺陷的方式也简单
【问题】横竖屏旋转canvas需要重新绘制
【问题】页面中凡是 canvas绘制而成的局部视图,横竖屏旋转,样式不会跟着调整,
【方案】监听旋转屏幕事件,旋转后刷新重绘canvas部分
<template>
<div><template v-if="!isOrientationChange"><canvas id="my-canvas"/></template>
</div>
</template>data() {return {isOrientationChange: false}
},
created() {const _this = this;window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", function () {_this.isOrientationChange = true;setTimeout(()=> {_this.isOrientationChange = false;}, 100)}, false);
}
横竖屏切换,全屏背景的坑
【问题】ios安全区域页面留白,背景容器100%的漏洞
【解决方案】 背景容器设置100vw,vh
<div id="parent" class="parent"><div class="parent__banner"><img class="parent__img-portrait" src="./img/banner-portrait.png" alt="竖屏背景"><img class="parent__img-landscape" src="./img/banner-landscape.png" alt="横屏背景"></div>
</div>
.parent{/* vw,vh适配ios安全区域页面留白的漏洞 */width: 100vw;height: 100vh;background-color: #010c0f;position: relative;display: flex;align-items: center;justify-content: center;
}
.parent__banner{width: 100vw;height: 100vh;position: fixed;top: 0;left: 0;
}
.parent__img-portrait{display: block;width: 100%;height: 100%;object-fit:cover;
}@media screen and (orientation: portrait) {/* 竖屏 */.parent__img-landscape{display: none;}
}
@media screen and (orientation: landscape) {/*横屏 css*/.parent__img-landscape{display: none;}
}
在游戏接入中-H5隐藏某些模块
从游戏页面操作跳转到H5页面。采用了window.location的跳转方式,这里不能忘记手动携带该有的参数,否则从这里开始接下来所有页面都失去了判断的参数,项目中该去掉的模块就会出现
/*** 获得时间戳*/
function randomTimeStamp() {return parseInt(new Date().getTime() / 1000) + '';
}
/*** 检测flag* @param search*/
function checkFlag(search) {if (/[&|?](flag=[\d]+)/.test(search)) {search = search.replace(/[&|?](flag=[\d]+)/, (str) => {let splitStrs = str.split('=');return splitStrs[0] + '=' + randomTimeStamp();})} else {if (search) {search += '&flag=' + randomTimeStamp();} else {search += '?flag=' + randomTimeStamp();}} return search;
}
/*** 拼接地址* @param path: 路由* @param flag: 是否需要刷新*/
function locationUrl(path, flag = true) {let url = '';if (flag) {let search = checkFlag(location.search);url = location.protocol + '//' + location.host + location.pathname + search + '#/' + path;} else {url = location.protocol + '//' + location.host + location.pathname + location.search + '#/' + path;}window.location = url;
}this.locationUrl('Index');
6、iphone适配
概念
安全区域适配
iphone x\xr\xs\11 pro 取消了物理按键,改成底部小黑条,这一改动导致网页出现了比较尴尬的屏幕适配问题。
对于网页而言,顶部(刘海部位)的适配问题浏览器已经做了处理,所以我们只需要关注底部与小黑条的适配问题即可(即常见的吸底导航、返回顶部等各种相对底部 fixed 定位的元素)。如下图:
安全区域
:一个可视窗口范围,处于安全区域的内容不受圆角(corners)、齐刘海(sensor housing)、小黑条(Home Indicator)影响。
viewport-fit
:iOS11 新增特性,苹果公司为了适配 iPhoneX 对现有 viewport meta 标签的一个扩展,用于设置网页在可视窗口的布局方式,可设置 三个值:
auto \ contain: 可视窗口完全包含网页内容(左图)。页面内容显示在safe area内
cover:网页内容完全覆盖可视窗口(右图)。页面内容充满屏幕。
env() 和 constant()
:iOS11 新增特性,Webkit 的一个 CSS 函数,用于设定安全区域与边界的距离,有四个预定义的变量:
safe-area-inset-left:安全区域距离左边边界距离
safe-area-inset-right:安全区域距离右边边界距离
safe-area-inset-top:安全区域距离顶部边界距离
safe-area-inset-bottom:安全区域距离底部边界距离
【注意】:
1)网页默认不添加扩展的表现是 viewport-fit=contain,需要适配 iPhoneX 必须设置 viewport-fit=cover,不然 constant 函数是不起作用的,这是适配的必要条件
2)env() 是官方文档中提到将来要替换 constant (),目前还不可用。
为此目前我们可以看作 constant:针对iOS < 11.2以下系统,env:针对于iOS >= 11.2的系统
使用的时候 env() 必须写在 constant () 后面
h5、小程序适配代码
第一步:设置网页在可视窗口的布局方式 (viewport-fit=cover")
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
第二步:页面主体内容限定在安全区域内
body {padding-top: constant(safe-area-inset-top); padding-top: env(safe-area-inset-top); padding-bottom: constant(safe-area-inset-bottom);padding-bottom: env(safe-area-inset-bottom);
}
第三步:相对底部 fixed 定位的元素适配
1、fixed吸底 元素(bottom = 0)
<div class="footer ipx-padding-safe-area"></div>
// 或者
<div class="footer ipx-height-safe-area"></div>
// 或者
<div class="footer ipx-margin-safe-area"></div>
<div class="ipx-footer-safe-area"></div>
/* 通过加内边距 padding 扩展高度 */
.ipx-padding-safe-area{padding-bottom: constant(safe-area-inset-bottom);padding-bottom: env(safe-area-inset-bottom);
}
/* 通过计算函数 calc 覆盖原来高度 */
.ipx-height-safe-area{height: calc(1rem + constant(safe-area-inset-bottom));height: calc(1rem + env(safe-area-inset-bottom));
}
/* 通过加外边距 margin 增加底部距离 */
.ipx-margin-safe-area{margin-bottom: constant(safe-area-inset-bottom);margin-bottom: env(safe-area-inset-bottom);
}
/*
以上方式需要吸底条必须是有背景色的,因为扩展的部分背景是跟随外容器的,否则出现镂空情况。
还有一种方案就是,可以通过新增一个新的元素(空的颜色块,主要用于小黑条高度的占位),然后吸底元素可以不改变高度只需要调整位置
*/
.ipx-footer-safe-area{position: fixed;bottom: 0;width: 100%;height: constant(safe-area-inset-bottom);height: env(safe-area-inset-bottom);background-color: #fff;
}
2、fixed 非完全吸底元素(bottom ≠ 0),比如 “返回顶部”、“侧边广告” 等
<div class="back-top ipx-margin-safe-area">返回顶部</div>
第四步:使用 @supports 隔离兼容样式(可有可无)
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {/* 通过加内边距 padding 扩展高度 */.ipx-padding-safe-area{padding-bottom: constant(safe-area-inset-bottom);padding-bottom: env(safe-area-inset-bottom);}...
}
7、常见适配问题
1px问题
【问题】:在设备像素比大于1的屏幕上,我们写的1px实际上是被多个物理像素渲染,这就会出现1px在有些屏幕上看起来很粗的现象
【方案】:伪类 + transform 基于media查询判断不同的设备像素比对线条进行缩放
/* 边框线条 top\bottom */
.border-top, .border-bottom{position: relative;
}
.border-top:before{content: '';position: absolute;top: 0;height: 1px;width: 100%;background-color: #000;transform-origin: 50% 0%;
}
.border-bottom:before{content: '';position: absolute;bottom: 0;height: 1px;width: 100%;background-color: #000;transform-origin: 50% 0%;
}
@media only screen and (-webkit-min-device-pixel-ratio:2){.border-top:before, .border-bottom:before{transform: scaleY(0.5);}
}
@media only screen and (-webkit-min-device-pixel-ratio:3){.border_1px:before, .border-bottom:before{transform: scaleY(0.33);}
}
图片模糊问题
【问题】:我们平时使用的图片大多数都属于位图(png、jpg…)。在dpr > 1的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以相同的图片在dpr > 1的屏幕上就会模糊
【方案】:在dpr=2的屏幕上展示两倍图(@2x),在dpr=3的屏幕上展示三倍图(@3x)
1、媒体查询缩放(只适用于背景图)
@media only screen and (-webkit-min-device-pixel-ratio:2){.bg1{transform: scaleY(0.5);}
}
@media only screen and (-webkit-min-device-pixel-ratio:3){.bg1{transform: scaleY(0.33);}
}
2、image-set
.avatar {background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );
}
3、使用img标签的srcset属性
<img src="conardLi_1x.png"srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">
4、使用svg可缩放矢量图