移动端适配(必须要知道的,亲测有效)

关于移动端适配(必须要知道的,亲测有效)

  • 一、各种单位概念理解
  • 二、移动,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

移动端适配(必须要知道的,亲测有效)_第1张图片

// 获取布局视口大小
document.documentElement.clientWidth / clientHeight

视觉视口:用户通过屏幕真实看到的区域。默认等于当前浏览器的窗口大小(包括滚动条宽度)

移动端适配(必须要知道的,亲测有效)_第2张图片

// 获取视觉视口大小
window.innerWidth / innerHeight

理想视口:网站页面在移动端展示的理想大小。在浏览器调试移动端时页面上给定的像素大小就是理想视口大小,它的单位正是设备独立像素。

screen.width / height // 获取理想视口大小

获取浏览器各种视口大小:
移动端适配(必须要知道的,亲测有效)_第3张图片

2、视口(viewport)适配(代码)

 元素 元数据信息。 告诉浏览器如何解析页面。

可以借助元素的viewport来帮助我们设置视口、缩放等,从而让移动端得到更好的展示效果
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />

移动端适配(必须要知道的,亲测有效)_第4张图片
为了在移动端让页面获得更好的显示效果,我们必须让布局视口、视觉视口都尽可能等于理想视口。
设置width=device-width就相当于让布局视口等于理想视口
设置initial-scale=1;就相当于让视觉视口等于理想视口
这时,1个CSS像素就等于1个设备独立像素,而且我们也是基于理想视口来进行布局的,所以呈现出来的页面布局在各种设备上都能大致相似。

3、rem单位适配

flexible方案

核心代码:

	// set 1rem = viewWidth / 100
    function setRemUnit() {
        var rem = docEl.clientWidth / 7.5
        docEl.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) { // pc
				if(clientWidth >= 1920) {
					docEl.style.fontSize = 1920 / 19.20 + "px";
				} else {
					docEl.style.fontSize = clientWidth / 19.20 + "px";
				}
			} 
		}
		resize();
		// reset rem unit on page resize
		window.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,可以理解为每个手机的宽度都是一样的,区别在于高度不一样。
移动端适配(必须要知道的,亲测有效)_第5张图片
有些单屏页面内容很少,在短屏上超出一屏、刚好,在长屏上只占页面顶上一小部分,布局会显得不协调。

长屏短屏布局适配方案:
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 resize
            window.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 定位的元素)。如下图:
移动端适配(必须要知道的,亲测有效)_第6张图片
安全区域:一个可视窗口范围,处于安全区域的内容不受圆角(corners)、齐刘海(sensor housing)、小黑条(Home Indicator)影响。

viewport-fit:iOS11 新增特性,苹果公司为了适配 iPhoneX 对现有 viewport meta 标签的一个扩展,用于设置网页在可视窗口的布局方式,可设置 三个值:
auto \ contain: 可视窗口完全包含网页内容(左图)。页面内容显示在safe area内
cover:网页内容完全覆盖可视窗口(右图)。页面内容充满屏幕。
移动端适配(必须要知道的,亲测有效)_第7张图片
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属性


4、使用svg可缩放矢量图

你可能感兴趣的:(移动端适配,移动端,前端)