HTTP05缓存控制

缓存控制

Expires

  • 设置过期时间 (http1.0的东西,现在用的少了)
1
2
3
// Wed, 30 Dec 2020 18:05:17 GMT
var date = new Date(Date.now()+1000*5).toGMTString()
res.setHeader('Expires',date)

存在问题是 : 服务器时间和你客户端时间不一致。

Pragma

  • 不要缓存,每次都重新拿
  • 优先级最高,一旦设置就不会走缓存
1
res.setHeader('Pragma','no-cache')

当 Pragma 和 Expires同时出现, Pragma优先级高

1
2
3
var date = new Date(Date.now()+1000*5).toGMTString()
res.setHeader('Expires',date)
res.setHeader('Pragma','no-cache')

Cache-Control

  • 设置过期时间戳
    • 解决了 Expires 时间不一致问题
1
2
// 10s后过期
res.setHeader('Cache-Control','max-age=10')

Cache-Control 为啥不能缓存 html

  • 访问 taobao
  • 你会发现 响应首页 的其他资源能被缓存,但是 这个首页html的响应头是
1
cache-control: max-age=0
  • 这是为了 解决网站改版的情况,浏览器自己设置的,如果缓存了,但是你走缓存就无法更新页面了

Cache-Control 其他值

1
2
3
4
5
6
// 代表不走缓存,即使浏览器里有缓存
cache-control: no-cache

// 代表不走缓存
// 资源可能中间有很多代理服务器(告诉代理服务器不要缓存)
cache-control: no-store

Last-Modified

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 当你访问服务器的时候,读取文件
// 获取文件有相关的信息,拿到——“修改时间”

// 这句代表你要重新找我拿,不走缓存,验证这个修改时间,如果变了 重新读取,没变返回304 从本地读取
// 不设置的时候,浏览器会缓存,就不会经过服务器验证了,直接从本地读取文件
res.setHeader('Cache-Control','no-cache')
// 如果没有这个字段,代表第一次请求
if(!req.headers['if-modified-since']){
// mtime 是修改时间
res.setHeader('Last-Modified',new Date(mtime).toGMTString())
res.writeHead(200,'OK')
res.end(data) // data是文件数据
}else{
let oldMtime = Date.parse(req.headers['if-modified-since'])
if(mtime>oldMtime){
// 文件修改了,重新读取吧
res.setHeader('Last-Modified',new Date(mtime).toGMTString())
res.writeHead(200,'OK')
res.end(data) // data是文件数据
}else{
// 文件没变,你用缓存吧
res.writeHead(304)
res.end()
}
}

案例1

1
2
3
4
5
6
7
8
9
10
11
12
13
res.setHeader('Cache-Control','no-cache')
res.setHeader('Last-Modified',new Date(mtime).toGMTString())
res.end(data) // data是文件数据

// no-cache 意思是 不要从本地缓存找 每次都找我要(每次都和服务器文件的mtime比对)
// 第一次请求的时候响应里设置 Last-Modified 和时间,并把文件返回
// 浏览器就会把这个时间缓存下来
// 之后的每次请求 都会带上 if-modified-since 的时间
// 但是发现 本地缓存信息里有 no-cache 就不会直接从本地读取,
// 而是带上 if-modified-since 和服务器文件的 mtime 比对,如果变了就重新读取,没变就走缓存


// no-cache 等价于 max-age=0

案例2,设置了 no-store

1
2
3
4
5
6
res.setHeader('Cache-Control','no-store')
res.setHeader('Last-Modified',new Date(mtime).toGMTString())
res.end(data) // data是文件数据

// no-store 意思是
// 你设置的 Last-Modified 响应报文不会被浏览器记录,每次都重新读取文件

ETag

  • 比如 a.css文件
1
2
3
body{
color:red;
}
  • 你修改了,修改时间变了
1
2
3
body{
color:red
}
  • 你又修改了,修改时间又变了
1
2
3
body{
color:red;
}

但是文件内容没变。但是 Last-Modified 只看时间,ETag代表 看文件内容是否变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 当你访问服务器的时候,读取文件
// 获取文件有相关的信息,生成——“md5值”

// 这句代表你要重新找我拿,不走缓存,验证这个 md5值,如果变了 重新读取,没变返回304 从本地读取
// 不设置的时候,浏览器会缓存,就不会经过服务器验证了,直接从本地读取文件

let md5 = crypto.createHash('md5');
res.setHeader('Cache-Control','no-cache')
// 如果没有这个字段,代表第一次请求
let oldETag = req.headers['if-none-match'];
if(!oldETag){
// 第一次请求
res.setHeader('Etag',md5.update(data).digest('base64'))
res.writeHead(200,'OK')
res.end(data) // data是文件数据
}else{
let newETag = md5.update(data).digest('base64')
if(newETag !== oldETag){
// 文件内容变了,重新读取吧
res.setHeader('Etag',newETag)
res.writeHead(200,'OK')
res.end(data) // data是文件数据
}else{
// 文件没变,你用缓存吧
res.writeHead(304)
res.end()
}
}

参考连接

HTTP04报文和http状态码

报文的内容

request 请求报文

  • 第一部分 请求行method path http版本号
  • 第二部分 K/V对的 请求首部
  • 第三部分 空行
  • 第四部分 请求体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET / HTTP/1.1 请求行
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

请求体

response 响应报文

  • 第一部分 请求行http版本号 状态码 状态码的解释
  • 第二部分 K/V对的 请求首部
  • 第三部分 空行
  • 第四部分 响应体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0xc2b78305000219a6
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Wed, 30 Dec 2020 15:49:03 GMT
Expires: Wed, 30 Dec 2020 15:49:03 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=116; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=33423_1467_33355_33306_33261_31253_33343_33313_33312_33311_33310_33309_26350_33308_33307_33389_33370; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1609343343346522650614030827221594282406
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
空行
响应体

HTTP状态码

  • 200 OK 成功
  • 204 No Content 请求成功了服务器做了处理了,但是不返回任何内容
  • 206 Partial Content 返回部分数据
  • 301 永久重定向,
    • 比如 当你访问aa.com ,重定向到bb.com,此时浏览器就会记录缓存,下次你输入 aa.com 会直接 跳转到 bb.com
  • 302 Found 临时重定向
  • 303 See Other
  • 304 Not Modified 缓存相关
  • 307 也是临时重定向
  • 400 bad request
  • 401 Unauthorized 未授权,没权限
  • 403 forbidden 被服务器拒绝了
  • 404 not found
  • 500 Internal Server Error
  • 503 Service Unavailable 服务器处理不过来了

重定向

302情况 访问 aa.com重定向
1
2
3
// 服务器端
statusCode = 302
setHeader('location','www.baidu.com')
301情况 访问 aa.com重定向
1
2
3
// 服务器端设置响应
statusCode = 301
setHeader('location','www.baidu.com')

注意!chrome调试的时候 不要勾选disable cache

响应类型和请求类型

1
2
3
4
5
// 响应类型
Content-Type: text/html;charset=utf-8

// 请求类型
Content-Type: application/x-www-form-urlencoded;charset=utf-8

HTTP03http常用方法

1.常见HTTP方法有哪些?使用场景分别是什么?
2.GET与POST有什么区别?
3.什么是RESTful API?

常见Http方法

  • GET
  • POST
  • PUT
  • HEAD
  • DELETE
  • OPTIONS
  • CONNECT

RESTful API

表现层状态转换(英语:Representational State Transfer,缩写:REST),API设计规范,HTTP方法与对应数据库增删改查对应

  • GET:读取(Read)
  • POST:新建(Create)
  • PUT:更新(Update)
  • PATCH:更新(Update),通常是部分更新
  • DELETE:删除(Delete)

范例

  • GET /books , 列出所有的图书
  • POST /books , 创建一本图书
  • PUT /books, 批量更新图书信息
  • DELETE /books, 删除所有图书
  • GET /books/10, 获取10号图书详细信息
  • PUT /books/10, 更新10号图书
  • PATCH /books/10, 更新10号图书
  • DELETE /books/10, 删除10号图书
PUT和PATCH有什么差别?

对于

1
2
3
4
{
"username": "xxx"
"email": "xxx@xxxx.com"
}

PUT修改传参,需包含全部参数

1
2
3
4
{
"username": "xxx"
"email": "xxx@vip.qq.com"
}

PATCH修改传参,只需要传递需要修改的参数

1
2
3
{
"email": "hunger@vip.qq.com"
}

另外推荐在URL中强制加入版本号,如

GET /v1/books

Form表单支持哪些方法

form表单只支持 post 和get。但是可以通过变通的方法,让服务器知道你本意是想做什么。

1
2
3
<form action="/books" method="POST">
<input type="hidden" name="_method" value="PUT">
</form>

HTTP-持久连接

1
2
3
4
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
...

一个请求的过程是

  • step01 三次握手 建立连接
  • step02 HTTP request / response
  • step03 四度挥手 断开连接

每次请求都经历这些过程,握手很频繁。损耗时间

通过添加 Connection: keep-alive 代表建立连接后,你们以后别再次握手挥手了,直接发请求就行了,等都ok了 在挥手停下来

HTTP02三次握手四度挥手

三次握手

A:我准备好了你准备好了吗,收到请回答。
B:收到收到,我也准备好了,收到请回答。
A:收到收到

粗略理解

  • step01 A询问B服务器,并带着序号seq=x的问题
    • SYN=1,seq=x
  • step02 B用 ack=x+1响应seq=x的问题,同时问了A一个问题 SYN=1,seq=y
    • ACK=1,ack=x+1,SYN=1,seq=y
  • step03 A回答B的问题,同时当前问题的序号加1
    • ACK=1,ack=y+1,seq=x+1

SYN 是啥?

  • 就是一个确认报文
  • SYN=1 代表向对方询问。 我已经OK了,我要去问你了,同时要加一个序号 seq = x,

序号 seq

保证 每次询问的问题都会有一个seq,这样即使 对方没有反应,下次询问的时候又有新的序号,不会发生错乱

ACK=1 代表回答

ack=x+1 代表 回答你刚刚seq=x的问题

TCP规定SYN=1时不能携带数据,但要消耗一个序号,因此声明自己的序号是 seq=x

四步挥手

1.客户端发送一个 FIN ,告诉服务器想关闭连接。
2.服务器收到这个 FIN ,发回一个 ACK。(关闭需要时间,此时只是回应知道这个事了)
3.服务器通知应用程序关闭网络连接,应用程序关闭后通知服务器。服务器发送一个 FIN 给客户端 。 (真的关闭后,再次发送FIN给客户端,同时告诉你也关闭吧,我已经关了)
4.客户端发回 ACK 报文确认。

为什么挥手要四步

这是因为服务端的 LISTEN 状态下的 SOCKET 当收到客户端建立连接请求的SYN 报文后,它可以把 ACK 和 SYN ( ACK 起应答作用,而 SYN 起同步作用)放在一个报文里来发送。但关闭连接时,当服务器收到客户端的 FIN 报文通知时,服务器只能发一个回应报文ACK:“哦,我知道了”,然后通知应用程序。应用程序完成全部数据发送并确定可以终止了,服务器才能发送FIN告诉客户端可以真正断开连接了。所以这一步ACK报文和FIN报文需要分开发送,因此多了一个步骤。

TCP报文格式

  • ACK : TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1。
  • SYN(SYNchronization) : 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此, SYN置1就表示这是一个连接请求或连接接受报文。
  • FIN (finis)即完,终结的意思, 用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。

参考链接

HTTP01网络各种层

网络基础

使用HTTP协议 访问Web

  • 打开浏览器输入 taobao.com
  • 套壳应用里内嵌网址
  • 命令行里 访问 www.baidu.com

HTTP的版本

  • HTTP/1.0 最早的
  • HTTP/1.1 主流的
  • 后续还有 HTTP/2.0

URI 和 URL区别

  • URI 统一资源标识符
    • 某一互联网资源,宽泛的范围
      • ftp上的某个资源
      • http服务器上的一个 txt文件
      • 等等
  • URL 统一资源定位符 (网址)

OSI7层模型和TCP/IP4层模型

OSI模型 (7层模型)

记忆口诀—— 应表会传网数物

  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层

记忆一下即可,已被 TCP/IP四层模型取代

TCP/IP四层模型 (应传网数)

来个例子 豪门之间的通信

  • 豪门望族,规矩多,还得有礼仪

  • step01 写信的规则 (应用层)

1
2
3
4
5
6
尊敬的xxx:
代表我和我家和您进行问候!
邀请xxx时来一起 high

您的朋友:
yyy
  • step002 把信给管家 (传输层) ,这种小事交给管家去做
    • 豪门a的管家给豪门b的管家通通信
      • A我们家要给你们家发信了
      • B收到,你发吧
      • A收到,我发了
  • step003 邮差 (网络层)
    • B家族的地址和小区名称
  • step004 邮筒 (数据链路层)
    • 信的大小不能超过 xx寸,太大了邮筒放不进去,还要贴邮票

应用层

应用层是大多数普通与网络相关的程序为了通过网络与其他程序通信所使用的层。这个层的处理过程是应用特有的;数据从网络相关的程序以这种应用内部使用的格式进行传送,然后被编码成标准协议的格式。每一个应用层协议一般都会使用到传输层协议TCP和UDP协议之一:

运行在TCP协议上的协议:

  • HTTP(80端口),主要用于普通浏览。
  • HTTPS(443端口),HTTP协议的安全版本。
  • FTP(20和21端口),由名知义,用于文件传输。
  • POP3(110端口),收邮件用。
  • SMTP(25端口),用来发送电子邮件。
  • SSH(22端口),用于加密安全登陆用。

运行在UDP协议上的协议:

  • DHCP(67端口,动态主机配置协议),动态配置IP地址。

其他:

  • DNS(Domain Name Service,域名服务),用于完成地址查找,邮件转发等工作(运行在TCP和UDP协议上)。
  • SNMP(Simple Network Management Protocol,简单网络管理协议),用于网络信息的收集和网络管理。
  • ARP(Address Resolution Protocol,地址解析协议),用于动态解析以太网硬件的地址。

传输层

解决诸如端到端可靠性(数据是否已经到达目的地?)和保证数据按照正确的顺序到达这样的问题。TCP、UDP都是传输层协议。

网络层

解决在一个单一网络上传输数据包的问题。IP协议是网络层协议。

数据链路层

它是数据包从一个设备的网络层传输到另外一个设备的网络层的方法。这个过程能够在网卡的软件驱动程序中控制或者专用芯片中控制。这将完成如添加报头准备发送、通过实体介质实际发送这样一些数据链路功能。另一端,链路层将完成数据帧接收、去除报头并且将接收到的包传到网络层。

粗略理解 http协议

  • 打开 chrome 输入 baidu.com
  • 调试模式 network 选项
  • 选择一个http请求 右键 copy request headers/ response headers

request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PSTM=1587438853; BIDUPSID=1E44DAEF1B34E707E2BCE796151A30D5; BAIDUID=212F1EB08E8B84AD626014F0EED3983F:SL=0:NR=10:FG=1; BD_UPN=123253; MCITY=-131%3A; BDUSS=taSllIdEtBckQtZWhkUVl5RVA5LUVzcERMWWJDcThRQ0w0dWl6RGhwMHVSNjFmRVFBQUFBJCQAAAAAAAAAAAEAAAA~F2-l1MbLtezh0razwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC66hV8uuoVfY0; BDUSS_BFESS=taSllIdEtBckQtZWhkUVl5RVA5LUVzcERMWWJDcThRQ0w0dWl6RGhwMHVSNjFmRVFBQUFBJCQAAAAAAAAAAAEAAAA~F2-l1MbLtezh0razwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC66hV8uuoVfY0; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; __yjs_duid=1_0f057bc5e273f735bb3b90c9f7fb3bf91608867444564; BDSFRCVID_BFESS=4iDOJeC62xkxsV7rg5g6KwgregKaGx6TH6aokUReTbdIYJeK4QwiEG0PDf8g0Ku-yxYDogKK0mOTHv8F_2uxOjjg8UtVJeC6EG0Ptf8g0M5; BDSFRCVID=u_-OJeC62CWHCSOrgOLXKwgreU1oMPQTH6aoFxh6Z3kqjmbLtEppEG0P8M8g0Ku-8Uu8ogKK0mOTHv8F_2uxOjjg8UtVJeC6EG0Ptf8g0M5; H_BDCLCKID_SF=fRPD_Dt5f-5_jJ7kqtbSMttfqx6betJyaR3tM56vWJ5TMCoJ3U7T0U4XbPnrXUnf55r--CjOBxJGShPC-tnP2-vBWGbWb4TLKeJJK4JM3l02VKnEe-t2ynQDebotqPRMW20jWl7mWPLWsxA45J7cM4IseboJLfT-0bc4KKJxbnLWeIJEjj6jK4JKjG0eJjvP; H_BDCLCKID_SF_BFESS=fRPD_Dt5f-5_jJ7kqtbSMttfqx6betJyaR3tM56vWJ5TMCoJ3U7T0U4XbPnrXUnf55r--CjOBxJGShPC-tnP2-vBWGbWb4TLKeJJK4JM3l02VKnEe-t2ynQDebotqPRMW20jWl7mWPLWsxA45J7cM4IseboJLfT-0bc4KKJxbnLWeIJEjj6jK4JKjG0eJjvP; delPer=0; BD_CK_SAM=1; PSINO=2; BAIDUID_BFESS=8A88B5E71009B0079D8E6B45E8F33721:FG=1; BD_HOME=1; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; Hm_lvt_aec699bb6442ba076c8981c6dc490771=1607495989,1607755061,1608720675,1609259205; Hm_lpvt_aec699bb6442ba076c8981c6dc490771=1609259205; COOKIE_SESSION=30608_8_9_5_53_38_0_2_9_6_1_1_0_0_2_4_1609228598_1609144332_1609259204%7C9%2398408_356_1609144328%7C9; H_PS_PSSID=33423_1467_33355_33306_33261_31253_33343_33313_33312_33311_33310_33309_26350_33308_33307_33389_33370; H_PS_645EC=fc05%2B%2BlWgGb0cU%2B%2BbxUlUMSOqIsEiSUpRrm1pdbAFhR7A8t6VjcWGqbPXM8TdxnFox3C; sug=0; sugstore=0; ORIGIN=0; bdime=0; BA_HECTOR=ak2la1048h81a4a02c1fup8b50q

response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0xc2b78305000219a6
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Wed, 30 Dec 2020 15:49:03 GMT
Expires: Wed, 30 Dec 2020 15:49:03 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=116; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=33423_1467_33355_33306_33261_31253_33343_33313_33312_33311_33310_33309_26350_33308_33307_33389_33370; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1609343343346522650614030827221594282406
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked

移动端-004-移动端常见问题

移动端常见问题

300ms延迟

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--
<meta name="viewport" content="width=device-width">
-->
</head>
<body>
<div id="delay">click有延迟 </div>
<div id="no-delay">touchstart无延迟</div>
<div> <a id="link1" href="#1">链接1</a> <a id="link2" href="#2">链接1</a></div>
<div id="log"></div>

<style>
body {
font-size: 60px;
}
</style>

<script>
const $ = s => document.querySelector(s)
const log = str => $('#log').innerText = str

let t1, t2

$('#delay').ontouchstart = e => {
t1 = Date.now()
}

$('#delay').onclick = e => {
log(Date.now() - t1)
}

$('#no-delay').ontouchstart = e => {
e.preventDefault()
log('touchstart无延迟')
}

$('#link1').ontouchstart = e => {
t2 = Date.now()
}

$('#link2').ontouchstart = e => {
t2 = Date.now()
}

window.onhashchange = () => {
log(`link: ${Date.now() - t2}ms`)
}
</script>
</body>
</html>

延迟的原因

当时的网站都是为大屏幕设备所设计的(没有设置<meta name="viewport">),当时的iphone手机有一个双击放大功能。手指在屏幕上快速点击两次,iOS 自带的 Safari 浏览器会将网页缩放至原始比例。

那如何判断用户快速点击两次呢?

假定用户点击后300ms内如果有第二次点击,就认为用户的目的是双击缩放而不是普通的点击页面元素。

所以用户第一次点击一个链接后,必须等300ms,浏览器才能决定是否是跳转还是缩放页面。 后来全触屏手机流行后,其他设备浏览器也开始效仿。

如何解决这个延迟问题

  • 方法1、设置meta
    <meta name="viewport" content="width=device-width">
  • 其他方法
    • faskclick库
    • 不用click用 touchstart

fastclick的原理和简单实现

什么时候用

  • 一些历史原因,没有使用 <meta name="viewport" content="width=device-width"> 的时候, 如 <meta name="viewport" content="width=750"> 这种写具体数值的时候在某些机型会有300ms延迟
  • 2017年以前使用,现在一般不用。

如何使用

1
2
3
4
5
// 引入 faskclick.js 
// 加入如下 内容
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body)
}, false)

fastclick 原理

在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后真正的click事件阻止掉

实现一个简单的fastclick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const FastClick = (function(){

function attach(root) {
let targetElement = null
root.addEventListener('touchstart', function () {
targetElement = event.target
})
root.addEventListener('touchend', function (event) {
event.preventDefault()
let touch = event.changedTouches[0]
let clickEvent = document.createEvent('MouseEvents')
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null)
clickEvent.forwardedTouchEvent = true
targetElement.dispatchEvent(clickEvent)
})
}

return { attach }
})()

FastClick.attach(document.body)

点击穿透和规避

什么是点击穿透

移动端弹窗有个浮层,假设关闭按钮在左上角,关闭按钮后面刚好是一个链接,关闭浮层的时候会触发链接跳转。 这就是点击穿透

点击穿透现象:请在手机看,或者调试为手机模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
padding: 30px;
font-size: 60px;
}
.mask {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.8);
color: #fff;
padding: 30px;

}
.close {
background: red;
padding: 10px;
}
</style>
</head>
<body>
<span class="bg">aaa</span>
<p class="log">0</p>
<div class="mask">
<span class="close">X</span>
</div>

<script>
const $ = s => document.querySelector(s)
const log = str => $('.log').innerText = str

let i = 1

$('.close').ontouchstart = (e) => {
$('.mask').style.display = 'none'
log('touched ' + (++i))
}

$('.bg').onclick = () => {
log('点击了背景')
}
</script>
</body>
</html>

原因分析

上古时代触屏设备为了区分用户双击缩放,对click做了300ms延迟触发,因此用户在移动端触屏设备的操作流程以及事件触发为:

1、手指触摸屏幕到屏幕,触发 touchstart

2、手指在屏幕短暂停留(如果是移动,触发 touchmove)

3、手指离开屏幕, 触发 touchend

4、等待约300ms,看用户在此时间内是否再次触摸屏幕。如果没有

5、300ms后 在用户手指离开的位置触发 click事件

击穿的根源在第4步。当用户用户点击关闭让遮罩隐藏后,浏览器在300ms后在原来用户手指离开的位置触发click事件,此时遮罩不存在了,自然“点击”到后面的元素。

解决方案

方案1

设置<meta name="viewport" content="device-width">。表面上看起来解决了300ms延时,就能解决穿透。实际上这种方法不可行。因为click事件是在touchend事件之后,更晚于touchstart(间隔了60~100ms),如果在touchstart 里绑定关闭了浮层,约100ms后 click事件触发的时候仍然会引发问题。

方法2

关闭浮层用click,不用touchstart。 可行。因为浮层关闭监听的是click,当浮层消失后不会再在原来的位置再冒出个click

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tap击穿解决1</title>
<meta name="viewport" content="width=device-width">

<style>
body {
margin: 0;
padding: 30px;
font-size: 60px;
}
.mask {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.8);
color: #fff;
padding: 30px;

}
.close {
background: red;
padding: 10px;
}
</style>
</head>
<body>
<span class="bg">aaa</span>
<p class="log">0</p>
<div class="mask">
<span class="close">X</span>
</div>

<script>
const $ = s => document.querySelector(s)
const log = str => $('.log').innerText = str

let i = 1

$('.close').onclick = (e) => {
$('.mask').style.display = 'none'
log('touched ' + (++i))
}

$('.bg').onclick = () => {
log('点击了背景')
}
</script>

</body>
</html>

方法3

浮层关闭的时间延长。可行。当用户触发touchstart关闭浮层时,浮层可以使用渐变消失(可设置浮层关闭动画在300ms以上),此刻click事件在原来的位置触发时点击的还是浮层,而不是浮层后面的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
padding: 30px;
font-size: 60px;
}
.mask {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.8);
color: #fff;
padding: 30px;

}
.close {
background: red;
padding: 10px;
}

.fade {
animation: fade .3s;
animation-fill-mode: forwards;
}

.hide {
display: none
}
@keyframes fade {
0% {
opacity: 100%;
}
100% {
opacity: 0;
}
}
</style>
</head>
<body>
<a href="https://baidu.com">百度</a>
<span class="bg">百度</span>
<p class="log">0</p>
<div class="mask">
<span class="close">X</span>
</div>

<script>
const $ = s => document.querySelector(s)
const log = str => $('.log').innerText = str

let i = 1

$('.close').ontouchstart = (e) => {

log('touched ' + (++i))
}
$('.close').ontouchend = e => {
//e.preventDefault()
//$('.mask').style.display = 'none'
$('.mask').classList.add('fade')
setTimeout(() => $('.mask').classList.add('hide'), 300)
}

$('.bg').onclick = () => {
log('bg click')
}
</script>

</body>
</html>

方法4

在ontouchstart 里阻止默认事件。可行。 这样可以阻止click的触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>

<style>
body {
margin: 0;
padding: 30px;
font-size: 60px;
}
.mask {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.8);
color: #fff;
padding: 30px;

}
.close {
background: red;
padding: 10px;
}
</style>
</head>
<body>
<span class="bg">背景</span>
<p class="log">0</p>
<div class="mask">
<span class="close">X</span>
</div>

<script>
const $ = s => document.querySelector(s)
const log = str => $('.log').innerText = str

let i = 1

$('.close').ontouchstart = (e) => {
e.preventDefault()
$('.mask').style.display = 'none'
log('touched ' + (++i))
}

$('.bg').onclick = () => {
log('bg click')
}
</script>

</body>
</html>

1px实现

为什么要实现1px

当我们在css里写 border: 1px solid #000的时候,用户会觉得边线依然很粗不美观。如何实现比1px更细的边线呢?下面列举了几种方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>1px</title>
<meta name="viewport" content="width=device-width">
</head>
<body>
<div class="item item-1px">列表1</div>
<div class="item item-0_5px">列表2</div>
<div class="item item-css3transform">列表3</div>
<div class="item item-shadow">列表4</div>
<div class="item item-background">列表5</div>
<div class="item item-4-border">列表6</div>


<style>

.item {
padding: 10px 0
}

.item-1px {
border-bottom: 1px solid #ccc;
}

.item-0_5px {
border-bottom: .5px solid #ccc;
/*ios有效 android无效*/
}

.item-css3transform::after {
content: '';
display: block;
height: 1px;
transform: scaleY(0.5);
background: #ccc;
position: relative;
top: 10px;
}

.item-shadow {
box-shadow: 0 0.5px 0 0 #ccc;
}

.item-background::after {
display: block;
content: '';
height: 1px;
background: linear-gradient(0deg, #ccc 50%, transparent 50%);
position: relative;
top: 10px;
}

.item-4-border {
margin: 10px 0;
padding: 10px;
background: linear-gradient(to bottom, #ccc .5px, transparent 0), linear-gradient(to left, #ccc .5px, transparent 0), linear-gradient(to right, #ccc .5px, transparent 0), linear-gradient(to top, #ccc .5px, transparent 0);
}
</style>
</body>
</html>

方案总结

方案1, viewport缩放

前面讲过viewport适配的方案,适配的原理是整个页面放大缩小,当然也包括边线

方案2, 设置0.5px

ios有效,部分安卓机型无效。 慢慢等吧,总有一天(可能一两年后)安卓会生效

方案3,使用CSS3 transform,给伪元素缩小尺寸

都有效,缺点是一条边框还好,如果是4条边框两个伪元素不够得新增标签

方案4,使用背景线性渐变

方便,兼容性好。

其他方案

背景图片(麻烦、兼容性不好),svg背景图片(麻烦)

要避免的问题

  • 1px像素缩小要避免和圆角一起使用

移动端-003-移动端适配方案

移动端适配方案

viewport 缩放适配

假设你做移动端,公司UI给了你一个750px宽的 设计稿

你直接按照标注写样式,如果只关注iphone6 就只需要这样即可

1
2
iphone的设备独立像素为 375 刚好是 750设计稿的一半
<meta name="viewport" content="width=750, initial-scale=0.5"

改善

我们不可能只处理 iphone6,因为还有 ipad,iphone5,android呢

所以需要动态设置缩放比例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html>
<head>
<script>
const WIDTH = 750;
let mobileAdapter = ()=>{
let scale = screen.width/WIDTH
let content = `width=${WIDTH}, user-scalable=no, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}`
let meta = document.querySelector('meta[name=viewport]');
if(!meta){
meta = document.creatElement('meta')
meta.setAttribute('name',viewport)
document.head.appendChild(meta)
}
meta.setAttribute('content',content)
}
mobileAdapter()
// 当屏幕翻转的时候再次执行这个函数
window.onorientationchange = mobileAdapter
</script>
<style>
.btn {
background:red;
width:98px
}
.btn2{
background:red;
width:100%;
}
</style>

</head>
<body>
<div class="btn">98px</div>
<hr>
<div class="btn2">100%</div>
</body>
</html>

缺陷

  • 边框线可能在大屏幕会变粗

vw适配方案

开发流程

1、拿到设计稿(假设为750px)

2、开始开发,对设计稿的标注进行转换,把px换成vw。比如页面元素字体标注的大小是32px,换成vw为 (100/750)*32 vw

3、对于需要等比缩放的元素,CSS使用转换后的单位

4、对于不需要缩放的元素,比如边框阴影,使用固定单位px

代码编写

1、viewport设置 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1"> 。把layout viewport 宽度设置为设备宽度,不需要缩放

2、用js定义css的自定义属性–width,对应的是把设计稿分成100份,每份的像素数。

3、根据设计稿标注设置样式, 比如标注稿里元素宽度为20px。这这里设置 calc(20px * var(–width))

4、对于不需要等比缩放的元素,比如边框,可以直接使用固定单位px即可。比如border-bottom: 1px solid #ccc

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<script>
const WIDTH = 750
document.documentElement.style.setProperty('--width', (100/WIDTH));
</script>
</head>
<body>
<header>
<span class="active" target="hot">热门</span>
<span target="focus">关注</span>
</header>
<style>
:root {
--color: red;
}
body, figure {
margin: 0;
}
body {
color: #fff;
background: #888;
background-size: cover;
min-height: 100vh;
}
header {
font-size: calc(28vw * var(--width));
display: flex;
padding: 0 calc(20vw * var(--width));
text-align: center;
}
header > span {
flex: 1;
line-height: calc(84vw * var(--width));
color: rgba(255, 255, 255, 0.3);
}
header > span.active {
color: #fff;
border-bottom: 2px solid rgba(255, 255, 255, 0.3);
}
</style>
</body>
</html>

REM方式

代码分析

1、viewport设置 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1"> 。把layout viewport 宽度设置为设备宽度,不需要缩放

2、用js定义html的fontSize。假设设计稿的宽度和屏幕宽度相同,我们设置html的fontSize为100px,对应设计稿的20px即为0.2rem。若设计稿的尺寸和屏幕尺寸不同,那让html的fontSize为 100*屏幕宽度/设计稿宽度,此时设计稿中的20px仍然对应0.2rem。

3、对于不需要等比缩放的元素,比如边框,可以直接使用固定单位px即可。比如border-bottom: 1px solid #ccc

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<script>
const WIDTH = 750 //设计稿尺寸
const setView = () => {
console.log(screen.width)
document.documentElement.style.fontSize = (100*screen.width/WIDTH) + 'px'
}
window.onorientationchange = setView
setView()
</script>
</head>
<body>
<header>
<span class="active" target="hot">热门</span>
<span target="focus">关注</span>
</header>
<style>
}
html {
/*font-size: 100 * window.innerWidth / 750*/
}
body, figure {
margin: 0;
}
body {
color: #fff;
background: #888;
background-size: cover;
min-height: 100vh;
}
header {
font-size: .28rem;
display: flex;
padding: 0 .20rem;
text-align: center;
}
header > span {
flex: 1;
line-height: .80rem;
color: rgba(255, 255, 255, 0.3);
}
header > span.active {
color: #fff;
border-bottom: 2px solid rgba(255, 255, 255, 0.3);
}
</style>
</body>
</html>

弹性盒适配方案

  • 不需要等比缩放,页面元素使用固定单位。使用弹性盒(flex)做布局,可结合媒体查询调整文字大小。

针对上面的适配方案,都是等比例缩小

但是某些场景 一段文字介绍 在手机到ipad上我们不需要变大,字体还是跟手机上一样即可

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>jirengu.com</title>
<meta name="viewport" content="width=device-width">
</head>
<body>
<section>
<div class="img"></div>
<div class="detail">
<h2>aaa</h2>
<p>aaaaaa</p>
</div>
</section>
<section>
<div class="img"></div>
<div class="detail">
<h2>bbb</h2>
<p>bbbbbbb</p>
</div>
</section>
<section>
<div class="img"></div>
<div class="detail">
<h2>ccc</h2>
<p>cccccccc</p>
</div>
</section>

<style>

section {
display: flex;
padding: 10px 0;
margin: 10px;
border-bottom: 1px solid #ccc;
background: #fff;
}

section > .img {
background:red;
width: 60px;
height: 60px;
border-radius: 50%;
}

section .detail {
margin-left: 10px;
flex: 1;
}

section .detail h2 {
font-size: 20px;
margin-top: 10px;
}

section .detail p {
font-size: 14px;
color: #676767;
}

</style>
</body>
</html>

移动端-002-viewport

viewport

尺寸的区别

屏幕的尺寸

  • 就是电脑设置的分辨率
    • mac pro 默认 screen.width 为 1440
1
2
screen.width
screen.height

窗口的尺寸

  • 浏览器窗口的大小
1
2
window.innerHeight
window.innerWidth

viewport的概念

对于页面一个div的宽度,我们设置width:10%; 这是 10%是相对于谁?最终是多宽

  • div宽度首先是相对于父亲 也就是 body的 10%
  • body的宽度,因为它是撑满html的,所以它应该和 html等宽,注意 body是有 margin的 ,清除margin 后html和body的宽度就都一致了
  • html的宽度是多少呢? html和窗口等宽
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!DOCTYPE html>
    <html>
    <head>
    <style>
    .aaa{
    height: 300px;
    width: 10%;
    background:red;
    }
    </style>
    </head>
    <body>
    <div class="aaa"></div>
    </body>
    </html>

测试viewport

document.documentElement.clientWidth 和 document.documentElement.clientHeight 测试视窗宽高

document.documentElement.clientWidth 和 window.innerWidth 的区别

当页面出现滚动条的时候

  • document.documentElement.clientWidth 不包含滚动条
  • window.innerWidth 包含滚动条

测量html的宽高

1
2
document.documentElement.offsetHeight 
document.documentElement.offsetWidth

移动端的 viewport

差异:屏幕太窄了,基本没有宽度超过400px的

此时有个网站 设置min-width:1000px比如有个侧栏 宽度为10% 那么 在PC没有问题,到了手机就会有问题,因为手机总共不超过 400px 宽度 ,这就导致了一个侧栏就占了屏幕的三分之一

为了解决这个问题 手机浏览器的厂商就提出了 两个概念 visual viewport 和 layout viewport

visual viewport 和 layout viewport

  • visual viewport 可视窗口:移动端视窗的设备独立像素
  • layout viewport 用于布局的窗口:页面渲染时参考的宽高

在移动端,页面渲染以 layout viewport 宽度作为计算标准。以iphone 为例,其 layout viewport 是980px,页面渲染时就认为窗口为 980px。 一些老网站(没做移动端适配) 在手机打开的时候就会按照 980的宽度计算 相当于 把980px的宽度内容缩小到手机上显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<style>
.btn {
background:red;
width:98px;
}
.btn2{
background:red;
width:100%;
}
</style>
</head>
<body>
<div class="btn">98px</div>
<hr>
<div class="btn2">100%</div>
</body>
</html>
  • 手机端显示 98px的按钮就被缩小了
    • 而且 document.documentElement.clientWidth 一取是 980px,你就懵逼了。
  • PC端显示 98px就很正常

总结

移动端渲染的时候不去管你窗口是多大,他就认为我的宽度是980,然后按照980去渲染这个页面,同样的内容 你看到PC在看手机 感觉手机上的内容缩小了。

现在做唯一的修改

1
2
3
4
5
6
7
8
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

快捷键是输入 meta:vp + tab键

效果就是
layout viewport 的宽度 = device width

此时再去做布局的时候,缩放比就为1:1,就达到 你写100px 在手机上也显示为 100px

移动端-001-像素单位

像素

设备像素 device pixels

  • 物理像素,屏幕上一个点。
  • 这个点,实际是由RGB三原色排列组成。
  • 想想霓虹灯(物理像素)由红绿蓝小灯组成,三盏灯不同的亮度混合出各种彩色

设备独立像素(device independent pixels)

1
2
3
可以通过 api 查看 
window.screen.width
window.screen.height
  • iphone6 的 375x812指的是设备独立像素
  • chrome调试移动端不同手机型号的尺寸也是设备独立像素

概念

一个设备独立像素里可能包含1个或多个物理像素点,包含的越多看起来越清晰

参考普通屏的mac air 和 mac pro

  • 很明显 mac pro的看起来更清晰,因为 mac pro 一个设备独立像素包含的物理像素点更多

像素分辨率

以 iphone手机为例它的像素分辨率是 1125x2436 指的是横向能显示1125个物理像素点,纵向能显示2436个物理像素点。

现在的4K屏就是 4096x2160s

PPI(pix per inch)

每英寸的像素数。如5.8寸 iphone为例。

  • 手机屏幕对角线长度就是 英寸数
  • 分辨率为1125x2436
1
ppi = Math.sqrt(1125*1125 + 2436*2436) / 5.8 = 463ppi

CSS像素

1
2
3
4
div{width:100px;}
在页面 缩放比为1:1情况下, 1个CSS像素=1个设备独立像素。

假设当前屏幕分辨率是 1440,如果给元素设置宽度720px,则视觉上宽度是屏幕的一半。 这也解释了为什么分辨率跳高后感觉网页文字变小了。

devicePixelRatio

window.devicePixelRatio指的是设备物理像素和设备独立像素(device-independent pixels, dips)的比例。window.devicePixelRatio = 物理像素 / 设备独立像素(dips) 。经计算, iphonex的 devicePixelRatio 是3。

为啥普通电脑上的图片放到 mac pro 变模糊了?

  • 参考芯片的制程,分别有28nm、7nm、5nm。
  • 假设在1厘米x1厘米的空间很明显 5nm能放置更多的晶体管

高清屏实际就是1个设备独立像素能容纳更多的 物理像素

一张图 假设宽高100px 的图片普通电脑可能就是 100x100 = 10000个物理像素点

而在高清屏的时候 100px 的图片 可能是 400*400 = 160000 个物理像素点

CSS-010-动态REM

动态REM

手机专用的自适应方案

REM是什么

常见单位

1
2
3
4
5
px 像素
em 一个字母的宽度 、一个汉子的宽度?
rem root em 根元素的 font-size
vh viewport height 100vh = 视口高度
vw viewport width 100vw = 视口宽度

网页的默认font-size是 16px

chrome有设置最小字号是12px,此时你就算写出 font-size:6px 也会变成 12px

问题1: font-size:1rem 是多少像素

1
2
3
4
5
6
7
8
9
10
p{font-size:1rem;}
答案是 16px,因为根元素font-size是16

body{font-size:20px;}
p{font-size:1rem;}
答案是 16px,因为根元素font-size是16, html才是根元素

html{font-size:20px;}
p{font-size:1rem;}
答案是 20px,因为根元素font-size是20

REM和 EM区别

p 的高度是多少

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<style>
p{
height:2em;
}
</style>
</head>
<body>
<p>回答</p>
</body>
</html>

经过浏览器检查发现是 32px

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<style>
p{
height:2em;
font-size:30px;
}
</style>
</head>
<body>
<p>回答</p>
</body>
</html>

经过浏览器检查发现是 60px,结论:1em就是自身元素 font-size的像素值

REM是根元素的font-size,em是自身元素的font-size

动态REM

移动端写页面的演进

  • 百分比布局,只能保证水平方向没问题,但是高度呢?
    • 要求高度是宽度的1半,就很难做到,因为 height是不会按照父容器的宽度来定的
  • 动态REM

    1
    2
    3
    4
    5
    当屏幕 640px的时候 banner为 320*320

    当屏幕 320px的时候 banner为 160*160

    当屏幕 414px的时候 banner为 212*212
    • 一切单位以“宽度”为基准,等比例缩放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<style>
*{margin:0;padding:0;}
body{
font-size:16px;
}
.child{
background:#ddd;
height: 0.2rem;
width: 0.4rem;
float:left;
margin:0.05rem 0.05rem;
}

.clearfix::after{
content:'';
display:block;
clear:both;
}
</style>
<script>
var pageWidth = window.innerWidth;
document.write('<style>html{font-size:'+pageWidth+'px;}</style>');
</script>
</head>
<body>
<div class="parent clearfix">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
</body>
</html>

对REM进行微调

  • border:1px问题,无法在小的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<style>
*{margin:0;padding:0;}
*{box-sizing:border-box;}
body{
font-size:16px;
}
.child{
background:#ddd;
height:2rem;
width: 4rem;
float:left;
margin:0.5rem 0.5rem;
/* 当像素特别小的时候就用1px因为网页最小1px */
border:1px solid red;
}

.clearfix::after{
content:'';
display:block;
clear:both;
}
</style>
<script>
var pageWidth = window.innerWidth;
document.write('<style>html{font-size:'+pageWidth/10+'px;}</style>');
</script>
</head>
<body>
<div class="parent clearfix">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
</body>
</html>