移动端显示PC网页
- 布局视口 layout viewport
- 视觉视口 visual layout
- 理想视口 ideal layout
🤔 让我们把时间倒回移动设备刚出来的时候,一台手机设备要显示PC网页(各种布局尺寸写死了px)的话,要如何展示?
答案是,想办法等比例缩放网页,网页代码里写死的尺寸也需要被缩放,那么就不是简单的把容器缩放了,还需要对所有尺寸都缩放
因此px单位的尺寸在移动端都需要被重新计算,也就是移动端浏览器另外计算尺寸
🤔 那么怎么缩放呢?
无论PC网页多宽都缩放到浏览器宽度,比例是 PC宽度/移动端宽度?
- 一个PC网页最终的宽度也是未知的,只有PC浏览器运行了才知道
- 因此移动端浏览器也不知道PC网页的宽度
- 而假设直接把PC网页宽度设置为移动端设备的宽度的话
- 各种px布局尺寸会乱版
希望保持PC网页在PC浏览器那样的布局而只是缩放大小的话
- 移动端浏览器就需要设置一个假想的PC网页宽度
- 把PC网页放在这个宽度里布局后
- 再按照移动端宽度等比例缩放网页
👆 根据我们的思考设计
移动端浏览器需要对所有网页设置一个假想的网页宽度 根据设备的不同,有可能是768px
、980px
或1024px
等 通过浏览器api可以获取 document.documentElement.clientWidth/clientHeight
👇 这个宽度就是布局视口 layout viewport
再按移动端设备宽度等比例缩放网页 通过浏览器api可以获取 window.innerWidth/innerHeight
👇 这个移动端设备宽度就是 视觉视口 visual layout
另外假设我们只针对某个设备宽度开发一个网页 这个网页不需要移动端浏览器来重新计算我们的尺寸
这种开发就是针对一个移动设备的理想视口 ideal layout
进行的 👇 也就是理想视口
,不同于布局视口
和视觉视口
是个更概念的概念
虽然👆的设计帮我们自动适配了移动端显示PC网页
但是这并不是适合移动端的交互方式 直接缩放页面会导致页面字体变小,使得缩放后的页面显示效果都不会很理想 因此当我们网页是纯移动端时,并不希望用移动端浏览器默认为我们做重新计算单位 而是让它保持原尺寸并且网页宽度为移动端设备宽度,即使乱版
meta的viewport
👇 我们写一个干净的HTML
<!DOCTYPE html>
<html>
<head> <title>Document</title> </head>
<style>
.test{
width: 100px;
height: 100px;
background-color: #ccc;
}
</style>
<body> <div class="test">100px*100px</div> </body>
</html>
👆 切换移动端/PC端,会发现选中元素都显示是100px,但是视觉上尺寸切换明显不一样
这就是移动端为了显示PC网页所做的等比例缩放处理,并且是默认处理的
就像我们说的这并不是适合纯移动端网页的交互方式
💻 我们需要关闭浏览器默认对网页缩放的处理
- html的元数据元素mate -MDN
- meta中的name -MDN
- Using the viewport meta tag to control layout on mobile browsers (en-US)
了解了 👆 HTML
对 meta
元信息的设计 CSS 设备适配规范(CSS Device Adaptation specification
)定义了 name
为 viewport
元数据名称,并且这个属性目前仅在移动设备上生效
👇 这是 vscode
自动生成的 html
模板
<meta name="viewport" content="width=device-width, initial-scale=1.0">
👆 我们看到只针对移动端生效的 meta
配置了两个属性 width
initial-scale
viewport 中的 width
定义
viewport
的宽度,如果值为正整数,则单位为像素。 正整数,或字符串‘device-width’
这个宽度就是上面 移动端显示PC网页中的假想PC网页宽度 布局视口layout viewport
默认值根据设备的不同,有可能是768px、980px或1024px等
这个值的大小影响网页布局后缩放的尺寸
假设我们设置布局视口宽度为100px
里面的100px
方块就会铺满100px
,再进行缩放的效果将会是铺满的
👆 设置了 <meta name="viewport" content="width=100">
- 但是指定具体的宽度值,并不能适配所有的移动设备尺寸
- 这里有个特殊的值
‘device-width’
- 动态设置视口宽度为设备宽度
👆 这就是我们希望关闭浏览器默认对网页缩放的处理(就是缩放前后比例相等),这也是 vscode
为我们默认生成的模版中的值
效果上:不同的设备显示的方块大小将会一样,无论设备大小都不会缩放
viewport 中的 initial-scale
定义设备宽度(宽度和高度中更小的那个:如果是纵向屏幕,就是
device-width
,如果是横向屏幕,就是device-height
)与viewport
大小之间的缩放比例。0.0
和10.0
之间的正数 默认值1.0
👆 会在移动端自动缩放的基础上再缩放一次,默认值就是 1.0
不缩放
但是这个值是移动端浏览器处理的,当不设置的时候,有可能会被浏览器设置成别的缩放值,如PC浏览器设置里可以设置默认缩放比
因此保险起见,手动设置成 1.0
这也是 vscode
为我们默认生成的模版中的考虑
viewport 禁止移动端缩放网页
<meta name="viewport" content="user-scalable=no">
设置为 no,用户将无法缩放当前页面 浏览器设置可以忽略此规则;iOS 10 开始,Safari iOS 默认忽略此规则 默认为 yes
一般纯移动端网页也不希望被人缩放导致乱版
移动端显示纯移动端网页
经过 👆 关闭浏览器默认对网页缩放的处理 <meta name="viewport" content="width=device-width">
我们得到了一个无论设备大小都不会缩放的布局容器
但是我们希望不同移动端设备还是能根据设备不同有一点缩放处理
🤔 既然我们有能力设置决定缩放比例的视口宽度,为什么不把布局视口设置成
750/375px
,然后代码CSS写相应的布局视口下的尺寸呢
经过浏览器自动的缩放不是也能实现不同移动端适配的效果吗?
👆 我们把视口宽度设置为 375
,设置方块宽高为 187.5
,也就是屏幕的一半
拉伸设备宽度可以看到始终保持设备的一半,这不就是我们希望实现的不同设备适配了吗?
而且按照375编写的css尺寸,在PC浏览器上也会是希望的375尺寸(视口宽度只在移动端生效)
🤯 为什么啊?利用浏览器自动缩放实现不同设备不好吗?为什么要把移动端适配搞得这么复杂啊!
缺点是不能局部控制不缩放,比如1px希望所有设备都是1px不缩放(其他方案也是通过特殊处理1px不缩放的
那设置viewport的方案也可以特殊处理1px吧,比如说伪元素
移动端开发适配不同尺寸方案
不依赖浏览器自动对所有尺寸做的缩放,而是我们自己决定所有尺寸的缩放
因此着手的地方就是每个写尺寸的地方 写成rem/vw
这种动态的单位
vw
按视觉视口(设备宽度)为375px
,那么 1vw = 3.75px
,这时UI给定一个元素的宽为75px(设备独立像素),我们只需要将它设置为 75 / 3.75 = 20vw
设计稿总宽度px / 100vw = 目标样式px / 得出的vw
得出的vw = 目标样式px / (设计稿总宽度px / 100vw)
👇 scss
@vv = 375/100
@function vw($px) {
@return ($px / @vv) vw;
}
.class-name{
width: vw(187.5);
}
👇 css
root {
--vv : calc(375 / 100); /* 不能写单位 */
}
.class-name{
width: calc(187.5vw / var(--vv)); /* 要写单位vw */
}
rem
把设计稿总宽度375,分为 100rem 则样式css187.5px,占50rem
设计稿总宽度px / 100 = 目标样式px / 得出的rem
得出的rem = 目标样式px / (设计稿总宽度px / 100)
👆 其实和vw的做法相同,需要编写css函数来计算
👇 另外还要做的是把1rem对应的尺寸得出来
设备宽度/100 = 1rem = 根元素font-size
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" >
</head>
<style>
* {
padding: 0;
margin: 0;
--vv : calc(375 / 100);
}
body{
font-size: 16px; /* 还原文字默认大小 */
}
.test{
width: calc(187.5rem / var(--vv));
height: 187.5px;
background-color: #ccc;
}
</style>
<script>
function setRem() {
const deviceWidth = window.innerWidth
const htmlEl = document.documentElement;
// 将设备宽度分为100份,作为1rem的大小 font-size
htmlEl.style.fontSize = (deviceWidth/100) + 'px';
};
// 第一次进入页面调用
setRem();
window.addEventListener('resize', setRem);
</script>
<body> <div class="test">187.5*187.5</div> </body>
</html>
因为1rem
对应多少由我们决定,因此我们可以让1rem
等于设计稿的``1px
可以想办法让开发直接写rem
,而减少写css函数
的繁琐
如375
的设计稿,187.5px
的样式css,开发代码可以写成 width: 187.5rem
而不是 width: calc(187.5rem / var(--vv))
👇 设计稿375px
,1rem
为1px
设备宽度/设计稿宽度 = 1rem = 根元素font-size
function setRem() {
const deviceWidth = window.innerWidth
const htmlEl = document.documentElement;
// 将设备宽度氛围设计稿宽度的份数,使设计稿1px对应1rem font-size
htmlEl.style.fontSize = (deviceWidth/375) + 'px';
};
setRem();
window.addEventListener('resize', setRem);
1px问题
1px问题也就是有时候希望样式尺寸不缩放,如1px的边框在无论大小的设备上都是一样的大小
在vw
和rem
的方案里
- 不用
css函数
和rem
单位写的尺寸 - 用
px
写尺寸就实现了不缩放的尺寸
这个问题更多的是针对使用webpack编译插件
扫描所有px
转化为vw/rem
导致的全局缩放问题
这些插件都有相应的配置项,设置如大于2px
才转化为vw/rem
🤔 问题是如果用的viewport width=设计稿宽度
,利用移动端浏览器自动缩放的方案
所有px都会被自动缩放,没办法局部设置不缩放
0.5px问题
0.5px
问题不属于移动端适配的问题,这里作为题外话
设计稿中有时会有
- 750设计稿1px
- 375设计稿0.5px
👆的需求
而浏览器不支持1以下的px,有些浏览器会当作1px
显示,有些浏览器会不显示
因此假如要实现这种0.5px
需要绘制不被缩放的1px
,利用css的缩放,缩小一半来实现
物理像素
名词
- 抽象像素 - 代码CSS像素
- 设备物理像素 - 宽高像素值(随设备不同变化)
- 设备独立像素 - CSS1px对应的物理像素(随设备不同变化)
代码css设置的1px并不是真实的设备上的1像素 而是运行时根据设备的物理像素和CSS像素的比例计算出来的物理像素,如 显示物理像素 = 代码CSS像素 * (设备物理像素和设备独立像素的比例)
👇 PC浏览器调试模式下,iphone6的设备尺寸
这里的375px并不是iphone6的物理像素,而是设备独立像素 而查询到设备物理像素是750px 也就是1个CSS像素对应2个物理像素 👆 这就是我们要的比例
而浏览器有提供相应的api给我们获取 devicePixelRatio -MDN
返回当前显示设备的物理像素分辨率与CSS 像素分辨率之比 此值也可以解释为像素大小的比率:一个 CSS 像素的大小与一个物理像素的大小 简单来说,它告诉浏览器应使用多少屏幕实际像素来绘制单个 CSS 像素
console.log(window.devicePixelRatio)
输出了2,即375对应750物理像素
👇 常见的设备像素比:
- 普通密度桌面显示屏:devicePixelRatio = 1
- 高密度桌面显示屏(Mac Retina):devicePixelRatio = 2
- 主流手机显示屏:devicePixelRatio = 2 or 3
决定我们使用几倍图会清晰
lib-flexible.js
1. 🔧 工具库代码结构上是立即执行函数IIFE
(function flexible (window, document) {
// ...
}(window, document))
👆 我们可以马上联想到其他直接通过<script>
标签引入的第三方库
但是这里的 window
、document
参数没必要传递吧 如模块化文章中,参数一般用于在UMD
中区分全局变量环境使用 也就是传递进一个this的话,适合这样传
但是这个🔧工具函数的使用场景只有浏览器 👇 感觉用普通立即执行函数就可以了
(()=>{
// ...
})()
🤔 为什么要用立即执行函数,引入一个js不是本来就会立即执行吗?
因为直接引入的js作用域是全局的,会造成变量污染,用闭包包住后,变量不会影响其他js
2. 立即设置1rem的font-size并且监听窗口事件
var docEl = document.documentElement
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
window.addEventListener('resize', setRemUnit)
3. 还原默认字体大小
// adjust body font size
function setBodyFontSize () {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
4. 处理跳转第三方网页调整大小后返回不触发重新计算问题-监听返回
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
5. 判断设备支不支持0.5px
👇 设备像素比 大于等于2 并且 两条0.5px占文档流1px偏移量
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// 测试 0.5px supports
if (dpr >= 2) { // 设备像素比大于等于2
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
// 两条0.5px占文档流1px偏移量
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines') // 往全局dom添加一个 `hairlines` 的 className
}
docEl.removeChild(fakeBody)
}
👆 只是往全局dom添加了一个 hairlines
的 className
🤔 TODO: 并不会做什么操作,这个className要用来做什么
设置一个css变量不是更好用吗? 如 --hair-unit = 0.5 or 1
,使用时设备支持则显示 0.5px
不支持则显示 1px