英寸
:屏幕的物理大小,如电脑显示器的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())
移动端开发
1、在iOS、Android和React Native开发中样式单位其实都使用的是设备独立像素。
2、移动端开发中,UI给我们的原型图一般是基于iphone6(750*1334)的像素给定的。
3、为了适配所有机型,我们在写样式时需要把物理像素转换为设备独立像素。
web开发
web端用到最多的单位是px,即CSS像素。当页面缩放比例为100%时,一个CSS像素等于一个设备独立像素
页面的缩放系数 = 理想视口宽度 / 视觉视口宽度
视口共包括三种:布局视口
、视觉视口
和理想视口
,它们在屏幕适配中起着非常重要的作用。
布局视口
:在PC浏览器上,布局视口就等于当前浏览器的窗口大小(不包括borders 、margins、滚动条)。在移动端,布局视口被赋予一个默认值,大部分为980px
// 获取布局视口大小
document.documentElement.clientWidth / clientHeight
视觉视口
:用户通过屏幕真实看到的区域。默认等于当前浏览器的窗口大小(包括滚动条宽度)
// 获取视觉视口大小
window.innerWidth / innerHeight
理想视口
:网站页面在移动端展示的理想大小。在浏览器调试移动端时页面上给定的像素大小就是理想视口大小,它的单位正是设备独立像素。
screen.width / height // 获取理想视口大小
元素 元数据信息。 告诉浏览器如何解析页面。
可以借助元素的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个设备独立像素,而且我们也是基于理想视口来进行布局的,所以呈现出来的页面布局在各种设备上都能大致相似。
核心代码:
// set 1rem = viewWidth / 100
function setRemUnit() {
var rem = docEl.clientWidth / 7.5
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
<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>
rem(font size of the root element)是指相对于根元素的字体大小的单位。虽然不同手机的宽高尺寸不一样,但是设置了root font-size之后,每个手机的宽度都是等量的rem,可以理解为每个手机的宽度都是一样的,区别在于高度不一样。
有些单屏页面内容很少,在短屏上超出一屏、刚好,在长屏上只占页面顶上一小部分,布局会显得不协调。
长屏短屏布局适配方案:
1、css媒体查询,适当扩展间距让内容撑满更多高度
2、垂直定位尽量用%,不要用固定单位
@media screen and (orientation: portrait) {
/* 竖屏 */
}
@media screen and (orientation: landscape) {
/*横屏 css*/
}
通过绑定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);
通过比较页面的宽高,当页面的高大于等于宽时则认为是竖屏,反之则为横屏。
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页面。采用了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');
安全区域适配
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 () 后面
第一步:设置网页在可视窗口的布局方式 (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);
}
...
}
【问题】:在设备像素比大于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可缩放矢量图