原文出处,禁止转载

2019-09-23 作者:小鱼儿主页高手论坛   |   浏览(193)

迈向PWA!利用serviceworker的离线访问模式

2017/02/08 · JavaScript · PWA

本文作者: 伯乐在线 - pangjian 。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

微信小程序来了,可以利用WEB技术在微信打造一个有着Native应用体验的应用,业界非常看好这种形式。但是你们也许不知道,Google早已有类似的规划,甚至层次更高。那就是PWA(渐进式增强WEB应用)。
PWA有以下几种特性:

  • Installablity(可安装性)
  • App Shell
  • Offline(离线能力)
  • Re-engageable(推送通知能力)

所有这些特性都是“优雅降级、渐进增强的”,给支持的设备更好的体验,不支持的设备也不会更差。这就和微信小程序这种二流设计的根本不同之处。

本博客也在向着PWA的方向迈进,第一步我选择了Offline,也就是离线能力。可以让客户在没有网络连接的时候仍然可以使用部分服务。这个能力利用了Service Worker技术。

实现思路就是,利用service worker,另起一个线程,用来监听所有网络请求,讲已经请求过的数据放入cache,在断网的情况下,直接取用cache里面的资源。为请求过的页面和图片,展示一个默认值。当有网络的时候,再重新从服务器更新。
图片 1
代码这里就不贴了,以后可能会专门写一篇来详细介绍Service Worker,有兴趣的可以直接参考源码。
注册起来也非常方便

JavaScript

// ServiceWorker_js (function() { 'use strict'; navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function(err) { // registration failed :( console.log('ServiceWorker registration failed: ', err); }); })();

1
2
3
4
5
6
7
8
9
10
11
12
// ServiceWorker_js
(function() {
    'use strict';
    navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
      // Registration was successful
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }).catch(function(err) {
      // registration failed :(
      console.log('ServiceWorker registration failed: ', err);
    });
 
})();

这里需要注意的是,sw.js所在的目录要高于它的控制范围,也就是scope。我把sw.js放在了根目录来控制整个目录。

接下来看看我们的最终效果吧,你也可以在自己的浏览器下断网尝试一下。当然有部分浏览器目前还不支持,比如大名鼎鼎的Safari。

前端优化带来的思考,浅谈前端工程化

2015/10/26 · 前端职场 · 2 评论 · 工程化

原文出处: 叶小钗(@欲苍穹)   

前端相关数据监控

2015/08/16 · HTML5 · 数据监控

原文出处: AlloyTeam   

项目开发完成外发后,没有一个监控系统,我们很难了解到发布出去的代码在用户机器上执行是否正确,所以需要建立前端代码性能相关的监控系统。

所以我们需要做以下的一些模块:

一、收集脚本执行错误

JavaScript

function error(msg,url,line){ var REPORT_URL = "xxxx/cgi"; // 收集上报数据的信息 var m =[msg, url, line, navigator.userAgent, new Date];// 收集错误信息,发生错误的脚本文件网络地址,用户代理信息,时间 var url = REPORT_URL m.join('||');// 组装错误上报信息内容URL var img = new Image; img.onload = img.onerror = function(){ img = null; }; img.src = url;// 发送数据到后台cgi } // 监听错误上报 window.onerror = function(msg,url,line){ error(msg,url,line); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function error(msg,url,line){
   var REPORT_URL = "xxxx/cgi"; // 收集上报数据的信息
   var m =[msg, url, line, navigator.userAgent, new Date];// 收集错误信息,发生错误的脚本文件网络地址,用户代理信息,时间
   var url = REPORT_URL m.join('||');// 组装错误上报信息内容URL
   var img = new Image;
   img.onload = img.onerror = function(){
      img = null;
   };
   img.src = url;// 发送数据到后台cgi
}
// 监听错误上报
window.onerror = function(msg,url,line){
   error(msg,url,line);
}

通过管理后台系统,我们可以看到页面上每次错误的信息,通过这些信息我们可以很快定位并且解决问题。

二、收集html5 performance信息

performance 包含页面加载到执行完成的整个生命周期中不同执行步骤的执行时间。performance相关文章点击如下:使用performance API 监测页面性能

计算不同步骤时间相对于navigationStart的时间差,可以通过相应后台CGI收集。

JavaScript

function _performance(){ var REPORT_URL = "xxxx/cgi?perf="; var perf = (window.webkitPerformance ? window.webkitPerformance : window.msPerformance ), points = ['navigationStart','unloadEventStart','unloadEventEnd','redirectStart','redirectEnd','fetchStart','domainLookupStart','connectStart','requestStart','responseStart','responseEnd','domLoading','domInteractive','domContentLoadedEventEnd','domComplete','loadEventStart','loadEventEnd']; var timing = pref.timing; perf = perf ? perf : window.performance; if( perf && timing ) { var arr = []; var navigationStart = timing[points[0]]; for(var i=0,l=points.length;i<l;i ){ arr[i] = timing[points[i]] - navigationStart; } var url = REPORT_URL arr.join("-"); var img = new Image; img.onload = img.onerror = function(){ img=null; } img.src = url; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function _performance(){
   var REPORT_URL = "xxxx/cgi?perf=";
   var perf = (window.webkitPerformance ? window.webkitPerformance : window.msPerformance ),
      points = ['navigationStart','unloadEventStart','unloadEventEnd','redirectStart','redirectEnd','fetchStart','domainLookupStart','connectStart','requestStart','responseStart','responseEnd','domLoading','domInteractive','domContentLoadedEventEnd','domComplete','loadEventStart','loadEventEnd'];
   var timing = pref.timing;
   perf = perf  ? perf : window.performance;
   if( perf  && timing ) {
      var arr = [];
      var navigationStart = timing[points[0]];
      for(var i=0,l=points.length;i<l;i ){
         arr[i] = timing[points[i]] - navigationStart;
      }
   var url = REPORT_URL arr.join("-");
   var img = new Image;
   img.onload = img.onerror = function(){
      img=null;
   }
   img.src = url;
}

通过后台接口收集和统计,我们可以对页面执行性能有很详细的了解。

三、统计每个页面的JS和CSS加载时间

在JS或者CSS加载之前打上时间戳,加载之后打上时间戳,并且将数据上报到后台。加载时间反映了页面白屏,可操作的等待时间。

XHTML

<script>var cssLoadStart = new Date</script> <link rel="stylesheet" href="xxx.css" type="text/css" media="all"> <link rel="stylesheet" href="xxx1.css" type="text/css" media="all"> <link rel="stylesheet" href="xxx2.css" type="text/css" media="all"> <sript> var cssLoadTime = ( new Date) - cssLoadStart; var jsLoadStart = new Date; </script> <script type="text/javascript" src="xx1.js"></script> <script type="text/javascript" src="xx2.js"></script> <script type="text/javascript" src="xx3.js"></script> <script> var jsLoadTime = ( new Date) - jsLoadStart; var REPORT_URL = 'xxx/cgi?data=' var img = new Image; img.onload = img.onerror = function(){ img = null; }; img.src = REPORT_URL cssLoadTime '-' jsLoadTime; </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>var cssLoadStart = new Date</script>
<link rel="stylesheet" href="xxx.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx1.css" type="text/css" media="all">
<link rel="stylesheet" href="xxx2.css" type="text/css" media="all">
<sript>
   var cssLoadTime = ( new Date) - cssLoadStart;
   var jsLoadStart = new Date;
</script>
<script type="text/javascript" src="xx1.js"></script>
<script type="text/javascript" src="xx2.js"></script>
<script type="text/javascript" src="xx3.js"></script>
<script>
   var jsLoadTime = ( new Date) - jsLoadStart;
   var REPORT_URL = 'xxx/cgi?data='
   var img = new Image;
   img.onload = img.onerror = function(){
      img = null;
   };
   img.src = REPORT_URL cssLoadTime '-' jsLoadTime;
</script>

XHTML

<a href="" target="_blank"> </a>

1
<a href="https://github.com/perltzhu/js-data-report" target="_blank"> </a>

HTML5实现屏幕手势解锁

2015/07/18 · HTML5 · 1 评论 · 手势解锁

原文出处: AlloyTeam   

效果展示

图片 2

实现原理 利用HTML5的canvas,将解锁的圈圈划出,利用touch事件解锁这些圈圈,直接看代码。

JavaScript

function createCircle() {// 创建解锁点的坐标,根据canvas的大小来平均分配半径 var n = chooseType;// 画出n*n的矩阵 lastPoint = []; arr = []; restPoint = []; r = ctx.canvas.width / (2 4 * n);// 公式计算 半径和canvas的大小有关 for (var i = 0 ; i < n ; i ) { for (var j = 0 ; j < n ; j ) { arr.push({ x: j * 4 * r 3 * r, y: i * 4 * r 3 * r }); restPoint.push({ x: j * 4 * r 3 * r, y: i * 4 * r 3 * r }); } } //return arr; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function createCircle() {// 创建解锁点的坐标,根据canvas的大小来平均分配半径
 
        var n = chooseType;// 画出n*n的矩阵
        lastPoint = [];
        arr = [];
        restPoint = [];
        r = ctx.canvas.width / (2 4 * n);// 公式计算 半径和canvas的大小有关
        for (var i = 0 ; i < n ; i ) {
            for (var j = 0 ; j < n ; j ) {
                arr.push({
                    x: j * 4 * r 3 * r,
                    y: i * 4 * r 3 * r
                });
                restPoint.push({
                    x: j * 4 * r 3 * r,
                    y: i * 4 * r 3 * r
                });
            }
        }
        //return arr;
    }

canvas里的圆圈画好之后可以进行事件绑定

JavaScript

function bindEvent() { can.addEventListener("touchstart", function (e) { var po = getPosition(e); console.log(po); for (var i = 0 ; i < arr.length ; i ) { if (Math.abs(po.x - arr[i].x) < r && Math.abs(po.y - arr[i].y) < r) { // 用来判断起始点是否在圈圈内部 touchFlag = true; drawPoint(arr[i].x,arr[i].y); lastPoint.push(arr[i]); restPoint.splice(i,1); break; } } }, false); can.addEventListener("touchmove", function (e) { if (touchFlag) { update(getPosition(e)); } }, false); can.addEventListener("touchend", function (e) { if (touchFlag) { touchFlag = false; storePass(lastPoint); setTimeout(function(){ init(); }, 300); } }, 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
28
29
30
31
32
function bindEvent() {
        can.addEventListener("touchstart", function (e) {
             var po = getPosition(e);
             console.log(po);
             for (var i = 0 ; i < arr.length ; i ) {
                if (Math.abs(po.x - arr[i].x) < r && Math.abs(po.y - arr[i].y) < r) { // 用来判断起始点是否在圈圈内部
 
                    touchFlag = true;
                    drawPoint(arr[i].x,arr[i].y);
                    lastPoint.push(arr[i]);
                    restPoint.splice(i,1);
                    break;
                }
             }
         }, false);
         can.addEventListener("touchmove", function (e) {
            if (touchFlag) {
                update(getPosition(e));
            }
         }, false);
         can.addEventListener("touchend", function (e) {
             if (touchFlag) {
                 touchFlag = false;
                 storePass(lastPoint);
                 setTimeout(function(){
 
                    init();
                }, 300);
             }
 
         }, false);
    }

接着到了最关键的步骤绘制解锁路径逻辑,通过touchmove事件的不断触发,调用canvas的moveTo方法和lineTo方法来画出折现,同时判断是否达到我们所画的圈圈里面,其中lastPoint保存正确的圈圈路径,restPoint保存全部圈圈去除正确路径之后剩余的。 Update方法:

JavaScript

function update(po) {// 核心变换方法在touchmove时候调用 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); for (var i = 0 ; i < arr.length ; i ) { // 每帧先把面板画出来 drawCle(arr[i].x, arr[i].y); } drawPoint(lastPoint);// 每帧花轨迹 drawLine(po , lastPoint);// 每帧画圆心 for (var i = 0 ; i < restPoint.length ; i ) { if (Math.abs(po.x - restPoint[i].x) < r && Math.abs(po.y - restPoint[i].y) < r) { drawPoint(restPoint[i].x, restPoint[i].y); lastPoint.push(restPoint[i]); restPoint.splice(i, 1); break; } } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function update(po) {// 核心变换方法在touchmove时候调用
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
 
        for (var i = 0 ; i < arr.length ; i ) { // 每帧先把面板画出来
            drawCle(arr[i].x, arr[i].y);
        }
 
        drawPoint(lastPoint);// 每帧花轨迹
        drawLine(po , lastPoint);// 每帧画圆心
 
        for (var i = 0 ; i < restPoint.length ; i ) {
            if (Math.abs(po.x - restPoint[i].x) < r && Math.abs(po.y - restPoint[i].y) < r) {
                drawPoint(restPoint[i].x, restPoint[i].y);
                lastPoint.push(restPoint[i]);
                restPoint.splice(i, 1);
                break;
            }
        }
 
    }

最后就是收尾工作,把路径里面的lastPoint保存的数组变成密码存在localstorage里面,之后就用来处理解锁验证逻辑了

JavaScript

function storePass(psw) {// touchend结束之后对密码和状态的处理 if (pswObj.step == 1) { if (checkPass(pswObj.fpassword, psw)) { pswObj.step = 2; pswObj.spassword = psw; document.getElementById('title').innerHTML = '密码保存成功'; drawStatusPoint('#2CFF26'); window.localStorage.setItem('passwordx', JSON.stringify(pswObj.spassword)); window.localStorage.setItem('chooseType', chooseType); } else { document.getElementById('title').innerHTML = '两次不一致,重新输入'; drawStatusPoint('red'); delete pswObj.step; } } else if (pswObj.step == 2) { if (checkPass(pswObj.spassword, psw)) { document.getElementById('title').innerHTML = '解锁成功'; drawStatusPoint('#2CFF26'); } else { drawStatusPoint('red'); document.getElementById('title').innerHTML = '解锁失败'; } } else { pswObj.step = 1; pswObj.fpassword = psw; document.getElementById('title').innerHTML = '再次输入'; } }

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
function storePass(psw) {// touchend结束之后对密码和状态的处理
        if (pswObj.step == 1) {
            if (checkPass(pswObj.fpassword, psw)) {
                pswObj.step = 2;
                pswObj.spassword = psw;
                document.getElementById('title').innerHTML = '密码保存成功';
                drawStatusPoint('#2CFF26');
                window.localStorage.setItem('passwordx', JSON.stringify(pswObj.spassword));
                window.localStorage.setItem('chooseType', chooseType);
            } else {
                document.getElementById('title').innerHTML = '两次不一致,重新输入';
                drawStatusPoint('red');
                delete pswObj.step;
            }
        } else if (pswObj.step == 2) {
            if (checkPass(pswObj.spassword, psw)) {
                document.getElementById('title').innerHTML = '解锁成功';
                drawStatusPoint('#2CFF26');
            } else {
                drawStatusPoint('red');
                document.getElementById('title').innerHTML = '解锁失败';
            }
        } else {
            pswObj.step = 1;
            pswObj.fpassword = psw;
            document.getElementById('title').innerHTML = '再次输入';
        }
 
    }

解锁组件

将这个HTML5解锁写成了一个组件,放在

二维码体验: 图片 3

 

参考资料:

1 赞 4 收藏 1 评论

图片 4

离线有缓存情况

图片 5

重复优化的思考

这段时间对项目做了一次整体的优化,全站有了20%左右的提升(本来载入速度已经1.2S左右了,优化度很低),算一算已经做了四轮的全站性能优化了,回顾几次的优化手段,基本上几个字就能说清楚:

传输层面:减少请求数,降低请求量 执行层面:减少重绘&回流

1
2
传输层面:减少请求数,降低请求量
执行层面:减少重绘&回流

传输层面的从来都是优化的核心点,而这个层面的优化要对浏览器有一个基本的认识,比如:

① 网页自上而下的解析渲染,边解析边渲染,页面内CSS文件会阻塞渲染,异步CSS文件会导致回流

② 浏览器在document下载结束会检测静态资源,新开线程下载(有并发上限),在带宽限制的条件下,无序并发会导致主资源速度下降,从而影响首屏渲染

③ 浏览器缓存可用时会使用缓存资源,这个时候可以避免请求体的传输,对性能有极大提高

衡量性能的重要指标为首屏载入速度(指页面可以看见,不一定可交互),影响首屏的最大因素为请求,所以请求是页面真正的杀手,一般来说我们会做这些优化:

参考资料:

  • html5 performance en
  • html5 performance cn
  • javascript onerror api

    1 赞 1 收藏 评论

图片 6

离线无缓存情况

会展示一个默认的页面

图片 7

-EOF-

打赏支持我写出更多好文章,谢谢!

打赏作者

减少请求数

① 合并样式、脚本文件

② 合并背景图片

③ CSS3图标、Icon Font

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

图片 8 图片 9

1 赞 1 收藏 评论

降低请求量

① 开启GZip

② 优化静态资源,jQuery->Zepto、阉割IScroll、去除冗余代码

③ 图片无损压缩

④ 图片延迟加载

⑤ 减少Cookie携带

很多时候,我们也会采用类似“时间换空间、空间换时间”的做法,比如:

① 缓存为王,对更新较缓慢的资源&接口做缓存(浏览器缓存、localsorage、application cache这个坑多)

② 按需加载,先加载主要资源,其余资源延迟加载,对非首屏资源滚动加载

③ fake页技术,将页面最初需要显示Html&Css内联,在页面所需资源加载结束前至少可看,理想情况是index.html下载结束即展示(2G 5S内)

④ CDN

……

从工程的角度来看,上述优化点半数以上是重复的,一般在发布时候就直接使用项目构建工具做掉了,还有一些只是简单的服务器配置,开发时不需要关注。

可以看到,我们所做的优化都是在减少请求数,降低请求量,减小传输时的耗时,或者通过一个策略,优先加载首屏渲染所需资源,而后再加载交互所需资源(比如点击时候再加载UI组件),Hybrid APP这方面应该尽可能多的将公共静态资源放在native中,比如第三方库,框架,UI甚至城市列表这种常用业务数据。

关于作者:pangjian

图片 10

庞健,金融IT男。 个人主页 · 我的文章 · 5 ·   

图片 11

拦路虎

有一些网站初期比较快,但是随着量的积累,BUG越来越多,速度也越来越慢,一些前端会使用上述优化手段做优化,但是收效甚微,一个比较典型的例子就是代码冗余:

① 之前的CSS全部放在了一个文件中,新一轮的UI样式优化,新老CSS难以拆分,CSS体量会增加,如果有业务团队使用了公共样式,情况更不容乐观;

② UI组件更新,但是如果有业务团队脱离接口操作了组件DOM,将导致新组件DOM更新受限,最差的情况下,用户会加载两个组件的代码;

③ 胡乱使用第三方库、组件,导致页面加载大量无用代码;

……

以上问题会不同程度的增加资源下载体量,如果听之任之会产生一系列工程问题:

① 页面关系错综复杂,需求迭代容易出BUG;

② 框架每次升级都会导致额外的请求量,常加载一些业务不需要的代码;

③ 第三方库泛滥,且难以维护,有BUG也改不了;

④ 业务代码加载大量异步模块资源,页面请求数增多;

……

为求快速占领市场,业务开发时间往往紧迫,使用框架级的HTML&CSS、绕过CSS Sprite使用背景图片、引入第三方工具库或者UI,会经常发生。当遇到性能瓶颈时,如果不从根源解决问题,用传统的优化手段做页面级别的优化,会出现很快页面又被玩坏的情况,几次优化结束后我也在思考一个问题:

前端每次性能优化的手段皆大同小异;代码的可维护性也基本是在细分职责; 既然每次优化的目的是相同的,每次实现的过程是相似的,而每次重新开发项目又基本是要重蹈覆辙的,那么工程化、自动化可能是这一切问题的最终答案

1
2
前端每次性能优化的手段皆大同小异;代码的可维护性也基本是在细分职责;
既然每次优化的目的是相同的,每次实现的过程是相似的,而每次重新开发项目又基本是要重蹈覆辙的,那么工程化、自动化可能是这一切问题的最终答案

工程问题在项目积累到一定量后可能会发生,一般来说会有几个现象预示着工程问题出现了:

① 代码编写&调试困难

② 业务代码不好维护

③ 网站性能普遍不好

④ 性能问题重复出现,并且有不可修复之势

像上面所描述情况,就是一个典型的工程问题;定位问题、发现问题、解决问题是我们处理问题的手段;而如何防止同一类型的问题重复发生,便是工程化需要做的事情,简单说来,优化是解决问题,工程化是避免问题,今天我们就站在工程化的角度来解决一些前端优化问题,防止其死灰复燃。

文中是我个人的一些开发经验,希望对各位有用,也希望各位多多支持讨论,指出文中不足以及提出您的一些建议

消灭冗余

我们这里做的第一个事情便是消除优化路上第一个拦路虎:代码冗余(做代码精简),单从一个页面的加载来说,他需要以下资源:

① 框架MVC骨架模块&框架级别CSS

② UI组件(header组件、日历、弹出层、消息框……)

③ 业务HTML骨架

④ 业务CSS

⑤ 业务Javascript代码

⑥ 服务接口服务

因为产品&视觉会经常折腾全站样式加之UI的灵活性,UI最容易产生冗余的模块。

UI组件

UI组件本身包括完整的HTML&CSS&Javascript,一个复杂的组件下载量可以达到10K以上,就UI部分来说容易导致两个工程化问题:

① 升级产生代码冗余

② 对外接口变化导致业务升级需要额外开发

本文由小鱼儿玄机30码发布于小鱼儿主页高手论坛,转载请注明出处:原文出处,禁止转载

关键词: 小鱼儿玄机30码