编写的计算器应用,浅谈浏览器http的缓存机制

H5直播起航

2016/10/31 · HTML5 · 开发

原文出处: 凹凸实验室   

图片 1

纯 HTML+CSS+JavaScript 编写的计算器应用

2016/09/26 · CSS, HTML5, JavaScript · 2 评论 · 计算器

原文出处: dunizb   

浅谈浏览器http的缓存机制

2016/04/05 · HTML5 · 缓存

原文出处: VaJoy Larn   

针对浏览器的http缓存的分析也算是老生常谈了,每隔一段时间就会冒出一篇不错的文章,其原理也是各大公司面试时几乎必考的问题。

之所以还写一篇这样的文章,是因为近期都在搞新技术,想“回归”下基础,也希望尽量总结的更详尽些。

那么你是否还需要阅读本篇文章呢?可以试着回答下面这个问题:

我们在访问百度首页的时候,会发现不管怎么刷新页面,静态资源基本都是返回 200(from cache)

图片 2

随便点开一个静态资源是酱的:

图片 3

哎哟有Response报头数据呢,看来服务器也正常返回了etag什么鬼的应有尽有,那状态200不是应该对应的非缓存状态么?要from cache的话不是应该返回304才合理么?

难道是度娘的服务器故障了吗?

如果你知道答案,那就可以忽略本文了。

图片 4

http报文中与缓存相关的首部字段

我们先来瞅一眼RFC2616规定的47种http报文首部字段中与缓存相关的字段,事先了解一下能让咱在心里有个底:

1. 通用首部字段(就是请求报文和响应报文都能用上的字段)

图片 5

2. 请求首部字段

图片 6

3. 响应首部字段

图片 7

4. 实体首部字段

图片 8

后续大体也会依次介绍它们。

图片 9

场景模拟

为方便模拟各种缓存效果,我们建个非常简单的场景。

1. 页面文件

我们建个非常简单的html页面,上面只有一个本地样式文件和图片:

XHTML

<!DOCTYPE html> <html> <head> <title>缓存测试</title> <link rel="stylesheet" href="css/reset.css"> </head> <body> <h1>哥只是一个标题</h1> <p><img src="img/dog.jpg" /></p> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<title>缓存测试</title>
<link rel="stylesheet" href="css/reset.css">
</head>
<body>
<h1>哥只是一个标题</h1>
<p><img src="img/dog.jpg" /></p>
</body>
</html>

2. 首部字段修改

有时候一些浏览器会自行给请求首部加上一些字段(如chrome使用F5会强制加上“cache-control:max-age=0”),会覆盖掉一些字段(比如pragma)的功能;另外有时候我们希望服务器能多/少返回一些响应字段。

这种情况我们就希望可以手动来修改请求或响应报文上的内容了。那么如何实现呢?这里我们使用Fiddler来完成任务。

在Fiddler中我们可以通过“bpu XXX”指令来拦截指定请求,然后手动修改请求内容再发给服务器、修改响应内容再发给客户端。

以我们的example为例,页面文件走nginx通过 可直接访问,所以我们直接执行“bpu localhost”拦截所有地址中带有该字样的请求:

图片 10

点击被拦截的请求,可以在右栏直接修改报文内容(上半区域是请求报文,下半区域是响应报文),点击黄色的“Break on Response”按钮可以执行下一步(把请求发给服务器),点击红色的按钮“Run to Completion”可以直接完成整个请求过程:

图片 11

通过这个方法我们可以很轻松地模拟出各种http缓存场景。

3. 浏览器的强制策略

如上述,当下大多数浏览器在点击刷新按钮或按F5时会自行加上“Cache-Control:max-age=0”请求字段,所以我们先约定成俗——后文提及的“刷新”多指的是选中url地址栏并按回车键(这样不会被强行加上Cache-Control)

事实上有的浏览器还有一些更奇怪的行为,在后续我们回答文章开头问题的时候会提到。

图片 12

石器时代的缓存方式

在 http1.0 时代,给客户端设定缓存方式可通过两个字段——“Pragma”和“Expires”来规范。虽然这两个字段早可抛弃,但为了做http协议的向下兼容,你还是可以看到很多网站依旧会带上这两个字段。

1. Pragma

当该字段值为“no-cache”的时候(事实上现在RFC中也仅标明该可选值),会知会客户端不要对该资源读缓存,即每次都得向服务器发一次请求才行。

Pragma属于通用首部字段,在客户端上使用时,常规要求我们往html上加上这段meta元标签(而且可能还得做些hack放到body后面去):

<meta http-equiv="Pragma" content="no-cache">

1
<meta http-equiv="Pragma" content="no-cache">

它告诉浏览器每次请求页面时都不要读缓存,都得往服务器发一次请求才行。

BUT!!! 事实上这种禁用缓存的形式用处很有限:

1. 仅有IE才能识别这段meta标签含义,其它主流浏览器仅能识别“Cache-Control: no-store”的meta标签(见出处)
2. 在IE中识别到该meta标签含义,并不一定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求(仅限页面,页面上的资源则不受影响)

做了测试后发现也的确如此,这种客户端定义Pragma的形式基本没起到多少作用。

不过如果是在响应报文上加上该字段就不一样了:

图片 13

如上图红框部分是再次刷新页面时生成的请求,这说明禁用缓存生效,预计浏览器在收到服务器的Pragma字段后会对资源进行标记,禁用其缓存行为,进而后续每次刷新页面均能重新发出请求而不走缓存。

图片 14

2. Expires

有了Pragma来禁用缓存,自然也需要有个东西来启用缓存和定义缓存时间,对http1.0而言,Expires就是做这件事的首部字段。

Expires的值对应一个GMT(格林尼治时间),比如“Mon, 22 Jul 2002 11:12:01 GMT”来告诉浏览器资源缓存过期时间,如果还没过该时间点则不发请求。

在客户端我们同样可以使用meta标签来知会IE(也仅有IE能识别)页面(同样也只对页面有效,对页面上的资源无效)缓存时间:

<meta http-equiv="expires" content="mon, 18 apr 2016 14:30:00 GMT">

1
<meta http-equiv="expires" content="mon, 18 apr 2016 14:30:00 GMT">

如果希望在IE下页面不走缓存,希望每次刷新页面都能发新请求,那么可以把“content”里的值写为“-1”或“0”。

注意的是该方式仅仅作为知会IE缓存时间的标记,你并不能在请求或响应报文中找到Expires字段。

如果是在服务端报头返回Expires字段,则在任何浏览器中都能正确设置资源缓存的时间:

图片 15

在上图里,缓存时间设置为一个已过期的时间点(见红框),则刷新页面将重新发送请求(见蓝框)

那么如果Pragma和Expires一起上阵的话,听谁的?我们试一试就知道了:

图片 16

我们通过Pragma禁用缓存,又给Expires定义一个还未到期的时间(红框),刷新页面时发现均发起了新请求(蓝框),这意味着Pragma字段的优先级会更高。

BUT,响应报文中Expires所定义的缓存时间是相对服务器上的时间而言的,如果客户端上的时间跟服务器上的时间不一致(特别是用户修改了自己电脑的系统时间),那缓存时间可能就没啥意义了。

图片 17

Cache-Control

针对上述的“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间,若报文中同时出现了 Pragma、Expires 和 Cache-Control,会以 Cache-Control 为准。

Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。在RFC中规范了 Cache-Control 的格式为:

"Cache-Control" ":" cache-directive

1
"Cache-Control" ":" cache-directive

作为请求首部时,cache-directive 的可选值有:

图片 18

作为响应首部时,cache-directive 的可选值有:

图片 19

我们依旧可以在HTML页面加上meta标签来给请求报头加上 Cache-Control 字段:

另外 Cache-Control 允许自由组合可选值,例如:

Cache-Control: max-age=3600, must-revalidate

1
Cache-Control: max-age=3600, must-revalidate

它意味着该资源是从原服务器上取得的,且其缓存(新鲜度)的有效时间为一小时,在后续一小时内,用户重新访问该资源则无须发送请求。

当然这种组合的方式也会有些限制,比如 no-cache 就不能和 max-age、min-fresh、max-stale 一起搭配使用。

组合的形式还能做一些浏览器行为不一致的兼容处理。例如在IE我们可以使用 no-cache 来防止点击“后退”按钮时页面资源从缓存加载,但在 Firefox 中,需要使用 no-store 才能防止历史回退时浏览器不从缓存中去读取数据,故我们在响应报头加上如下组合值即可做兼容处理:

Cache-Control: no-cache, no-store

1
Cache-Control: no-cache, no-store

图片 20

缓存校验字段

上述的首部字段均能让客户端决定是否向服务器发送请求,比如设置的缓存时间未过期,那么自然直接从本地缓存取数据即可(在chrome下表现为200 from cache),若缓存时间过期了或资源不该直接走缓存,则会发请求到服务器去。

我们现在要说的问题是,如果客户端向服务器发了请求,那么是否意味着一定要读取回该资源的整个实体内容呢?

我们试着这么想——客户端上某个资源保存的缓存时间过期了,但这时候其实服务器并没有更新过这个资源,如果这个资源数据量很大,客户端要求服务器再把这个东西重新发一遍过来,是否非常浪费带宽和时间呢?

答案是肯定的,那么是否有办法让服务器知道客户端现在存有的缓存文件,其实跟自己所有的文件是一致的,然后直接告诉客户端说“这东西你直接用缓存里的就可以了,我这边没更新过呢,就不再传一次过去了”。

为了让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,Http1.1新增了几个首部字段来做这件事情。

1. Last-Modified

服务器将资源传递给客户端时,会将资源最后更改的时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。

客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码即可。

至于传递标记起来的最终修改时间的请求报文首部字段一共有两个:

⑴ If-Modified-Since: Last-Modified-value

示例为 If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT

1
示例为  If-Modified-Since: Thu, 31 Mar 2016 07:07:52 GMT

该请求首部告诉服务器如果客户端传来的最后修改时间与服务器上的一致,则直接回送304 和响应报头即可。

当前各浏览器均是使用的该请求首部来向服务器传递保存的 Last-Modified 值。

**⑵ If-Unmodified-Since: Last-Modified-value**

告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412(Precondition Failed) 状态码给客户端。

当遇到下面情况时,If-Unmodified-Since 字段会被忽略:

  1. Last-Modified值对上了(资源在服务端没有新的修改); 2. 服务端需返回2XX和412之外的状态码; 3. 传来的指定日期不合法
1
2
3
1. Last-Modified值对上了(资源在服务端没有新的修改);
2. 服务端需返回2XX和412之外的状态码;
3. 传来的指定日期不合法

Last-Modified 说好却也不是特别好,因为如果在服务器上,一个资源被修改了,但其实际内容根本没发送改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)

图片 21

2. ETag

为了解决上述Last-Modified可能存在的不准确的问题,Http1.1还推出了 ETag 实体首部字段。

服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。

客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。

如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

那么客户端是如何把标记在资源上的 ETag 传去给服务器的呢?请求报文中有两个首部字段可以带上 ETag 值:

⑴ If-None-Match: ETag-value

示例为 If-None-Match: "56fcccc8-1699"

1
示例为  If-None-Match: "56fcccc8-1699"

告诉服务端如果 ETag 没匹配上需要重发资源数据,否则直接回送304 和响应报头即可。

当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。

⑵ If-Match: ETag-value

告诉服务器如果没有匹配到ETag,或者收到了“*”值而当前并没有该资源实体,则应当返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。

If-Match 的一个应用场景是,客户端走PUT方法向服务端请求上传/更替资源,这时候可以通过 If-Match 传递资源的ETag。

 

需要注意的是,如果资源是走分布式服务器(比如CDN)存储的情况,需要这些服务器上计算ETag唯一值的算法保持一致,才不会导致明明同一个文件,在服务器A和服务器B上生成的ETag却不一样。

图片 22

如果 Last-Modified 和 ETag 同时被使用,则要求它们的验证都必须通过才会返回304,若其中某个验证没通过,则服务器会按常规返回资源实体及200状态码。

在较新的 nginx 上默认是同时开启了这两个功能的:

图片 23

上图的前三条请求是原始请求,接着的三条请求是刷新页面后的新请求,在发新请求之前我们修改了 reset.css 文件,所以它的 Last-Modified 和 ETag 均发生了改变,服务器因此返回了新的文件给客户端(状态值为200)

而 dog.jpg 我们没有做修改,其Last-Modified 和 ETag在服务端是保持不变的,故服务器直接返回了304状态码让客户端直接使用缓存的 dog.jpg 即可,没有把实体内容返回给客户端(因为没必要)

图片 24

缓存实践

当我们在一个项目上做http缓存的应用时,我们还是会把上述提及的大多数首部字段均使用上,例如使用 Expires 来兼容旧的浏览器,使用 Cache-Control 来更精准地利用缓存,然后开启 ETag 跟 Last-Modified 功能进一步复用缓存减少流量。

那么这里会有一个小问题——Expires 和 Cache-Control 的值应设置为多少合适呢?

答案是不会有过于精准的值,均需要进行按需评估。

例如页面链接的请求常规是无须做长时间缓存的,从而保证回退到页面时能重新发出请求,百度首页是用的 Cache-Control:private,腾讯首页则是设定了60秒的缓存,即 Cache-Control:max-age=60。

而静态资源部分,特别是图片资源,通常会设定一个较长的缓存时间,而且这个时间最好是可以在客户端灵活修改的。以腾讯的某张图片为例:

1
http://i.gtimg.cn/vipstyle/vipportal/v4/img/common/logo.png?max_age=2592000

客户端可以通过给图片加上“max_age”的参数来定义服务器返回的缓存时间:

图片 25

当然这需要有一个前提——静态资源能确保长时间不做改动。如果一个脚本文件响应给客户端并做了长时间的缓存,而服务端在近期修改了该文件的话,缓存了此脚本的客户端将无法及时获得新的数据。

解决该困扰的办法也简单——把服务侧ETag的那一套也搬到前端来用——页面的静态资源以版本形式发布,常用的方法是在文件名或参数带上一串md5或时间标记符:

1
2
3
https://hm.baidu.com/hm.js?e23800c454aa573c0ccb16b52665ac26
http://tb1.bdstatic.com/tb/_/tbean_safe_ajax_94e7ca2.js
http://img1.gtimg.com/ninja/2/2016/04/ninja145972803357449.jpg

如果文件被修改了,才更改其标记符内容,这样能确保客户端能及时从服务器收取到新修改的文件。

图片 26

关于开头的问题

现在回过头来看文章开头的问题,可能会觉得答案很容易回答出来。

百度首页的资源在刷新后实际没有发送任何请求,因为 Cache-Control 定义的缓存时间段还没到期。在Chrome中即使没发送请求,但只要从本地的缓存中取,都会在Network面板显示一条状态为200且注明“from cache”的伪请求,其Response内容只是上一次回包留下的数据。

然而这并不是问题的全部答案,我们前面提到过,在Chrome中如果点击“刷新”按钮,Chrome会强制给所有资源加上“Cache-Control: max-age=0”的请求首部并向服务器发送验证请求的,而在文章开头的动图中,我们的确点击了“刷新”按钮,却不见浏览器发去新请求(并返回304)

关于这个问题其实在组内跟小伙伴们讨论过,通过Fiddler抓包发现,如果关闭Chrome的开发者面板再点击“刷新”按钮,浏览器是会按预期发送验证请求且接收返回的304响应的,另外这个奇怪的情况在不同的网站甚至不同的电脑下出现频率都不一致,所以暂时将其归咎于浏览器的怪异反应。

那么有这么一个问题——是否有办法在浏览器点击“刷新”按钮的时候不让浏览器去发新的验证请求呢?

办法还是有的,就是不怎么实用——在页面加载完毕后通过脚本动态地添加资源:

$(window).load(function() { var bg=''; setTimeout(function() { //setTimeout是必须的 $('#bgOut').css('background-image', 'url('+bg+')'); },0); });

1
2
3
4
5
6
$(window).load(function() {
      var bg='http://img.infinitynewtab.com/wallpaper/100.jpg';
      setTimeout(function() {  //setTimeout是必须的
       $('#bgOut').css('background-image', 'url('+bg+')');
      },0);
});

出处来自知乎,更具体的解释可以去看看。

图片 27

其它相关的首部字段

事实上较常用和重要的缓存相关字段我们都介绍完了,这里顺带讲讲几个跟缓存有关系,但没那么主要的响应首部字段。

1. Vary

“vary”本身是“变化”的意思,而在http报文中更趋于是“vary from”(与。。。不同)的含义,它表示服务端会以什么基准字段来区分、筛选缓存版本。

我们先考虑这么一个问题——在服务端有着这么一个地址,如果是IE用户则返回针对IE开发的内容,否则返回另一个主流浏览器版本的内容。这很简单,服务端获取到请求的 User-Agent 字段做处理即可。但是用户请求的是代理服务器而非原服务器,且代理服务器如果直接把缓存的IE版本资源发给了非IE的客户端,这就出问题了。

因此 Vary 便是着手处理该问题的首部字段,我们可以在响应报文加上:

Vary: User-Agent

1
Vary: User-Agent

便能知会代理服务器需要以 User-Agent 这个请求首部字段来区别缓存版本,防止传递给客户端的缓存不正确。

Vary 也接受条件组合的形式:

Vary: User-Agent, Accept-Encoding

1
Vary: User-Agent, Accept-Encoding

这意味着服务器应以 User-Agent 和 Accept-Encoding 两个请求首部字段来区分缓存版本。

图片 28

2. Date 和 Age

HTTP并没有提供某种方法来帮用户区分其收到的资源是否命中了代理服务器的缓存,但在客户端我们可以通过计算响应报文中的 Date 和 Age 字段来得到答案。

Date 理所当然是原服务器发送该资源响应报文的时间(GMT格式),如果你发现 Date 的时间与“当前时间”差别较大,或者连续F5刷新发现 Date 的值都没变化,则说明你当前请求是命中了代理服务器的缓存。

上述的“当前时间”自然是相对于原服务器而言的时间,那么如何获悉原服务器的当前时间呢?

常规从页面地址请求的响应报文中可获得,以博客园首页为例:

图片 29

每次你刷新页面,浏览器都会重新发出这条url的请求,你会发现其 Date 值是不断变化的,这说明该链接没有命中缓存,都是从原服务器返回过来的数据。

因此我们可以拿页面上其它静态资源请求回包中的 Date 与其进行对比,若静态资源的 Date 早于原服务端时间,则说明命中了代理服务器缓存。

通常还满足这么个条件:

静态资源Age + 静态资源Date = 原服务端Date

1
静态资源Age + 静态资源Date = 原服务端Date

这里的 Age 也是响应报文中的首部字段,它表示该文件在代理服务器中存在的时间(秒),如文件被修改或替换,Age会重新由0开始累计。

我们在上面那张博客园首页报文截图的同个场景下,看看某个文件(jQuery.js)命中代理服务器缓存的回包数据:

图片 30

会发现它满足我们上述的规则:

//return true new Date('Mon, 04 Apr 2016 07:03:17 GMT')/1000 == new Date('Sat, 19 Dec 2015 01:29:14 GMT')/1000 + 9264843

1
2
//return true
new Date('Mon, 04 Apr 2016 07:03:17 GMT')/1000 == new Date('Sat, 19 Dec 2015 01:29:14 GMT')/1000 + 9264843

不过这条规则也不一定准确,特别是当原服务器经常修改系统时间的情况下。

关于http缓存原理的知识就整理到这,希望能让你有所收获,共勉~

3 赞 13 收藏 评论

图片 31

前言

前不久抽空对目前比较火的视频直播,做了下研究与探索,了解其整体实现流程,以及探讨移动端HTML5直播可行性方案。

发现目前 WEB 上主流的视频直播方案有 HLS 和 RTMP,移动 WEB 端目前以 HLS 为主(HLS存在延迟性问题,也可以借助 video.js 采用RTMP),PC端则以 RTMP 为主实时性较好,接下来将围绕这两种视频流协议来展开H5直播主题分享。

一道笔试题

之前偶然看到一个公司的笔试题,题目如下:

用HTML5、CSS3、JavaScript,做一个网页,实现如下图形式计算器

图片 32

具体要求:

  1. 有且只有一个文件:index.html。不允许再有其他文件,不允许再有单独的CSS、JS、PNG、JPG文件。
  2. 运行环境为 Google Chrome。
  3. 必须支持标准的四则运算。例如:1+2*3=7。
  4. 请在收到邮件的48小时内独立完成本测试,并回复本邮件。

一、视频流协议HLS与RTMP

一道笔试题引发的一个练手项目

花了一点时间写好的第一版,符合了笔试题的要求。后来左看右看觉得还可以改进做的更好,于是给它不断的改进,加新功能等,这样下来没完没了,利用业余时间一点一点的写,从刚开始的网页版,到后来做响应式的移动版,再到现在的移动App,短短续续大概写了3个月吧。

1. HTTP Live Streaming

HTTP Live Streaming(简称 HLS)是一个基于 HTTP 的视频流协议,由 Apple 公司实现,Mac OS 上的 QuickTime、Safari 以及 iOS 上的 Safari 都能很好的支持 HLS,高版本 Android 也增加了对 HLS 的支持。一些常见的客户端如:MPlayerX、VLC 也都支持 HLS 协议。

HLS 协议基于 HTTP,而一个提供 HLS 的服务器需要做两件事:

  • 编码:以 H.263 格式对图像进行编码,以 MP3 或者 HE-AAC 对声音进行编码,最终打包到 MPEG-2 TS(Transport Stream)容器之中;
  • 分割:把编码好的 TS 文件等长切分成后缀为 ts 的小文件,并生成一个 .m3u8 的纯文本索引文件;

浏览器使用的是 m3u8 文件。m3u8 跟音频列表格式 m3u 很像,可以简单的认为 m3u8 就是包含多个 ts 文件的播放列表。播放器按顺序逐个播放,全部放完再请求一下 m3u8 文件,获得包含最新 ts 文件的播放列表继续播,周而复始。整个直播过程就是依靠一个不断更新的 m3u8 和一堆小的 ts 文件组成,m3u8 必须动态更新,ts 可以走 CDN。一个典型的 m3u8 文件格式如下:

#EXTM3U

#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000
gear1/prog_index.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=311111
gear2/prog_index.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=484444
gear3/prog_index.m3u8

#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=737777
gear4/prog_index.m3u8

可以看到 HLS 协议本质还是一个个的 HTTP 请求 / 响应,所以适应性很好,不会受到防火墙影响。但它也有一个致命的弱点:延迟现象非常明显。如果每个 ts 按照 5 秒来切分,一个 m3u8 放 6 个 ts 索引,那么至少就会带来 30 秒的延迟。如果减少每个 ts 的长度,减少 m3u8 中的索引数,延时确实会减少,但会带来更频繁的缓冲,对服务端的请求压力也会成倍增加。所以只能根据实际情况找到一个折中的点。

对于支持 HLS 的浏览器来说,直接这样写就能播放了:

XHTML

<video src="" height="300" width="400" preload="auto" autoplay="autoplay" loop="loop" webkit-playsinline="true"></video>

1
2
<video src="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"
height="300" width="400" preload="auto" autoplay="autoplay" loop="loop" webkit-playsinline="true"></video>

注意:HLS 在 PC 端仅支持safari浏览器,类似chrome浏览器使用HTML5 video标签无法播放 m3u8 格式,可直接采用网上一些比较成熟的方案,如:sewise-player、MediaElement、videojs-contrib-hls、jwplayer。

项目地址

最终版的计算器,项目地址和预览图片在 GitHub:。

2. Real Time Messaging Protocol

Real Time Messaging Protocol(简称 RTMP)是 Macromedia 开发的一套视频直播协议,现在属于 Adobe。这套方案需要搭建专门的 RTMP 流媒体服务如 Adobe Media Server,并且在浏览器中只能使用 Flash 实现播放器。它的实时性非常好,延迟很小,但无法支持移动端 WEB 播放是它的硬伤。

虽然无法在iOS的H5页面播放,但是对于iOS原生应用是可以自己写解码去解析的, RTMP 延迟低、实时性较好。

浏览器端,HTML5 video标签无法播放 RTMP 协议的视频,可以通过 video.js 来实现。

XHTML

<link href="" rel="stylesheet">   <video id="example_video_1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264" loop="loop" webkit-playsinline> <source src="rtmp://10.14.221.17:1935/rtmplive/home" type='rtmp/flv'> </video>   <script src="; <script> videojs.options.flash.swf = 'video.swf'; videojs('example_video_1').ready(function() { this.play(); }); </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
<link href="http://vjs.zencdn.net/5.8.8/video-js.css" rel="stylesheet">
 
<video id="example_video_1" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264" loop="loop" webkit-playsinline>
<source src="rtmp://10.14.221.17:1935/rtmplive/home" type='rtmp/flv'>
</video>
 
<script src="http://vjs.zencdn.net/5.8.8/video.js"></script>
<script>
videojs.options.flash.swf = 'video.swf';
videojs('example_video_1').ready(function() {
this.play();
});
</script>

功能说明

最终版的功能如下:

  1. 界面布局采用CSS3 的 Flex box布局
  2. 内置两套主题可切换
  3. 计算历史记录显示
  4. 左滑右滑可以切换单手模式(App)
  5. 当输入手机号码后长按等于号可以拨打手机号码(App)
  6. 版本更新检查(App)

界面布局

由于这个项目只是练手,所以采用了HTML5个CSS3技术,也不打算兼容IE等低版本浏览器,所以直接使用CSS3提供的Flexbox布局方式。并且使用rem单位来进行自动计算尺寸。

计算计算历史记录显示功能,使用HTML5提供的本地存储功能之Local Storage,为了方便使用Local Storage,对它进行了简单的封装(见js/common.js文件)使之key值按一定规律生产,方便管理。

图片 33

key由appName+id组成,id是自动增长不重复的,可以按id和appName删除一条记录,输入*则全部删除。

打包APP

移动Web版计算器写完后,又想把他做成APP在手机上运行,由于本人没用过混合APP诸如ionic之类的框架,所以参考了一下,选择了Hbuild来进行开发和APP的打包,非常方便。(HBuild).

单手模式

左滑右滑可以切换单手模式,这就需要移动端的touch事件了,使用如下代码判断是左滑还是又滑:

JavaScript

/** 单手模式 */ function singleModel(){ var calc = document.getElementById("calc"); var startX = 0,moveX = 0,distanceX = 0; var distance = 100; var width = calc.offsetWidth; //滑动事件 calc.addEventListener("touchstart",function(e){ startX = e.touches[0].clientX; }); calc.addEventListener("touchmove",function(e){ moveX = e.touches[0].clientX; distanceX = moveX - startX; isMove = true; }); window.addEventListener("touchend",function(e){ if(Math.abs(distanceX) > width/3 && isMove){ if( distanceX > 0 ){ positionFun("right"); //右滑 }else{ positionFun("left"); //作滑 } } startY = moveY = 0; isMove = false; }); }

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
/** 单手模式 */
function singleModel(){
    var calc = document.getElementById("calc");
    var startX = 0,moveX = 0,distanceX = 0;
    var distance = 100;  
    var width = calc.offsetWidth;
    //滑动事件
    calc.addEventListener("touchstart",function(e){
        startX = e.touches[0].clientX;
    });
    calc.addEventListener("touchmove",function(e){
        moveX = e.touches[0].clientX;
        distanceX = moveX - startX;
        isMove = true;
    });
    window.addEventListener("touchend",function(e){
        if(Math.abs(distanceX) > width/3 && isMove){
            if( distanceX > 0 ){
                positionFun("right");        //右滑
            }else{
                positionFun("left");          //作滑
            }
        }
        startY = moveY = 0;
        isMove = false;
    });  
}

如果是坐滑,就position:absolut;left:0,bottom:0,再把最外层DIV缩小到80%,这样就实现了左滑计算器缩小移动到左下角。右滑道理一样。

电话拨打功能

当输入手机号码后长按等于号可以拨打手机号码。这个功能没什么神奇,在移动Web上会对那些看起来像是电话号码的数字处理为电话链接,比如:

  • 7位数字,形如:1234567
  • 带括号及加号的数字,形如:(+86)123456789
  • 双连接线的数字,形如:00-00-00111
  • 11位数字,形如:13800138000

可能还有其他类型的数字也会被识别。我们可以通过如下的meta来开启电话号码的自动识别:

XHTML

<meta name="format-detection" content="telephone=yes" />

1
<meta name="format-detection" content="telephone=yes" />

开启电话功能

XHTML

<a href="tel:123456">123456</a>

1
<a href="tel:123456">123456</a>

开启短信功能:

XHTML

<a href="sms:123456">123456</a>

1
<a href="sms:123456">123456</a>

但是,在Android系统上,只能调用系统的拨号界面,在iOS上则能调过这一步直接把电话拨打出去。

版本更新检查

在关于页面,有一个版本更新检查按钮,就能检查是否有新版本,这个功能的原理是发送一个JSOPN请求去检查服务器上的JSON文件,比对版本号,如果服务器上的版本比APP的版本高则会提示有新版本可以下载。

客户端JavaScript代码:

JavaScript

function updateApp(){ //检查新版本 var updateApp = document.getElementById("updateApp"); updateApp.onclick = function(){ var _this = this; $.ajax({ type:'get', url:'', dataType:'jsonp', beforeSend : function(){ _this.innerHTML = "<i class='iconfont updateAppIcon updateAppIconRotate'></i> 正在检查新版本..."; }, success:function(data){ var newVer = data[0].version; if(newVer > appConfig.version){ var log = data[0].log; var downloadUrl = data[0].downloadUrl; if(confirm("检查到新版本【"+newVer+"】,是否立即下载?n 更新日志:n " + log)){ var a = document.getElementById("telPhone"); a.href = downloadUrl; a.target = "_blank"; a.click(); } }else{ alert("你很潮哦,当前已经是最新版本!"); } _this.innerHTML = "<i class='iconfont updateAppIcon'></i> 检查新版本"; }, error:function(msg){ _this.innerHTML = "<i class='iconfont updateAppIcon'></i> 检查新版本"; alert("检查失败:"+msg.message); } }); } }

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
function updateApp(){
    //检查新版本
    var updateApp = document.getElementById("updateApp");
    updateApp.onclick = function(){
        var _this = this;
        $.ajax({
            type:'get',
            url:'http://duni.sinaapp.com/demo/app.php?jsoncallback=?',
            dataType:'jsonp',
            beforeSend : function(){
                _this.innerHTML = "<i class='iconfont updateAppIcon updateAppIconRotate'></i> 正在检查新版本...";
            },
            success:function(data){
                var newVer = data[0].version;
                if(newVer > appConfig.version){
                    var log = data[0].log;
                    var downloadUrl = data[0].downloadUrl;
                    if(confirm("检查到新版本【"+newVer+"】,是否立即下载?n 更新日志:n " + log)){
                        var a = document.getElementById("telPhone");
                        a.href = downloadUrl;
                        a.target = "_blank";
                        a.click();
                    }
                }else{
                    alert("你很潮哦,当前已经是最新版本!");
                }
                _this.innerHTML = "<i class='iconfont updateAppIcon'></i> 检查新版本";
            },
            error:function(msg){
                _this.innerHTML = "<i class='iconfont updateAppIcon'></i> 检查新版本";
                alert("检查失败:"+msg.message);
            }
        });
    }
}

服务端JSON:

JavaScript

[ { "version":"3.1.0", "downloadUrl":"", "hashCode":"20160812224616", "log":"1.新增切换主题功能 n 2.新增单手切换模式功能 n 3.调整UI " } ]

1
2
3
4
5
6
7
8
[
    {
        "version":"3.1.0",
        "downloadUrl":"http://dunizb.b0.upaiyun.com/demo/app/myCalc-3.1.0.apk",
        "hashCode":"20160812224616",
        "log":"1.新增切换主题功能 n 2.新增单手切换模式功能 n 3.调整UI "
    }
]

3. 视频流协议HLS与RTMP对比

协议 原理 延时 优点 使用场景
HLS 短链接Http 集合一段时间数据生成ts切片文件更新m3u8文件 10s – 30s 跨平台 移动端为主
RTMP 长链接Tcp 每个时刻的数据收到后立即发送 2s 延时低、实时性好 PC+直播+实时性要求高+互动性强

下个版本计划

当前3.1.0版本还存在一些问题:

  1. 由于JS本身存在计算浮点数精度丢失问题,所以这个问题在项目中同意存在,需要自己去处理这个问题
  2. 由于使用了第三方的天气接口,用了jquery.Ajax方法,所以违背了使用纯原生写的初衷。

所以下个版本的开发计划为:

  1. 解决浮点数计算精度问题
  2. 把获取天气信息的jquery.Ajax方法替换为原生JavaScript代码,自己封装JSONP请求函数
  3. 使用面向对象方式重构APP

欢迎大家到github上来看看,如果喜欢可以star、watch一下,或提issue。

4 赞 21 收藏 2 评论

图片 34

本文由澳门新葡亰平台官网发布于web前端,转载请注明出处:编写的计算器应用,浅谈浏览器http的缓存机制

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。