打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
基于 HTML5 构建 Web 操作系统
胡 睿
2012 年 9 月 26 日发布

HTML5 是下一代 web 语言的标准,具有兼容性好,安全性高,功能丰富,开发便捷等优点,特别适合如 Web 操作系统一类的富客户端互联网应用的前端开发。本文将展示如何利用 HTML5 提供的多种新技术如:本地数据库、多线程开发、视频支持、离线编程等构建一个基本的 Web 操作系统。

简介

传统的操作系统有着一些难以克服的缺点,如仅能在本地终端访问,或仅支持有限的远程访问,限于本地终端的资源,计算能力薄弱,存储空间有限,缺乏强大的防火墙等一系列安全机制,安全性较差。鉴于以上缺点,Web 操作系统应运而生 - Web 操作系统是一种基于浏览器的虚拟的操作系统,用户通过浏览器可以在其中进行应用程序的操作,以及相关数据的存储。Web 操作系统提供的基本服务有文本文档的创建与存储,音频视频文件的播放与存储,提供对时间信息的支持等,更高级的服务则包含即时通信,邮件甚至游戏等服务。Web 操作系统克服了传统操作系统的缺点,在网络的支持下,它可以在任何时间,任何地点经由任何支持 Web 的终端进行访问,可以利用服务器端无限的计算及存储资源,用户数据保存在服务器端,安全性较高。

相关技术

目前构建 Web 操作系统的前端技术主要有 Flex、Silverlight、ActiveX 插件等等,它们各有一些优缺点。

Flex

Flex 是一个优秀的富客户端应用框架,专注于页面显示,Adobe 专业维护,统一稳定,而且其脚本语言 ActionScript3 是面向对象的,非常适合程序员使用。缺点则是耗能高,占用带宽多,对移动应用的支持性差。

Silverlight

Silverlight 是由微软推出的用以跟 Flash 抗衡的 RIA(富互联网应用)解决方案,优点是具备硬件级的加速功能,但它目前仍不成熟,对非 Windows 系统的支持性并不够好,且学习难度较大。

ActiveX 插件

ActiveX 插件同样是微软推出的 RIA 解决方案,它是一个开放的解决方案,可以兼容多种语言,不过它的缺点也是显而易见的,用户需要调整浏览器的安全等级并下载插件才能运行 RIA 应用,极大地降低了安全性。

HTML5

为推动 web 标准化运动的发展,W3C 推出了下一代 HTML 的标准 - HTML5,为众多的公司所支持,因此具有良好的前景。它有以下特点:首先,为增强用户体验,强化了 web 网页的表现性能;其次,为适应 RIA 应用的发展,追加了本地数据库等 web 应用的功能;再次,由于高度标准化以及诸多浏览器厂商的大力支持,它的兼容性和安全性非常高;最后它是一种简洁的语言,容易为广大开发者掌握。更为难得的是,由于节能和功耗低,在移动设备上 HTML5 将具有更大的优势。因此更适合如 Web 操作系统一类的 RIA 应用的前端开发。

系统简介

本系统基于 HTML5 开发,利用 HTML5 引入的多种新技术如拖拽 API、视频标签、本地数据库、draw API、多线程开发、离线编程等提供了一个基本的 Web 操作系统环境,包含了对桌面的支持、应用程序的支持,提供了一个简单的视频播放器和记事本以及一个时钟,并对系统日志进行了记录,此外还提供了对离线状态的支持。

桌面实现

系统对桌面的支持主要包括应用程序图标的打开与拖拽,以及桌面的上下文菜单等。

桌面拖拽

桌面的布局由一定数量的 div 组成,它们按照次序依次排列在矩形的桌面上,为应用程序图标的打开与拖拽提供了基本的支持。

清单 1. 创建 div
1
2
3
4
var iconHolder = document.createElement("div");
iconHolder.id = 'iconHolder' + i;
iconHolder.className = "iconHolder";
mainDiv.appendChild(iconHolder);

HTML5 提供了对 drag 事件的支持,大大简化了实现拖拽的难度。通过对 dragstart 事件的监听,将被拖拽的应用程序图标所在的 div 记录下来,作为拖拽的源。

清单 2. 拖拽支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
iconHolder.addEventListener("dragstart", function(ev) {
var dt = ev.dataTransfer;
dt.setData("text/plain", ev.currentTarget.id);// 记录被拖拽图标的 id
}, false);
iconHolder.addEventListener("drop", function(ev) {
var dt = ev.dataTransfer;
var srcIconHolderId = dt.getData("text/plain");
var srcIconHolder = document.getElementById(srcIconHolderId);
// 如果拖拽至回收站,则删掉被拖拽图标,否则互换两图标位置
if(ev.currentTarget.firstChild && ev.currentTarget.firstChild.id == "recycleBin" &&
srcIconHolder.firstChild.id != "recycleBin"){
               srcIconHolder.innerHTML = "";
}else if(ev.currentTarget.firstChild){
       var temp =  ev.currentTarget.firstChild;
       ev.currentTarget.appendChild(srcIconHolder.firstChild);
       srcIconHolder.appendChild(temp);
}else{
      ev.currentTarget.appendChild(srcIconHolder.firstChild);
}
}, false);

通过对 drop 事件的监听,可以获取拖拽的源,以及拖拽的目标 div。若目标 div 为空,则将源 div 中的应用程序图标转移至目的 div 中。若目标 div 中已包含应用程序图标,则将两个图标的位置互换。若回收站图标处于目标 div 中,回收站将发挥作用并将源 div 中的应用程序图标删除。图 1 显示了桌面拖拽的效果。

图 1. 桌面拖拽效果

程序打开

程序可以以两种方式打开,左键点击或通过上下文菜单打开。

通过监听 div 的 onclick 事件,获取要打开的应用程序 id,并利用 openApp 方法打开相应的应用程序可实现对左键点击的支持。

清单 3. 左键点击
1
2
3
4
5
6
iconHolder.onclick =  function(ev){
if(ev.currentTarget.firstChild){
       openApp(ev.currentTarget.firstChild.id);
       ev.stopPropagation();
}
};

通过监听 div 的 oncontextmenu 事件,获取要打开的应用程序 id,并利用 openAppContextMenu 方法显示相应应用程序的上下文菜单,可实现对右键上下文菜单的支持。

清单 4. 上下文菜单
1
2
3
4
5
6
7
iconHolder.oncontextmenu =  function(ev){
if(ev.currentTarget.firstChild){
       openAppContextMenu(ev.currentTarget.firstChild.id, ev);
       ev.stopPropagation();
}
return false;
};

利用相应应用程序的 id,可以获取对应应用程序的脚本,并执行,同时在系统日志中记录下相应的操作。

清单 5. 程序打开
1
2
3
4
5
6
7
8
function openApp(appId){
   var time = new Date().getTime();
   var action = "open app";
   var details = "open: " + appId;
   addHistory(time, action, details);// 记录系统日志
   var appScript = getAppScript(appId);// 获取应用程序脚本
   eval(appScript);// 执行应用程序
}
清单 6. 打开程序上下文菜单
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 openAppContextMenu(appId, ev){
var appContextMenu = document.getElementById("appContextMenu");
appContextMenu.style.display="block";// 令上下文菜单可见
appContextMenu.style.pixelTop=ev.clientY;// 设置上下文菜单位置
appContextMenu.style.pixelLeft=ev.clientX;
appContextMenu.style.background = "#eee";
appContextMenu.style.color = "black";
appContextMenu.style.fontSize = "30";
appContextMenu.style.width = "200px";
appContextMenu.style.height = "220px";
appContextMenu.style.opacity = 0.5;// 令上下文菜单透明度为 50%
appContextMenu.innerHTML = "";
// 获取应用程序相应上下文菜单的内容
var apps = getApps();
for(var i=0; i<apps.length; i++){
               if(apps[i].appId == appId){
                       for(var j=0; j<apps[i].contextMenu.length; j++){
                       appContextMenu.innerHTML += "<div class='appContextMenuItem'
                       onclick=\"appContextMenu.style.display='none';" +
                       apps[i].contextMenu[j].action + "\"
                       onmouseover='this.style.background=\"darkblue\"'
                       onmouseout='this.style.background=\"#eee\"'>"
                       +apps[i].contextMenu[j].name+"</div>";
                       }
                       break;
                }  
}
}

应用程序的上下文菜单由名为 appContextMenu 的 div 实现,将 oncontextmenu 事件中的 clientX 及 clientY 作为上下文菜单出现的位置,并将其透明度设置为 0.5。利用相应应用程序的 id 获取上下文菜单对应的内容,并将其填充至上下文菜单。

图 2 显示了应用程序上下文菜单打开时的效果。

图 2. 应用程序上下文菜单

上下文菜单

桌面上下文菜单的实现方式与应用程序上下文菜单的实现方式基本类似,图 3 和图 4 分别是桌面以及任务栏的上下文菜单。

图 3. 桌面上下文菜单
图 4. 任务栏上下文菜单

视频播放器

系统提供了一个简单的视频播放器,它支持从系统外部拖拽视频文件进行播放。

顺应网络媒体的发展,HTML5 提供了视频标签 video 以便于加强对视频的支持,大大简化了 web 播放器开发的难度,开发人员仅凭几行代码,就可以开发出一个基本功能完善的视频播放器。

清单 7. 视频标签的创建
1
2
3
4
5
6
7
8
9
var video = document.createElement('video');
video.id ='video';
video.src ='';
video.width  = 370;
video.height = 260;
video.controls = 'controls';
video.className = 'video';
appHolder.appendChild(video);
addDragSupport(appHolder);

清单 7 中构造了一个 video 标签并将其添加到一个名为 appHolder 的 div 中。代码的最后一行为其添加了拖拽的支持。

HTML5 不但支持浏览器内的拖拽,也支持浏览器与本地系统之间的拖拽。清单 8 显示了为一个 div 添加拖拽支持的过程。

清单 8. 添加拖拽支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function addDragSupport(dropbox){
document.addEventListener("dragenter", function(e){
}, false);
document.addEventListener("dragleave", function(e){
}, false);
dropbox.addEventListener("dragenter", function(e){
}, false);
dropbox.addEventListener("dragleave", function(e){
}, false);
dropbox.addEventListener("dragenter", function(e){
e.stopPropagation();
e.preventDefault();
}, false);
dropbox.addEventListener("dragover", function(e){
e.stopPropagation();
e.preventDefault();
}, false);
dropbox.addEventListener("drop", function(e){
handleFiles(e.dataTransfer.files, e.currentTarget, e);
e.stopPropagation();
e.preventDefault();             
}, false); 
}

其中,handleFiles 函数说明了如何对拖拽的文件进行处理。

清单 9. 拖拽处理
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 handleFiles(files, dropbox, e) {
   if(files.length == 0){// 若文件不存在,则用相应文本代替
        var dt = e.dataTransfer;
        var text = dt.getData("text/plain");
        var p = document.createElement("p");
        p.innerHTML += text;
        dropbox.appendChild(p);
        return;
}
for (var i = 0; i < files.length; i++) {
        var file = files[i];
        var fileProcessor = dropbox.firstChild;
        fileProcessor.classList.add("obj");
        fileProcessor.file = file; // 添加文件
       
        var reader = new FileReader();
        reader.onload = (// 读取文件内容
        function(aFileProcessor) {
                return function(e) {
                aFileProcessor.src = e.target.result;
};
}
)(fileProcessor);
 reader.readAsDataURL(file);
}
}

handleFiles 函数首先判断文件是否存在,若不存在,则以相应文字取代,若存在,则对

所有文件一一进行处理。向 fileprocessor( 这里是视频标签 ) 添加文件,然后利用 FileReader 读取文件内容至 fileprocessor 进行处理。

图 5 显示了拖拽一个视频文件 movie.ogg 到播放器的效果。

图 5. 视频播放

本地存储

Web 操作系统通常将大部分数据存储于服务器端,这样做的好处显而易见,数据存储空间更大,安全性更好。然而这样做也有不足之处,由于网络的稳定性依然较本地磁盘差,所以在脱离网络的状况下,Web 操作系统无法获取相应的数据资源,因此 Web 操作系统需要一定的访问本地存储空间的能力,当然本地存储空间仅是作为服务器端存储的一个补充,它的空间有限,访问也受到一定的限制。

一直以来,HTML 以 Cookie 作为访问本地空间的方式,然而,这种方式有着很多缺点和不足,如存储的数据格式过于简单,通常仅为键值对;存储的空间大小有限。为此,HTML5 提供了本地数据库以增强本地存储空间的访问能力,它是一个简化版的数据库,能够支持模拟的 SQL 以及简单的事务处理等功能。

系统为支持本地存储,创建了一个名为 MyData 的数据库。清单 10 显示了数据库创建的过程。

清单 10. 创建数据库
1
2
3
4
var db;
var openDatabase;
if(openDatabase != undefined)
    db = openDatabase('MyData', '', 'My Database', 102400);

其中 MyData 为数据库的名称,省略的参数为数据库的版本,My Database 为显示的名称,最后的数字为数据库预估长度(以字节为单位)。

系统日志将系统在某一时间的行为操作记录下来,本地数据库为其提供存储支持。日志在数据库中存储为表 History,包含 3 个字段,分别为时间,操作,及操作的详细信息。清单 11 显示了系统是如何记录日志的。

清单 11. 日志记录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var time = new Date().getTime(); 
var action = "open app";
var details = "open: " + appId;
addHistory(time, action, details);// 向系统日志中添加一条记录
function addHistory(time, action, details){
if(openDatabase != undefined)
db.transaction(
function(tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS History(time INTEGER,
action TEXT, details TEXT)',[]);// 创建日志记录表 
tx.executeSql('INSERT INTO History VALUES(?, ?, ?)', [time,
action, details], // 插入一条日志
function(tx, rs) { 
//alert("store: "+time+"-"+action+"-"+details);  
             }, 
function(tx, error) {
   //alert(error.source + "::" + error.message); 
}); 
}); 
}

清单的第一部分显示了如何调用日志记录,第二部分显示了日志记录的详细过程。在一个 transaction 中,首先判断表 History 是否存在,若不存在,则创建它。第二部分执行一条 SQL 语句,向数据库中插入当前的日志。

通过检索表 History,我们可以查看系统日志,清单 12 显示了如何从数据库中查询系统日志,并将其显示出来。

清单 12. 日志显示
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
var historyTable = document.getElementById("historyTable");
 // 定义表头
 historyTable.innerHTML = "";
 var th = document.createElement('thead');
 th.style = "color:#CC3300";
 var th1 = document.createElement('td');
 th1.align = "center";
 th1.width=300;
 th1.innerHTML = "Time";
 var th2 = document.createElement('td');
 th2.align = "center";
 th2.width=100;
 th2.innerHTML = "Action";
 var th3 = document.createElement('td');
 th3.align = "center";
 th3.width=150;
 th3.innerHTML = "Details";
 th.appendChild(th1); 
 th.appendChild(th2); 
 th.appendChild(th3);
 historyTable.appendChild(th);
                
 if(openDatabase != undefined)
 db.transaction(function(tx) {   
 tx.executeSql('SELECT * FROM History', [], function(tx, rs)
 
      // 将日志逐条显示到表的各行中
 for(var i = 0; i < rs.rows.length && i<15; i++) {                   
 var tr = document.createElement('tr');
 var td1 = document.createElement('td');
 td1.style.paddingLeft = "3px";
 td1.style.paddingRight = "3px";
                     
 var t = new Date(); 
 t.setTime(rs.rows.item(i).time); 
 td1.innerHTML = t.toLocaleDateString()+
" "+t.toLocaleTimeString();
  
 var td2 = document.createElement('td'); 
 td2.style.paddingLeft = "3px";
 td2.style.paddingRight = "3px";
 td2.innerHTML = rs.rows.item(i).action;
                      
 var td3 = document.createElement('td');
 td3.style.paddingLeft = "3px";
 td3.style.paddingRight = "3px";
 td3.innerHTML = rs.rows.item(i).details; 
                       
 tr.appendChild(td1); 
 tr.appendChild(td2); 
 tr.appendChild(td3);
                       
 historyTable.appendChild(tr);                  
 
 }); 
 });

清单 12 中,首先获取用于显示的日志的 HTML 表格 historyTable,并设置其样式及表头。

然后在一个 transaction( 事务 ) 中,执行一条 SQL 语句,查询系统日志,并将每条日志添加为 historyTable 中的一行以便显示。图 6 显示了系统日志的效果。

图 6. 系统日志

记事本

系统提供了一个简单的记事本,实现了文本文档的基本操作。文本文档包含标题和内容两个显式属性,以及一个名为 id 的隐式属性。与系统日志类似,本地数据库为文本数据的存储提供了底层的支持。图 7 显示了记事本程序的界面。

图 7. 记事本

当编辑完文档的标题与内容后,点击左上角的保存按钮,将执行 createFile 函数。清单 13 显示了 createFile 函数的详细过程。

清单 13. 创建文件
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
function createFile(fileId, fileTitle, fileContent){
    var idx = 1;
    var update = false;//false 表示新建,true 表示修改
    if(openDatabase != undefined)
        db.transaction(function(tx) {
        tx.executeSql('CREATE TABLE IF NOT EXISTS TextFiles(idx INTEGER,
        title TEXT, content TEXT)',[]);// 创建文本文档表
        tx.executeSql('SELECT * FROM TextFiles', [], function(tx, rs){
            for(var i = 0; i < rs.rows.length; i++) {
               // 若文档存在,则修改它
                if(rs.rows.item(i).idx == fileId){
                    db.transaction(function(tx) {   
                    tx.executeSql('UPDATE TextFiles
                    SET title=?, content=?
                    WHERE idx='+fileId,
                    [fileTitle, fileContent],
                    function(tx, rs) {  
                            alert("update successfully");
                    }); 
                });
                return;
            }        
}   
// 若文档不存在,则新建一个文档        
if(rs.rows.length>0)
idx = rs.rows.item(rs.rows.length-1).idx + 1;
db.transaction(function(tx) {                        
tx.executeSql('INSERT INTO TextFiles VALUES(?, ?, ?)', [idx, fileTitle, fileContent],
              function(tx, rs){ 
              alert("save successfully: "+idx+"-"+fileTitle+ "-"+fileContent); 
              createFileIcon(idx); 
}, 
function(tx, error) {
               alert(error.source + "::" + error.message); 
                }); 
            });
        });
    });
}

清单 13 首先在一个 transaction 中,首先判断用于存储文本文档的表 TextFiles 是否存在,若不存在,则创建它。然后通过查询表 TextFiles 判断文本文档是否存在,若存在,则当前操作为更新操作,程序将执行一条 SQL 语句,对当前文本文档进行更新。若不存在,则取当前最大文档 id 并加 1 作为新文档的 id,并执行一条 SQL 语句,将文档信息,包括文档 id,以及标题和内容插入到数据库中,并于插入操作结束后的回调方法中,利用 createFileIcon 方法在桌面上为新文档创建一个文档图标。清单 14 显示了 createFileIcon 方法的具体过程。

清单 14. 创建文档图标
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
function createFileIcon(fileId){
    var iconHolder;
    for(var i=1;i<=120;i++){// 查询第一个为空的位置
        iconHolder = document.getElementById('iconHolder' + if(!iconHolder.firstChild ){
            var text = document.createElement('img');
            text.src = "images/text.gif";
            text.id = fileId;
            iconHolder.appendChild(text);
            text.onclick =  function(ev){ 
                if(ev.currentTarget){
                openApp('notebook');// 打开记事本应用程序
                var saveHolder = document.getElementById('saveHolder');
                saveHolder.onclick  = function(){
                    var title = document.getElementById('title');
                    var content = document.getElementById('content');
                    createFile(fileId, title.value, content.value);// 创建文本文档
                };
            var openedFileId = ev.currentTarget.id;
            if(openDatabase != undefined)
            db.transaction(function(tx) {// 查询数据库,显示文档内容
            tx.executeSql('SELECT * FROM TextFiles', [], function(tx, rs){
                for(var i = 0; i < rs.rows.length; i++) { 
                if((rs.rows.item(i).idx+"") == (openedFileId+"")){
                    var title = document.getElementById('title');
                    var content = document.getElementById('content');          
                    title.value = rs.rows.item(i).title;                  
                    content.value = rs.rows.item(i).content;}    
                             }
               });
});
  ev.stopPropagation();
}
};
break;
}    
}//for
}

清单 14 首先在桌面中寻找一个空的 div,然后创建一个文档图标,并将其填充至 div。文档图标有一个 id 属性对应文档 id。最后为文档图标添加点击事件处理函数,当点击文档图标时,会首先打开记事本,然后根据文档图标的 id 查询数据库,提取文档的标题和内容进行显示。

图 8 显示了创建后的文本文档,点击后的效果如图 7 所示。

图 8. 文本文档

时钟

系统提供了一个简单的时钟用以显示当前时间,它由一个表盘以及分针和时针组成,能够随着时间的变化动态地变换。以往的 web 应用利用 JavaScript 或 Flash 完成此类功能,其复杂性可想而知。借助 HTML5 的 draw API,可以轻松地画出所需的图形,极大的方便了此类应用的构建,此外,HTML5 还提供了以往 JavaScript 无法支持的多线程编程,大大加强了 web 应用的交互性和丰富性。

时钟有一个基本的表盘,它仅是一副简单的图片,如图 9 所示。

图 9. 表盘

在表盘之上,建有一个 canvas( 画布 ),如清单 15 所示。

清单 15. 画布
1
<canvas id="canvas" width="128px" height="128px" class="canvas"></canvas>

接下来,清单 17 将在画布上模拟出时钟以及分针,在这之前,额外需要一个后台线程用以计算时间,它被定义在名为 time.js 的独立脚本文件中,如清单 16 所示。

清单 16. 后台线程
1
2
3
4
5
6
7
8
onmessage = function(event)
{
//var i = 1;
   setInterval(function() {
   //i++;
   postMessage("");
   }, 60000);
};

每过 60 秒钟,后台线程将会向前台线程发送一个空消息,以告诉前台线程有 60 秒钟已经过去了。

清单 17. 前台线程的初始化
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
var canvas = document.getElementById("canvas");
if (canvas == null) 
return false; 
var context = canvas.getContext('2d');// 这是一个二维的图像
context.lineWidth = 2;
context.translate(64, 64);// 定义原点
// 初始化分针
context.beginPath();
context.moveTo(0,0);// 从原点开始
var date = new Date();
var mhx = 37*Math.cos((date.getMinutes()-15)*Math.PI/30);
var mhy = 37*Math.sin((date.getMinutes()-15)*Math.PI/30);
context.lineTo(mhx, mhy);// 至分针末端所在位置
context.closePath();
context.stroke();
    
// 初始化时针
context.beginPath();
context.moveTo(0,0);// 从原点开始
var date = new Date();
var hour = date.getHours();
if(hour>=12)
hour = hour - 12;
var minute = date.getMinutes();
var hhx = 27*Math.cos((hour-3)*Math.PI/6 + minute*Math.PI/360);
var hhy = 27*Math.sin((hour-3)*Math.PI/6 + minute*Math.PI/360);
context.lineTo(hhx, hhy);// 至时针末端所在位置
context.closePath();
context.stroke();

前台线程首先会获取 canvas,并设置表盘中心为坐标原点。然后,获取当前时间,计算分针当前所应指向的坐标,然后从原点出发,画出分针。对于时针,若系统为 24 小时制,需要首先转化为 12 小时制,此后的处理类似于分针。

接下来,需要将前台与后台线程联系起来,利用 HTML5 提供的多线程编程方法,声明 Worker 对象作为后台线程的代理,并利用 onmessage 事件,对后台线程发出的消息进行处理。

清单 18. 前台线程的 onmessage 事件
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
var worker = new Worker("js/timer.js");
worker.onmessage = function(event){
    
   context.clearRect(-64, -64, 128, 128);// 清空分针和时针
    
   // 重画分针
   context.beginPath();
   context.moveTo(0,0);// 从原点开始 
   var date = new Date();
   var mhx = 37*Math.cos((date.getMinutes()-15)*Math.PI/30);
   var mhy = 37*Math.sin((date.getMinutes()-15)*Math.PI/30);
   context.lineTo(mhx, mhy);// 至分针末端所在位置
   context.closePath();
   context.stroke();
    
       // 重画时针
   context.beginPath();
   context.moveTo(0,0);// 从原点开始 
   var date = new Date();
   var hour = date.getHours();
   if(hour>=12)
   hour = hour - 12;
   var minute = date.getMinutes();
   var hhx = 27*Math.cos((hour-3)*Math.PI/6 + minute*Math.PI/360);
   var hhy = 27*Math.sin((hour-3)*Math.PI/6 + minute*Math.PI/360);
   context.lineTo(hhx, hhy);// 至时针末端所在位置
   context.closePath();
   context.stroke();
   };
   worker.postMessage("");

每过 60 秒钟,后台线程将会向前台线程发送一个空消息,前台线程接收到消息后,首先,清空 canvas,然后重新获取当前时间,计算分针以及时针对应的坐标,并重新画出时针和分针,从而完成对分针以及时针的更新,最终,每过 1 分钟,表盘更新一次,从而模拟出动态时针的效果,如图 10 所示。

图 10. 时钟

离线支持

虽然 Web 操作系统的优点是可以利用网络随时随地进行访问。然而在无法访问网络的情况下,Web 操作系统便无法发挥作用。因此 Web 操作系统有必要在离线状态下,仍能对部分应用及其功能进行支持。事实上,各种浏览器已提供了各式各样的缓存机制以提供对离线应用的支持,然后这些缓存机制往往是临时性的,不可控的。HTML5 为开发人员提供了解决此问题的另一种途径,它提供了一种永久性的,自定义的缓存方法,使得 Web 操作系统可以在离线的状况下,依然支持部分应用的功能。

HTML5 离线支持的核心是一个缓存清单,其中列出了需要缓存的文件,本系统中的缓存文件 index.manifest,如清单 19 所示。

清单 19. 缓存清单
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
CACHE MANIFEST
#version 1.10
CACHE:
index.html
js/app.js
js/clock.js
js/data.js
js/database.js
js/desktop.js
js/history.js
js/taskbar.js
js/timer.js
js/file.js
js/utils.js
css/index.css
images/appHolder1.png
images/background.jpg
images/clock.png
images/close.gif
images/computer.gif
images/history.png
images/network.gif
images/recycleBin.gif
images/startIcon.png
images/taskBar.png
images/vidioplayer.gif
images/notebook.gif
images/text.gif
images/save.gif
movs/movie.ogg
sounds/WindowsLogonSound.wav

其中,CACHE MANIFEST 标示本文件为缓存文件,#version 1.10 标示了本文件的版本。

CACHE 之后所罗列的则是开发人员自定义的内容,其中包含了所有在离线状态下用户访问应用程序所必不可少的文件。

缓存清单定义结束后,在 index.html 中插入这个清单文件名,这样,当浏览器加载这个页面的时候,会自动缓存清单文件中所罗列的文件。

清单 20. 应用缓存清单
1
<html manifest="index.manifest">

值得一提的是,若要支持离线缓存,除客户端浏览器的支持以外,服务端的支持也是必不可少的,就本系统所使用的 tomcat 而言,需要在其配置文件 web.xml 中添加清单 21 所示的条目。

清单 21. 服务器端缓存配置
1
2
3
4
<mime-mapping>
<extension>manifest</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>

最后,禁用本地机器的网络,重新打开浏览器并访问 Web 操作系统所在的网址,系统中的大部分应用程序依然可以正常工作,如图 11 所示。

图 11. 离线系统

结束语

本文介绍了 Web 操作系统的基本知识,并与传统的操作系统进行了比较,进而介绍了 HTML5 这种新技术为 Web 操作系统开发带来的益处,并与传统的 web 前端开发技术进行了比较,最后通过构建一个基本的 Web 操作系统详细的展现了 Web 操作系统的基本模式和功能以及支撑其运行的 web 前端开发技术是如何实现其具体功能的。从本文的讨论中可以看出,基于 HTML5 的 Web 操作系统是未来的一大趋势,必将逐步走入人们的日常生活工作中去。


下载资源

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
console.log简写
Javascript通过bind()掌控this
超越 DOM
(译) 一篇非常不错的前端面试文章
Handlebars中文文档 - 块级helpers(译自官方版)
理解 JavaScript 作用域
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服