文章分类 » 前端技术

用PHP Session和Javascript实现文件上传进度条

Web应用中常需要提供文件上传的功能。典型的场景包括用户头像上传、相册图片上传等。当需要上传的文件比较大的时候,提供一个显示上传进度的进度条就很有必要了。

在PHP 5.4以前,实现这样的进度条并不容易,主要有三种方法:

  1. 使用Flash, Java, ActiveX
  2. 使用PHP的APC扩展
  3. 使用HTML5的File API

第一种方法依赖第三方的浏览器插件,通用性不足,且易带来安全隐患。不过由于Flash的使用比较广泛,因此还是有很多网站使用Flash作为解决方案。

第二种方法的不足在于,它需要安装PHP的APC扩展库,要求用户能够控制服务器端的配置。另外,如果安装APC仅仅是为了实现一个上传进度条,那么显然有点杀鸡用牛刀的意思。

第三种方法应该是最为理想的方法,不需要服务器端的支持,仅在浏览器端使用Javascript即可。但是由于HTML5标准尚未确立,各浏览器厂商的支持也不相同,所以暂时这种方法还难以普及。

PHP 5.4中引入的基于session的上传进度监视功能(session.upload_progress),它提供了一个服务器端的上传进度监视解决方案。升级到PHP 5.4之后,可以不必安装APC扩展,仅使用原生PHP和前端的Javascript即可实现上传进度条。

下面我们就详细介绍一下 PHP 5.4 的这个 session.upload_progress 新特性。

原理介绍

当浏览器向服务器端上传一个文件时,PHP将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中。然后,随着上传的进行,周期性的更新session中的信息。这样,浏览器端就可以使用Ajax周期性的请求一个服务器端脚本,由该脚本返回session中的进度信息;浏览器端的Javascript即可根据这些信息显示/更新进度条了。

那么,文件上传信息具体是如何存储的?我们要如何访问它呢?下面我们来详细说明。

PHP 5.4 中引入了一些配置项(在php.ini中进行设置)

session.upload_progress.enabled = "1"
session.upload_progress.cleanup = "1"
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"

其中enabled控制upload_progress功能的开启与否,默认开启;cleanup 则设置当文件上传的请求提交完成后,是否清除session的相关信息,默认开启。

prefixname 两项用来设置进度信息在session中存储的变量名/键名。关于这两项的详细使用见下文。

freqmin_freq 两项用来设置服务器端对进度信息的更新频率。合理的设置这两项可以减轻服务器的负担。

在上传文件的表单中,需要为该次上传设置一个标识符,并在接下来的过程中使用该标识符来引用进度信息。具体的,在上传表单中需要有一个隐藏的input,它的name属性为php.ini中 session.upload_progress.name 的值;它的值为一个由你自己定义的标识符。如下:

<input type="hidden"
    name="<?php echo ini_get('session.upload_progress.name'); ?>"
    value="test" />

接到文件上传的表单后,PHP会在$_SESSION变量中新建键,键名是一个将session.upload_progress.prefix的值与上面你自定义的标识符连接后得到的字符串,可以这样得到:

$name = ini_get('session.upload_progress.name');
$key = ini_get('session.upload_progress.prefix') . $_POST[$name];

$_SESSION[$key]; // 这里就是此次文件上传的进度信息了

$_SESSION[$key]这个变量的结构是这样的:

$_SESSION["upload_progress_test"] = array(
 "start_time" => 1234567890,   // 开始时间
 "content_length" => 57343257, // POST请求的总数据长度
 "bytes_processed" => 453489,  // 已收到的数据长度
 "done" => false,              // 请求是否完成 true表示完成,false未完成

 // 单个文件的信息
 "files" => array(
  0 => array( ... ),
  // 同一请求中可包含多个文件
  1 => array( ... ),
 )
);

这样,我们就可以使用其中的 content_lengthbytes_processed 两项来得到进度百分比。

程序示例

原理介绍完了,下面我们来完整的实现一个基于PHP和Javascript的文件上传进度条。

本示例的代码仓库: Github: pureweber/samples/php-upload-progress

上传表单

首先,来编写我们的上传表单页面 index.php,代码如下:

<form id="upload-form"
    action="upload.php" method="POST" enctype="multipart/form-data"
    style="margin:15px 0" target="hidden_iframe">

        <input type="hidden" name="" value="test" />
        <p><input type="file" name="file1" /></p> 
        <p><input type="submit" value="Upload" /></p>
</form>     

<iframe id="hidden_iframe" name="hidden_iframe" src="about:blank" style="display:none;"></iframe>

<div id="progress" class="progress" style="margin-bottom:15px;display:none;">
        <div class="bar" style="width:0%;"></div>
        <div class="label">0%</div>
</div>

注意表单中的session.upload_progress.name隐藏项,值设置为了test。表单中仅有一个文件上传input,如果需要,你可以添加多个。

这里需要特别注意一下表单的target属性,这里设置指向了一个当前页面中的iframe。这一点很关键,通过设置target属性,让表单提交后的页面显示在iframe中,从而避免当前的页面跳转。因为我们还得在当前页面显示进度条呢。

#progress 这个div是用来显示进度条的。

注意 别忘了在index.php的最开始加上session_start()

处理上传的文件

表单的action指向upload.php,我们在upload.php中处理上传的文件,将它转存到当前目录。这里与通常情况下的上传处理没有区别。

if(is_uploaded_file($_FILES['file1']['tmp_name'])){
        move_uploaded_file($_FILES['file1']['tmp_name'], "./{$_FILES['file1']['name']}");
}
?>

Ajax获取进度信息

这一步是关键,我们需要建立一个 progress.php 文件,用来读取session中的进度信息; 然后我们在 index.php 中增加Javascript代码,向 progress.php 发起Ajax请求,然后根据获得的进度信息更新进度条。

progress.php 的代码如下:

session_start();

$i = ini_get('session.upload_progress.name');

$key = ini_get("session.upload_progress.prefix") . $_GET[$i];

if (!empty($_SESSION[$key])) {
        $current = $_SESSION[$key]["bytes_processed"];
        $total = $_SESSION[$key]["content_length"];
        echo $current < $total ? ceil($current / $total * 100) : 100;
}else{
        echo 100;
}
?>

在这里我们获得$_SESSION变量中的进度信息,然后输出一个进度百分比。

index.php 中,我们将如下代码添加到页面底部 (为简便,这里使用jQuery):

function fetch_progress(){
        $.get('progress.php',{ '' : 'test'}, function(data){
                var progress = parseInt(data);

                $('#progress .label').html(progress + '%');
                $('#progress .bar').css('width', progress + '%');

                if(progress < 100){
                        setTimeout('fetch_progress()', 100);
                }else{
            $('#progress .label').html('完成!');
        }
        }, 'html');
}

$('#upload-form').submit(function(){
        $('#progress').show();
        setTimeout('fetch_progress()', 100);
});

#upload-form被提交时,我们把进度条显示出来,然后反复调用 fetch_progress() 获得进度信息,并更新进度条,直到文件上传完毕,显示'完成!'

Done!

完整代码见: Github: pureweber/samples/php-upload-progress

注意事项

input标签的位置

name为session.upload_progress.name的input标签一定要放在文件input <input type="file" /> 的前面。

取消上传

通过设置 $_SESSION[$key]['cancel_upload'] = true 可取消当次上传。但仅能取消正在上传的文件和尚未开始的文件。已经上传成功的文件不会被删除。

setTimeout vs. setInterval

应该通过 setTimeout() 来调用 fetch_progress(),这样可以确保一次请求返回之后才开始下一次请求。如果使用 setInterval() 则不能保证这一点,有可能导致进度条出现'不进反退'。

参考资料

配图来自: dingatx

HTML5-Canvas动画原理

大二下学期,学校里开设了《软件设计与开发实践I》课程,要求利用所学《数据结构》的知识,独自开发一个应用。我做的是数据结构的交互式动画,在开发过程中,我用JavaScript创建了一个简单的HTML5-Canvas动画库。

下面我将利用“矩形”作为一个特例来讲解:

  • JavaScript实现canvas动画的基本原理
  • 如何创建一个简单的动画控制器

此外,我还写了一个此文的国际版本,较本文,其代码和样例比较多,且为英文撰写。

一、在canvas上绘制一个矩形

首先,通过var ctx = document.getElementById(“canvas”).getContext(“2d”)获取canvas的2d上下文对象,可以把ctx看做是一只神奇的画笔,如果你想绘制一个实体矩形,那么调用它的fillRect(x,y,width,height)方法,如果你想绘制一条直线,可以调用lineTo(x, y)……

var context = document.getElementById("canvas").getContext("2d");

//Set location coordinates
var x = 100;
var y = 50;

//Set size
var width = 50;
var height = 80;

//Set colors
var backColor = "Red";
var edgeColor = "Black";

//Set backColor as pen color
ctx.fillStyle = backColor;
//Draws a filled rectangle
ctx.fillRect(x,y,width,height);
//Set edgeColor as pen color
ctx.strokeStyle = edgeColor;
//Draws a rectangular outline
ctx.strokeRect(x,y,width,height); 

二、构建矩形类

在一个动画中,矩形肯定不止有一个。如果想绘制多个矩形,而且它们各自有不同的样式,那么最好的方法就是:将矩形抽象为一个类。

Rectangle = function(cfg)  //cfg is a object of customize parameters
{
    //Set default parameters
    this.width = 50;   
    this.height = 80;       
    this.x = 100;    
    this.y = 50;
    this.backColor = "Red";  
    this.edgeColor = "Black"; 

    //Set customize parameters
    this.setArguments(cfg);  
}
//Set customize parameters
Rectangle.prototype.setArguments = function(cfg) 
{
    for(var x in cfg)
        this[x] = cfg[x];
}
Rectangle.prototype.draw = function() //Draw method
{
    //Set backColor as pen color 
    ctx.fillStyle = this.backColor;
    //Draws a filled rectangle
    ctx.fillRect(this.x,this.y,this.width,this.height);
    //Set edgeColor as pen color
    ctx.strokeStyle = this.edgeColor;
    //Draws a rectangular outline
    ctx.strokeRect(this.x,this.y,this.width,this.height);
}

三、移动一个矩形

很可惜ctx只能绘制静态的图形。所以需要我们自己编码实现动画效果。先看看下面这两幅图片:

static-picture

dynamic-picture

第二张动态的图片是由4张静态图片组成的,由于人眼的视觉残留效应.所以当多张静态的图片快速切换时,我们便看到了动画。将此原理代码化,来实现矩形的移动动画。

function move()
{
    moveShape.animationStatus["Move"] = "new";
    timer = setInterval(function()   //Run once every 24ms
    {   
        if(moveShape.animationStatus["Move"] == "new")
        {                   
            ctx.clearRect(0,0,600,400);  //Clear the canvas
            moveShape.nextPosition();   //Set new (x,y) 
            moveShape.draw();   //Draw the rectangle
        }
        else
            clearInterval(timer);  //Stop setInterval() when it arrives
    }, 24);
}

四、丢失的红色矩形

根据之前的三步内容,我编写了一个Demo:

查看示例 »

在黄色矩形移动时,红色矩形不见了,问题出在哪呢?回到之前第三步代码的第10行moveShape.draw(),由于重绘canvas时,只调用了黄色矩形的draw(),红色矩形自然就消失了。正确的做法是:在第10行后增加一行代码调用红色矩形的draw(),但是这并不是最好的解决方法。如果canvas上有100个矩形呢?难道要增加100行?比较好的方法是:将画板上的所有图形保存到一个ShapeOnCanvas数组里,把第10行代码替换成:

for(var i=0;i < ShapeOnCanvas.length;i++)  
    ShapeOnCanvas[i].draw();  //Draw all shapes that on canvas

五、构建动画控制器

通常的情况是,在同一时刻,有些图形在移动、有些图形在淡入、有些图形在旋转……人都是懒惰的,所以我的目标是一行代码就能实现这些功能,当然这行代码肯定是一个函数调用的接口。

函数调用接口:cmd ({ a1,b1,c1, a2,b2,c2, a3,b3,c3, ... });

  • a* is a string such as “Move”,”Draw”
  • b* is an object of shape
  • c* is an object of animation parameters such as {aimx:10,aimy:10,moveSpeed:2}
var cmd = function()
{
    command = arguments;  //"command" is a long array that save animation commands
    //"command[i]" is a string such as "Move","Draw"
    //"command[i+1]" is an object of shape
    //"command[i+2]" is an object of animation parameters such as {aim_x:10,aim_y:10,moveSpeed:2} 

    //Do the preparation before start animation(refresh canvas)
    for(var i=0; i < command.length; i+=3)  
    {
        //init all animation status as "new"
        command[i+1].animationStatus[command[i]] = "new";  
        //Set animation parameters
        command[i+1].setArguments(command[i+2]);
        //Push it into the stack
        ShapeOnCanvas.push(command[i+1]);  
    }

    timer = setInterval(function()   //Run once every 24ms
    {
        var j = 0;
        var allStop = true;  //"allStop" is the flag of all animations have been stopped
        for(var j=0; j < command.length; j+=3)
            if(command[j+1].animationStatus[command[j]] == "new")
            {
                switch(command[j])
                {
                    case "Draw":
                        command[j+1].draw();
                        break;
                    case "Move":
                        command[j+1].nextPosition();
                        break;
                }   
                allStop = false;
            }

            //Clear the canvas
            ctx.clearRect(0,0,600,400);

            for(var i=0;i < ShapeOnCanvas.length;i++){
                //Draw all shapes that on canvas
                ShapeOnCanvas[i].draw();
            }
            if(allStop){
                //Stop setInterval() when all animations stop
                clearInterval(timer);
            }
    }, 24);
}

如果你想扩充其他动画效果,例如添加“旋转”,只需要在switch添加:

case "Rotate":
    command[j+1].rotate(); //rotate()应该是根据旋转参数设置图形新的坐标位置
    break;

六、串行动画

上一步的动画控制器只能处理并行的动画,现在我们把它扩展一下,实现串行动画。 其最终效果是:如下代码能实现Demo中的动画效果。

cmd("Setup");
cmd
(
    "Draw",staticShape,{},
    "Move",moveShape,{aim_x:400,aim_y:300,moveSpeed:3},
    "Move",fastMoveShape,{aim_x:100,aim_y:300,moveSpeed:6}
);
cmd("Move",staticShape,{aim_x:300,aim_y:100,moveSpeed:5});
cmd("End");

查看示例 »

解决方案:创建一个cmdQueue队列,按照调用cmd()的顺序,将传入cmd()的参数(一条并行动画命令)入队,启动一个setInterval(),每隔10ms检测一下之前一条并行动画命令是否执行结束,如果结束,则从队列出队下一条动画命令去执行。

cmd = function()
{
    if(arguments[0] == "Setup")  //setup animation
    {
        //"cmdQueue" is a queue to save animation commands
        this.cmdQueue = new Array();
        //init the rear and front of the cmdQueue                   
        this.rear = 0;
        this.front = 0;
        cmdRun = false;

        var me = this;

        //run once every 10ms
        cmdTimer = setInterval(function()  
        {
            //All previous animation commands have been stopped 
            //and there are remaining animation commands
            if(me.cmdRun == false && me.front < me.rear) 
            {
                if(me.cmdQueue[me.front][0] == "End")
                    clearInterval(cmdTimer);  //Stop cmdTimer
                else
                {
                    //Run one animation command
                    refresh(me.cmdQueue[me.front]);
                    me.front++;
                }
            }
        }, 10);
    }
    else{
        //Enqueue animation commands to cmdQueue
        this.cmdQueue[this.rear++] = arguments;
    }
}

refresh = function(command)
{
    cmdRun = true;                  

    //Do the preparation before start animation(refresh canvas)
    for(var i=0; i < command.length; i+=3)  
    {
        //init all animation status as "new"
        command[i+1].animationStatus[command[i]] = "new";
        //Set animation parameters
        command[i+1].setArguments(command[i+2]);
        //Push it into the stack
        ShapeOnCanvas.push(command[i+1]);
    }

    timer = setInterval(function()   //Run once every 24ms
    {
        var j = 0;

        //"allStop" is the flag of all animations have been stopped
        var allStop = true;
        for(var j=0; j < command.length; j+=3)
        if(command[j+1].animationStatus[command[j]] == "new")
        {
            switch(command[j])
            {
                case "Draw":
                    command[j+1].draw();
                    break;
                case "Move":
                    command[j+1].nextPosition();
                    break;
            }   
            allStop = false;
        }
        //Clear the canvas
        ctx.clearRect(0,0,600,400);
        for(var i=0;i < ShapeOnCanvas.length;i++){
            //Draw all shapes that on canvas
            ShapeOnCanvas[i].draw();
        }
        if(allStop)
        {
            cmdRun = false;
            //Stop setInterval() when all animations stop
            clearInterval(timer);  
        }
    }, 24);
}

消息推送系统——(一)概念与原理

这里我们从系统结构的层面来看消息推送系统(Push Server)的基本原理。

首先需要了解几个基本的概念:

HTTP长连接

翻译自http keep-alive connection和http persistent connection,又叫http connection reuse,网上也有反过来翻译成http long connection。

下面这个图来自wikipedia,讲解了http长连接是在一个TCP连接的基础之上,发送多个HTTP请求以及接收多个HTTP响应,这是为了避免每一次请求都去打开一个新的连接。在HTTP 1.1标准中,所有的请求都认为是长连接。

HTTP长连接图解

在这里的消息推送系统中,HTTP长连接的作用就是向服务器发送请求,然后一直等待服务器的返回数据。这就相当于客户端在“监听”服务器了,可以随时接收来自服务器的消息。OK,lolita is ready to be pushed!

同步与异步

同步:IO操作将导致请求进程阻塞,直到IO操作完成。也就是说客户端在发送请求后,必须得在服务端有回应后才发送下一个请求。

异步:IO操作不导致请求进程阻塞。也就是说客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求。

同步与异步说的是客户端与服务器端之间的一种通信方式。

阻塞与非阻塞

阻塞:服务器端的线程或者进程没有处理完数据的时候,不会返回,线程或者进程回被挂起,不再响应其他请求。

非阻塞:服务器端在没有处理完的时候,会立即返回,不会挂起线程或者进程,可以继续响应其他请求。

阻塞与非阻塞说的是服务器端对请求的处理方式。

在消息推送系统中,客户端+服务器端一起,使用的是异步非阻塞。

消息推送系统(Push Server)的结构和原理

好了,接下来是就是消息推送系统(Push Server)的结构和原理了:

push-server-1

  1. 客户端发出一个http长连接请求,然后等待服务器的响应。这个请求是异步的,所以客户端可以继续工作,比如发起其他ajax请求等等。这个时候客户端就是一个待推倒的小萝莉了。
  2. 服务器接到请求之后,并不立即发送出数据,而是hold住这个connecton。这个处理是非阻塞的,所以服务器可以继续处理其他请求。
  3. 在某个时刻,比如服务器有新数据了,服务器再主动把这个消息推送出去,即通过之前建立好的连接将数据推送给客户端。
  4. 客户端收到返回。这个时候就可以处理数据,然后再次发起新的长连接。

基本原理就是这么简单。

但是在具体实现的时候,还有很多细节要处理,需要一些其他的技术。

下一篇会讲解客户端Javascript的实现,主要内容是HTTP长连接的建立和CORS在不同浏览器下的实现。

参考资料

原文链接

消息推送系统——(零)推倒萝莉之术

当一个初学Web开发的童鞋,产生让服务器“主动”给浏览器客户端发送数据的想法的时候,比他入门稍早的同学会说:

“这是Web!只能由浏览器发起请求,然后得到服务器返回的数据。”

可能接触得更多的童鞋会说:

“除非你用Javascript轮询/心跳,不断请求服务器看有没有新的数据。但是用户多了服务器会受不了。”

都没错。

但主动推送数据非是不可实现的。聪明的先驱们已经找到了更优的解决方案,那就是利用http长连接来实现消息推送系统。

消息推送系统又叫服务器推、Comet技术、Push Server、Server Push等等。它们的含义大同小异,只是从不同场景中得来的不同的称呼而已,具体可以google。我个人比较喜欢Push Server这个名称,很形象——用来向客户端push消息的这么一个server,就叫Push Server。

消息推送系统是一个很有魔力的技术,它实现了攻受的颠倒和权力的反转。服务器不用再傻乎乎地等待着客户端的请求才能发送最新的数据,而是占据了主动,当有新数据的时候,服务器可以立即主动地将数据push给相关的客户端。

想一想,这个时候,你就可以push消息指挥客户端的Javascript做任何事,所有用户的页面都是你的线控木偶了。话说萝莉有三宝,轻音柔体易推倒。Web相比传统软件来说,也算是轻音柔体的萝莉了,而这里的Push Server,也就是推倒萝莉之术了:P 。

推倒萝莉之后,可以做什么呢?当然是很fancy的事情了:

  • 在线好友列表
  • 在线聊天(聊天室、点对点,多人聊天)
  • 即时通知
  • 统计、监控在线用户
  • 实时内容更新

这里要讲的Push Server,是由Javascript + Python(Tornado) + Memcache实现的。但文章中会着重介绍实现原理,而非具体的代码。

Push Server主要包含以下几个方面:

  • http长连接
  • Javascript 的 CORS (The Cross-Origin Resource Sharing)与跨浏览器实现
  • 服务器异步响应
  • 客户端的链接与断开
  • 性能何如

后面的文章会慢慢解来。

原文链接

初识jQuery

初步了解JavaScript后,其实对于新手来说,很难写出兼容性良好的JavaScript代码。 这里对上述的兼容性,做一点补充:对于相同的一段JavaScript代码,不同的浏览器可能会给出不一样的解释与运行结果。如果你想了解一些兼容性的例子,可以看看这篇文章《IE和Firefox的Javascript兼容性总结》

不过,如果依靠一些JavaScript框架,就能很容易构建出适应各种浏览器的代码。比较流行的JavaScript框架有jQuery,YUI等等。它们的使用都很简单,只要在页面中引入框架的js脚本文件,就能在接下来的编程中使用框架中提供的各种方法了。

1.什么是DOM (DOM——HTML与JavaScript的桥梁)

DOM是Document Object Model的缩写,从字面翻译看,叫做“文档对象模型”

以下是我对它的理解:

    1.一种抽象的树形结构(将文档中的各个对象通过树形结构链接起来) 2.一种抽象的编程接口 (通过DOM我们可以访问文档的API)
总的来说,DOM将文档文件解析成为一种树形结构,树的结点是文档中的元素,然后我们可以通过这颗树的节点选取文档中的元素,通过文档元素的属性、方法和事件来掌控、操纵和创建动态的html元素。

我们可以将HTML文档理解为一座大楼,文档中的的房间、物品等相当于HTML文档中的对象,JavaScript看做是一个在大楼外面的人,DOM看做是大楼所有入口的集合。大楼外的人是无法访问大楼内部的事物的,除非这个人会穿墙术,当然这是不可能的,作为大楼的HTML为了让大楼外的人可以访问、修改它内部的东西,提供了许多入口,人通过入口进入大楼后,就可以对大楼内部经行操作了。

2.JavaScript对象和jQuery对象的区别

JavaScript对象 = 通过JavaScript方法获得的DOM元素的对象 jQuery对象 = 通过jQuery选择器取得的DOM元素的对象

其实对于同一个DOM元素,我们既可以通过JavaScript选取它,也可以通过jQuery选取它。

他们的区别是:

  • 对于JavaScript对象我们只能使用JavaScript里的方法
  • 对于jQuery对象我们只能使用jQuery里的方法
  • 如果非要使用对方的方法,我们可以先将其转换为对方对象

3.如何为页面元素绑定事件(点击,鼠标移入,鼠标移除,失去焦点等事件)

  • 用JavaScript绑定
    • 方法一:调用addEventListenr()方法 例子:element.addEventListenr(“click”,function(){},true)
    • 方法二:直接绑定 例子:element.onclick=function(){}
    • 方法三:调用attachEvent()(IE的绑定模式) 例子:element.attachEvent(“onclick”,function(){})
  • 用jQuery绑定
    • 方法一:调用bind()方法 例子:$(“#div1”).bind(“click”,function(){})
    • 方法二:使用快捷事件 例子:$(“#div1”).click(function(){})
注:用jQuery绑定事件是多浏览器兼容的,因此可以忽略浏览器对于绑定事件的差异

4.如何使用简单的动画效果(淡入,淡出,滑动等)

jQuery的动画效果函数主要有:animate() fadeIn() fadeOut() fadeTo() 通过调用这些函数,就可以实现相应的动画效果 具体用法:《jQuery 参考手册 – 效果》

5.一个小动画页面示例(采用jQuery框架)

演示地址:jQuery小动画示例

动画效果说明: 第一个按钮:以滑动效果出现一个白色黑边框的正方形 第二个按钮:正方形变成红色 第三个按钮:正方形淡出消失 第四个按钮:点击依次循环执行上述三个按钮的功能

HTML代码

<button id="one">淡入</button>
<button id="two">变红</button>
<button id="three">消失</button>
<button id="four">只点我</button>

Javascript代码

$(document).ready(function() 
{
    var count = 0;

    $("#one").bind("click",function(){
        $("#block").css("left","100");
        $("#block").css("opacity","0");
        $("#block").css("background-color","white");
        $("#block").animate({opacity: "1",left: "200px"},1500)
    });

    $("#two").bind("click",function(){
        $("#block").css("opacity","0")
        $("#block").css("background-color", "red")
        $("#block").animate({opacity: "1"}, 1000)
    });

    $("#three").click(function(){
        $("#block").animate({opacity: "0"}, 1000)
    });

    $("#four").click(function(){
        count++;
        count = count % 3;
        if( count == 1 ){
            $("#block").css("left","100");
            $("#block").css("opacity","0");
            $("#block").css("background-color","white");
            $("#block").animate({opacity: "1",left: "200px"},1500)
        }
        if( count == 2 ){
            $("#block").css("opacity","0")
            $("#block").css("background-color", "red")
            $("#block").animate({opacity: "1"}, 1000)
        }
        if( count == 0 ){
            $("#block").animate({opacity: "0"}, 1000)
        }
    });
});

Html5新特性概览

  • 主讲人 : 李洪祥
  • 时间 : 2011-11-06

注意:本文中的所有测试页面请使用Google chrome或其他对html5兼容较好的浏览器测试,避免可能的浏览器兼容问题对测试结果产生的影响。

Html的简单历史

  • 超文本置标语言(第一版)——在1993年6月作为互联网工程工作小组(IETF)工作草案发布(并非标准)
  • HTML 2.0——1995年11月作为RFC 1866发布,在RFC 2854于2000年6月发布之后被宣布已经过时
  • HTML 3.2——1996年1月14日,W3C推荐标准
  • HTML 4.0——1997年12月18日,W3C推荐标准
  • HTML 4.01——1999年12月24日,W3C推荐标准

HTML 5——在Html 4问世七年之后,因为当时W3C的工作目标是制定并推行XHtml语言,于是还没有开发下一代HTML的工作组,W3C内部的一些人就开始想做点什么了, 于是,在2004年W3C成员内部的一次研讨会上,当时Opera公司的代表伊恩·希克森(Ian Hickson)提出了一个扩展和 改进HTML的建议,他建议新任务组可以跟XHTML 2并行,但是在已有HTML的基础上开展工作,目标是对HTML进行扩展。但W3C投票表决的结果是——”反对”,因为他们认为HTML已经死了,XHTML 2才是未来的方向。然后,Opera、Apple等浏览器厂商,以及其他一些成员说:”那好吧,不指望他们了,我们自已一样可以做这件事,我们脱离W3C。”于是他们成立了Web Hypertext Applications Technology Working Group(Web超文本应用技术工作组,WHATWG) ,WHATWG决定完全脱离W3C,在HTML的基础上开展工作,向其中添加一些新东西。这个工作组的成员里有浏览器厂商,因此他们不仅可以说加就加,而且还能够一一实现。结果,大家不断提出一些好点子,并且逐一做到了浏览器中。

WHATWG的工作效率很高,不久就初见成效。在此期间,W3C的XHTML 2没有什么实质性的进展。特别是,如果从实现的角度来说,用原地踏步形容似乎也不为过。等到2006年W3C认识到xhtml 2并不符合当前的发展实际,同时他们认识到了html5的重要性,于是在2007年他们又投了一次票,同意成立html5开发小组,”在WHATWG工作成果的基础上继续开展工作”并与WHATWG并肩工作,于是逐渐有了我们现在讨论的HTML5问世。

浏览器对html5 的支持情况

目前,对 HTML5 支持最好的是 Chrome,Safari 次之,Firefox 和 Opera 旗鼓相当,IE从IE9终于开始拥抱标准。鉴于这种情况,假如你想使用Html5创建一个先锋体验式站点,现在的 HTML5 可以让你实现,假如你想大规模应用于实际项目,现在还为时过早。

主流浏览器对HTML5 图形和内嵌内容支持情况:

主流浏览器对HTML5 图形和内嵌内容支持情况

主流浏览器对HTML5 Web 应用程序的支持情况:

主流浏览器对HTML5 Web 应用程序的支持情况

Html5移除的元素

Html语言的作用是标记文本的结构,而非用于网页表现,所以用于网页表现的<front><center>等可以用CSS达到同样目的的元素被移除,其他被移除的元素还有:font, center, strike, big, s, u, acronym, applet, dir等。此外还有一些无用或者不被使用的元素属性被移除,例如link的traget属性,这个属性几乎不被浏览器支持。还有<script>的language属性,html4.01推荐使用type属性替代language属性,该属性将被废弃使用,所以language属性没有存在的价值,还有其他的一些属性例如link和a元素的rev属性,body元素的alink,link,text和vlink属性等因为类似的原因被移除。

Html5的基本布局(语义化的标记)

Html是用来描述网页的一种语言,它是一种标记语言,标记语言就是一套标记标签,比如用来描述一段文字是标题的<h1>,标记一个图像的<image>等。html4中使用没有任何语义的<div>用于内容分块,所以html4的网页基本布局是这样的,许多div加上用于命名区块的class或者id,

<div id="wrapper">
    <div id="header"></div>
    <div id="main">
        <div id="sidebar"></div>
        <div id="content"></div>
    </div>
    <div id="footer"></div>
</div>

在html5中,这种情况将被改变,html5新增了若干个明确语义化的标签,在html5中,<div id="header">将会被简洁的<header>代替,如下:

<article>
    <header></header>
    <section></section>
    <section></section>
    <aside></aside>
    <footer></footer>
</article>

其中的<section><article>标签值得注意,<section>标签用来分组相类似的信息,而<article>标签则应该是用来放置诸如一篇文章或是博客一类的信息,你可以在<artile>中嵌入<header><section><footer>等内容。<article>标签,正如它的名称所暗示的那样,提供了一个完整的信息包。相比之下,<section>标签包含的是有关联的信息,但这些信息自身不能被放置到不同的上下文中,因为这样的话其所代表的含义就会丢失。

一个普通的Html 5页面结构会如下所示:

<!DOCTYPE html>
<html>
    <title>标题</title>
    <body>
        <header></header>
        <nav>导航</nav>
        <article>
            <section>区块</section>
        </article>
        <aside>侧边栏</aside>
        <footer>页脚</footer>
    </body>
</html>

Html5对表单的支持

Html5新增了多个控件类型,如输入网址的url类型,输入电子邮箱地址的email类型,用于输入日期的date类型,更有用于输入颜色的color类型等等。更为神奇的是这些类型还内建表单验证,如required属性就说明该表项不能为空,max属性提供了该表项输入允许的最大值,当然在html4中这些功能也可以通过javascript来实现,但是的内建在html5中的表单验证优越性不言而喻。需要注意的是,现在Html5的输入类型还没有被所有浏览器所支持。

测试代码:

<form>
    Email:<input type="email" required /></br>
    Number:<input type="number" max="10" /></br>
    Date :<input type="date"></br>
    File:<input type="file" accept="image/png" /></br>
    <input type="submit">
</form>

实例页面:input.html

Html5中的Media

Html5 的另一大特点就是不借助其他插件就可播放视频和音频文件,在网页中添加如下代码就可以用一个宽度为320像素、高度为240像素的带控制条的浏览器内置播放器来播放”movie.mp4″视频文件,而不再需要使用Adobe Flash播放器。效果就像这样:video.html

<video src="movie.mp4" width="320" height="240" controls="controls">
    Your browser does not support the video tag.
</video>

当然这种直接使用视频源文件的方法会牵扯到视频文件格式的版权问题,所以各主流浏览器都做不到对所有的视频格式都完全支持,这时就需要使用如下的代码来实现视频播放:

<video>
    <source src="movie.mp4">
    <source src="movie.ogv">
    <object data="movie.swf">
        <a href="movie.mp4">Download</a>
    </object>
</video>

<video></video> 之间插入的内容是供不支持 video 元素的浏览器显示的,由于视频格式的版权问题,不同的浏览器支持不同的视频格式,所以video 元素含有允许多个 source 元素。source 元素可以链接不同的视频文件。浏览器将使用第一个可识别的格式。在上面的那个程序里:

  1. 如果浏览器支持video元素,也支持H264(mp4格式),那么就使用第一个视频;
  2. 如果浏览器支持video元素,不支持H264(mp4格式)但支持Ogg,那么用第二个视频;
  3. 如果浏览器不支持video元素,那么就播放Flash;
  4. 如果浏览器不支持video元素,也不支持Flash,那么就给出了下载链接,这下就该没有问题了吧。

不得不提,html5的支持媒体播放的特性会冲击adobe flash在网络媒体中的地位,Adobe公司已经深刻意识到了这个问题,更多信息参见关于Adobe最新的Flash/HTML5策略的问答

动画元素Canvas

HTML5 的 canvas 元素可以使用 JavaScript 直接在网页上绘制图像。在canvas中,画布是一个很重要的概念,画布是一个矩形区域,利用canvas可以控制其每一像素。canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

创建 Canvas 元素十分简单,只需要向 HTML5 页面添加 canvas 元素,规定元素的 id、宽度和高度:

<canvas id="myCanvas" width="200" height="100"></canvas>

然后通过 JavaScript 来绘制,canvas 元素本身是没有绘图能力的,因此所有的绘制工作必须在 JavaScript 内部完成:

var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
cxt.fillStyle="#FF0000";
cxt.fillRect(0,0,150,75);

JavaScript 使用 id 来寻找 canvas 元素:

var c=document.getElementById("myCanvas");

然后,创建 context 对象:

var cxt=c.getContext("2d");

getContext("2d") 对象是内建的 HTML5 对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

下面的两行代码绘制一个红色的矩形:

cxt.fillStyle="#FF0000";
cxt.fillRect(0,0,150,75);

fillStyle 方法将其染成红色,fillRect 方法规定了形状、位置和尺寸。fillRect 方法拥有参数 (0,0,150,75)。意思是:在画布从左上角开始 (0,0)绘制一个 150×75 的矩形,很简单吧。上面的例子是最简单的例子,canvas是十分强大的,现在大多数html5的Demon页面都是在展示html5强大的动画能力,通过canvas完全可以实现与flash动画相媲美的页面,下面的几个网页供大家惊叹:

Drag&Drop

HTML5为元素新增了用于拖拽的属性draggable,这个属性决定了元素是否能被拖拽, 如果draggable=”true”,则元素可被拖拽,否则只能选择元素的文本。下面几行简单的代码可以简单的说明Html5的Drag&Drop特性:

<section>
    <p draggable="true" ondragstart="dragstartHandler(event)">
    Drag me!Drag me!!</p>
    <p draggable="false">Don’t drag me!!</p>
</section>

Section区块中包含两行文本,第一行文本的draggable = true,即可拖拽的,且声明了发生dragstart事件是调用的函数;第二行文本draggable = false,即不可拖拽的。下面的<script>实现了发生dragstart事件时调用的函数,显示一个提示框。

function dragstartHandler(e)
    {alert('dragstart');}

实例页面:drag.html

运行这段代码可以发现,当拖拽第一行文本时会弹出一个提示框,而拖拽第二行文本时除了复制文本以外没有任何反应,这是因为当拖拽第一行文本时会触发ondragstart事件,从而调用相应的处理函数产生一个提示框。同样的定义其他拖拽事件如ondragenter、ondragover、ondragover、ondrop等的动作函数就可以完成一个完整的拖拽动作,实现想要实现的目的。

下面是一个网络上的demon供大家尝鲜:

把本地文件直接拖拽上传

Html 5的Application Cache

现在,web应用的火爆已经是不折不扣的现实,并且相对传统的应用,web应用不需要安装,所占空间小的特性使其具备传统软件应用所不具备的优势,然而,目前制约web应用最大的问题在于网络连接不能够无时无处。在飞机上,汽车上,火车上,有很多地方都无法被网络信号所覆盖,因此web应用也就无法使用。

HTML5的离线存储使得这个问题迎刃而解。HTML5的web storage API 采用了离线缓存,会生成一个清单文件(manifest file),这个清单文件实质就是一系列的URL列表文件,这些URL分别指向页面当中的HTML,CSS,Javascrpit,图片等相关内容。

一个Mainfest file实例:

–  CACHE MANIFEST
   /demo/test/style.css
   /demo/test/jquery.min.js
   /demo/test/test.html

当使用离线应用时,应用会引入这一清单文件,浏览器读取这一文件,下载相应的文件,并将其缓存到本地。使得这些web应用能够脱离网络使用,而用户在离线时的更改也同样会映射到清单文件中,并在重新连线之后将更改返回应用,工作方式与我们现在所使用的网盘有着异曲同工之处。

Local storge

Web应用的发展,使得客户端存储使用得也越来越多,而实现客户端存储的方式则是多种多样。最简单而且兼容性最佳的方案是Cookie,但是作为真正的客户端存储,Cookie则存在很多致命伤。此外,在IE6及以上版本中还可以使用userData Behavior、在Firefox下可以使用globalStorage、在有Flash插件的环境中可以使用Flash Local Storage,但是这几种方式都存在兼容性方面的局限性,因此真正使用起来并不理想。针对以上情况,HTML5中给出了更加理想的解决方案:假如你需要存储复杂的数据则可以使用Web Database,可以像客户端程序一样使用SQL(不过Web Database标准当前正陷于僵局之中,而且目前已经实现的浏览器很有限);假如你需要存储的只是简单的数据则可以使用Web Storage。

Web Storage实际上由两部分组成:sessionStorage与localStorage。

sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。

localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

下面一段代码同时包含sessionStorage和localStorage用于统计页面的访问次数:


if (sessionStorage.pagecount){
    sessionStorage.pagecount=Number(sessionStorage.pagecount) +1;
}else{
    sessionStorage.pagecount=1;
}
document.write("Visits "+ sessionStorage.pagecount + " time(s).");
if (localStorage.pagecount){
    localStorage.pagecount=Number(localStorage.pagecount) +1;
}else{
    localStorage.pagecount=1;
}
document.write("Visits "+ localStorage.pagecount + " time(s).");

多次刷新页面locaStorage.html可以发现,两种计数每刷新一次就增加1,如果把页面关闭然后再打开,那么sessionStorage计数将会从0开始重新计数,而localStorage仍然继续上次的计数。这正是因为sessionStorage仅仅存储本次会话中的数据,而localStorage则会持久的存储数据。

Html 5 相关网页

参考资料

JavaScript事件冒泡和事件委托

接触JavaScript不久,学的东西也不是特别多。小雨就是习惯把平时学到的东西拿出来分享。一方面加强自己的印象,一方面可以让自己的经验为他人答疑解惑。我们知道JavaScript可以监控页面上元素的各种事件,常用的事件有很多,例如点击,鼠标移入、移出,元素改变等等。这次主要说一下事件冒泡及其一个比较酷的应用,事件委托。不做特殊说明,以下都在jQuery框架内执行。

事件冒泡

什么是“事件冒泡”呢?假设这里有一杯水,水被用某种神奇的方式分成不同颜色的几层。这时,从最底层冒出了一个气泡,气泡会一层一层地上升,直到最顶层。而你不管在水的哪一层观察都可以看到并捕捉到这个气泡。好了,把“水”改成“DOM”,把“气泡”改成“事件”。这就是“事件冒泡”。

为了可以直观地观察到这一现象,我写了一个小程序。这个页面中一共有4个嵌套的正方形。最大的那个在最顶层,最小的那个在最底层。我为每一层都单独绑定了一个点击事件,当这一层被点击时,会为这层涂色。试试看,点击最小的正方形会发生什么?点击其他的又会发生什么呢? (点击这里查看demo)

CSS

.white{background-color:#fff;}
#d1{width:400px;height:400px;border:1px solid #000;margin:50px 50px;}
#d2{width:300px;height:300px;border:1px solid #000;margin:50px 50px;}
#d3{width:200px;height:200px;border:1px solid #000;margin:50px 50px;}
#d4{width:100px;height:100px;border:1px solid #000;margin:50px 50px;}

HTML

<div id="d1" class="white">
    <div id="d2" class="white">
        <div id="d3" class="white">
            <div id="d4" class="white"></div>
        </div>
    </div>
</div>
<button id="reset1">重置</button>

Javascript

jQuery('#d4').click(function(){jQuery(this).css('background-color', 'yellow')});
jQuery('#d3').click(function(){jQuery(this).css('background-color', 'green')});
jQuery('#d2').click(function(){jQuery(this).css('background-color', 'blue')});
jQuery('#d1').click(function(){jQuery(this).css('background-color', 'red')});
jQuery('#reset1').click(function(){jQuery('.white').css('background-color', '#fff')});

没错,点击最小的那个,外面所有的都会被上色。你会发现,点击里层的正方形,外层所有的正方形都会被上色。因为它们也都捕捉到了点击事件。看,他们抓到“气泡”了!

事件委托

上一节的例子我们做一点小小的修改。气泡带上了某种信息,会告诉其经过的每一层自己是在哪一层产生的。JavaScript的事件确实会带着这个属性。当程序捕获一个事件的时候,它会知道这个事件来自于页面上哪个元素。修改上面的程序,使用事件委托来处理点击事件。当最顶层捕获点击事件时,查看事件来源于哪一层,然后只将那一层涂色。再次点击每一层,查看实际效果。只有当前点击的正方形变色了,其他的都毫无反应。(点击这里查看demo)

jQuery('#d1').click(function(e){
        var t = jQuery(e.target);
        var id = t.attr('id');
        if (id==='d4'){
                t.css('background-color', 'yellow');
        } else if (id==='d3') {
                t.css('background-color', 'green');
        } else if (id==='d2') {
                t.css('background-color', 'blue');
        } else {
                t.css('background-color', 'red');
        }
});

当然,如果你有这样嵌套的页面元素,使用了事件委托,委托到了最顶层,这时需要注意:如果其中某个元素,你不希望它的事件冒泡,那么可以使用某种方式阻止事件的冒泡。在jQuery框架中,可以使用stopPropagation()方法来实现而不必关心浏览器兼容性。

$('#bind').click(function(){
    if ($(this).is(':checked')) {
        $('#d4').bind('click', function(e){
            e.stopPropagation();
            alert('冒泡被阻止,这块将不会改变颜色');
        });
    } else {
        $('#d4').unbind('click');
    }
});

重置后选中“阻止最小的方块的事件冒泡”,再点击最小的方块,看是否变色。显然是不会变色,阻止了冒泡,父层将无法接收到点击事件。

注意事项

事件委托是事件冒泡的一个应用,可以减少绑定元素的个数,也不必担心子节点被替换后可能需要进行重新的事件绑定。因为事件的捕获和后续代码的执行已经完全委托给了其父节点。如果页面中含有大量元素需要绑定事件,这样做会减少事件绑定数量,为浏览器减负,无疑会提高页面性能。

但也有些需要注意的。如果用于捕获事件的节点会在某些情况下return false,而你的一个点击事件未委托给父节点,那么,你可能需要阻止这个节点的事件冒泡来达到正确的目的。例如:我在一个div里面有一些按钮和其他元素。利用事件委托来处理这些按钮的点击,如果不是按钮则return false。这时,错误就出现了。如果其他元素中含有链接,那么链接的点击事件也会被委托给div。然而点击链接,会没有任何反应。解决办法一是在委托的代码中对链接进行处理,二是阻止链接的事件冒泡。

源代码

源代码打包下载

FireBug和YSlow介绍

注:本文中的所有操作可以随意打开一个网页进行,因为操作都很简单,建议每看一步就实际操作一下,这样还可以发现一些文中没提到的功能。

Firebug是Firefox浏览器最好的插件之一,对于网页开发人员来说是一个利器,能够极大地提升工作效率。当你在浏览网页时,它能使你实时的在任何一个页面上编辑,测试css,DOM,Html,javascript。 Yslow也是Firefox的一个扩展,可以对网站的页面进行分析,并告诉你为了提高网站性能,如何基于某些规则而进行优化。

Firebug窗口预览

firebug-1

运行Firefox浏览器后,按F12键就可启动Firebug。

  • Console标签: 主要使用javascript命令行操作,显示javascript错误信息。
  • HTML标签: 显示HTML源码 CSS标签: 浏览所有已经装入的样式表。
  • Script标签: 显示javascript文件及其所在页面。
  • DOM标签: 显示所有的页面对象和window物体的属性。
  • Net标签: 显示本页面涉及的所有下载,以及它们各自花费的时间,各自的HTTP请求头信 息和服务器响应的头信息。
  • Yslow标签: Yslow工具,安装后就会嵌套在Firebug工具当中随时编辑页面

随时编辑页面

firebug-2

点击窗口上方的“inspect”命令(如图),然后滑动鼠标选择页面中的文本节点,下面的html也会瞬时定位到你选择的节点。你也可以对其进行修改,修改结果会马上反应在页面中。反之亦然,若鼠标停留在下方代码中的某个节点上,页面上也会瞬时显示出该节点的width,border和margin等信息。

firebug-3

Firebug同时是源码浏览器和编辑器。所有HTML、CSS和Javascript文件中的对象,都可以用单击或双击进行编辑。当你输入完毕,浏览器中的页面立刻会发生相应变化,你可以得到瞬时反馈。

用Firebug处理CSS

在CSS标签中,Firebug会自动补全你的输入。在DOM标签中,当你按Tab键时,Firebug会自动补全属性名。

firebug-4

此外,我们还可以利用Firebug来查看某元素的盒模型和其所有的CSS样式。在CSS窗口上方,有一个“布局”按钮,点击后会展示与该元素相关的方块模型,包括padding、margin和border的值。还有一个“计算出的样式”按钮,点击后可以查看该节点的所有CSS样式,在这里我们可以看到我们并没有进行设置而是有浏览器自己默认出来的属性。如下图:

firebug-5

评估下载速度

Net标签中图形化了页面中所有http请求所用的时间。你可以用这项功能评估javascript文件下载,占用整个页面显示的时间。并且可以查看AJAX信息。

firebug-6

Javascript调试

在script标签中,可以查看整个页面的所有javascript代码,并在代码中设置断点进行debug,左上角有下一步和继续等按钮,窗口右侧是代码中变量和一些DOM的值,他们会随着javascript代码的运行而变化,同时debug时如果代码对页面内容或效果作出改变,页面也会作出即使的响应。这个debug过程和codeblock和netbeans等IDE都非常相似而且操作要简单的多。

firebug-7

Firebug控制台使用

控制台(console)非常强大,也是firebug里最重要的面板,主要作用是显示网页加载过程中产生各类信息。

它能列出javascript调用的所有函数,及其所花的时间。对javascript调试非常有用。而且它还提供了一个console对象,我们可以利用这个对象的各种函数来对javascript代码进行测试。给前端开发带来了极大的方便。下面简单介绍下console对象的使用,在javascript代码中加入下列代码。

var dog = {};//声明一个对象
dog.name = "大黄";//为dog对象设定一些属性
dog.color = "黄色";
dog.bark = function(){alert("wa kakaka");};

//分组开始——group console.group("group"); console.log("%o",dog); console.log("%d年%d月%d日",2011,9,17); console.info("this is a %s","info"); console.debug("a debug"); console.error("this is a error"); console.warn("this is a warn"); console.dir(dog); console.groupEnd(); //分组结束

//判断表达式——assert var result = 0; console.assert(result == 1);

//计时功能——time console.time("firsttime"); for(var i = 0;i < 1000;i++) { for(var j = 0;j < 1000;j++){} } console.timeEnd("firsttime");

//创建两个函数foo和foo2 function foo(){ for(var i = 0;i < 1000;i++){ for(var j = 0;j < 30;j++){ foo2();} } } function foo2(){ for(var k = 0;k < 1000;k++){} }

//性能分析器——profile console.profile("性能分析器"); foo(); foo2(); console.profileEnd();

运行之后在firebug控制台可看到下面的效果

firebug-8

试想一下在调试代码的时候,为了方便我们可能想让程序在运行过程中就返回给我们一些变量,好让我们查错。以前很多人都喜欢用alert函数弹出个窗口,现在可以完全用console对象来完成这个功能。

不同的函数用来显示不同类型的信息。如log,info,debug,error,warn分别显示的信息类型都不同;这些函数使用起来没什么区别,都和C语言的printf()函数差不多,但只支持字符(%s)、整数(%d或%i)、浮点数(%f)和对象(%o)这四种占位符。唯一的区别就是在控制台当中显示的样式不一样。

下面看下其他的一些函数,如:

  • console.dir()是用来显示一个对象里所有元素的函数。
  • console.assert(),判断某表达式是否正确,如例子中0==1就是一个错误的表达式,所以会产生一个错误信息,如果表达式是对的就什么也不显示。
  • console.group()和console.groupEnd();这两个函数是一对,分组的意思,如果你在调试代码时想要输出看的东西比较多,就可以用这两个函数来进行分组,其group可带上参数,表示该组的名称。
  • console.time()和console.timeEnd();该函数使用来计算时间的,它会为开发者显示出在这两个函数中间的代码所运行的时间。
  • console.profile()和console.profileEnd();该函数被叫做性能分析器,看运行出来的效果就可知道,它能对夹在两函数中间的函数进行测试,包括被调用次数和运行时间等等。该功能和控制台界面中的概况(profile)按钮的功能是一样的。

控制台和console对象的使用比较复杂,还有用来显示xml代码的dirxml()函数和跟踪函数trace()等等。可参考下面的链接进行详细的学习。 链接:http://www.ruanyifeng.com/blog/2011/03/firebugconsoletutorial.html 上面的代码在文章尾部有相关下载,可下载下来实践一下。

Yslow分析网页性能

Yslow的功能在文章的开始处已经说明。它有四个视图,每个视图都是对整个页面在不同方面的信息显示。

组件视图

该视图中列出了整个页面所加载的所有文件,包括html,css和javascript等。并列出每个文件的详细信息和加载每个文件时的http请求信息等。

yslow-1

统计信息视图

该视图会显示整个页面加载的所有文件的个数和各类文件的大小比例。

yslow-2

等级视图

这个视图是YSlow最让人关注的了,它会根据不同中规则对该页面进行评测并给出相应的分数和等级划分,对做的不好的地方还会给出优化的建议。关于每个规则的说明和优化方法可参考本站的另一篇文章《读书分享:高性能网站建设指南》

yslow-3

工具视图

该视图里提供了一些工具,例如对代码进行压缩等等。

示例代码:下载

读书分享:《高性能网站建设指南》

high-performance-web-sites

同样的网络环境,看着别人的网站“唰”的一下就展现出来,你是否和我一样,心急如焚,盼望着早一点攒出一笔钱,给服务器加点内存?或者你已经挽起袖子,开始研究数据库优化?又或者你在暗自思量着可以把哪些设计模式或编码技巧运用在自己的后台代码里,盼望以此带来性能上的巨幅提升? 哦,别激动,很多时候事情并没有你想象的这么严重。

这是High Performance Web Sites(以下称HPWS)的译者在序中说到的前两段。对于网站的响应速度的优化,后台的优化和硬件的升级往往伴随着很多困难,诸如成本,工作量等。但实际上前台的优化更为重要也更为高效,为什么呢?HPWS中引入了一条性能黄金法则:

只有10%~20%的最终用户响应时间花在了下载HTML上,其余的80%~90%时间花在了下载页面中的所有组件上。

这就是为什么我们要把精力集中到对于前台的优化上。

HPWS中引入了14条可以实施的方案用于优化前台性能,我将逐条说明。

减少HTTP请求

减少HTTP请求的有效方法,HPWS中提供了以下几个方法:

图片地图

对于一个和图片关联在一起的超链接,使用图片地图能够有效减少请求数,因为这样在不改变显示效果的前提下,将很多张图片改为了一张。

CSS Sprites

这种方法类似于图片地图,也是用于合并图片,而且更为灵活

内联图片

这种方法是将小块的图片内联为“立即数”,图片的全部数据就在其URL自身之中

合并脚本和样式表

虽然软件工程师会推荐将代码按照模块化原则分开放在多个小文件中,但这样做会降低性能,因为每个文件都会导致一个额外的HTTP请求,所以合并脚本和样式表也可带来性能的提升。

使用内容发布网络

需要这样做的原因是,当你的用户在地理上分布很广时,你就有必要在多个地理位置不同的服务器上部署内容。所以使用内容发布网络(CDN)能够使HTTP请求响应的时间缩短。CDN是一组分布在多个不同地理位置的Web服务器,用于更有效地向用户发布内容。

添加Expires头

Expiers头用于控制浏览器对于缓存的保存时间。因此这一条规则的含义是为你的页面添加长久的Expires头,这样可以有效地将大部分组件保存在缓存中,这样当用户再次访问页面时,浏览器将使用缓存的图片从而减少了HTTP请求的数量,从而节省了时间。

Expires头在服务器端配置,如Apache中有mod_expires模块。使用与不使用Expires头的HTTP响应将如下所示

不使用Expires头

使用Expires头

两者在YSlow中对Expires的评价中分别得到了F和A(注:YSlow是FireFox下的一款插件,用于对一个网站速度进行多个方面的评价)。可以看到合适的Expires头对于网站速度的重要性。

压缩组件

减少HTTP响应的大小来减少响应时间这一点很容易理解,只传输很少的数据,肯定相应有很少的响应时间。减小响应包大小的方法之一是使用gzip编码来压缩HTTP响应包。当然也有一些其他的方法,但都不如压缩组件来得效果好。

压缩组件的配置同样在服务器端,如Apache的modgzip及moddeflate

组件压缩后将在响应头中有显示:

对于压缩的内容,压缩所有内容在YSlow中得分最高A,不进行压缩将得到D,只压缩HTML将得到C

将样式表放在顶部

CSS样式表对于用户而言并不是最重要的,因此若将CSS样式表放在顶部将会延迟页面中其他重要组件的下载,但实际情况与此相反(请到文章末尾给出的链接中体会)将CSS放在顶部的加载速度快于将CSS放在底部。这是因为放在底部的CSS将会阻塞浏览器对于页面的渲染,因为假如没有得到准确的CSS信息,那么对于页面的呈现就显得是一种浪费,所以为了避免这点,浏览器会等到CSS下载完毕后再呈现页面,这样就导致用户界面很长时间会出现“白屏”的现象。显然这种用户体验是不好的,因此将CSS放在底部,将有助于浏览器逐步呈现页面,而不是给用户一个浏览器假死的现象。

将CSS放在底部

将CSS放在顶部

两种做法在YSlow中分别得到B和A

将脚本放在底部

与CSS样式表类似,脚本也会阻碍页面的逐步呈现,但解决的方法与CSS样式表正相反:将脚本放在底部将加快浏览器的呈现速度。这涉及到浏览器的行为。浏览器在下载组件时,往往采取并行下载多个组件的策略,并行下载数与浏览器相关,但脚本将阻塞并行下载,其中一个原因是,脚本可能使用document.write来修改页面内容,因此浏览器会等待,以确保页面能够恰当地布局,而另一个原因是为了保证脚本能够按照正确的顺序执行。如果并行下载多个脚本,就无法保证响应是按照特定顺序到达浏览器的,这使得脚本的执行顺序不可预知,假如脚本之间有关联,就有可能产生错误。文章后面的链接中有一个例子,从里面可以很明显地看到js脚本是如何阻塞呈现的。

但是对于阻塞并行下载,不同浏览器的行为并不相同,经我们的观察和讨论得出,有些浏览器阻塞的并非是并行下载,而是页面的渲染,但不论如何,这都影响了页面的呈现,所以将脚本放在页面的底部能够加速页面的呈现。

在firefox中js脚本和其他组件也是并行下载的(使用firebug查看)

避免CSS表达式

CSS属性设置中可以接受js表达式,在某些情况下(譬如动态改变样式)非常有用,例如在IE中不支持min-width属性,这样就需要使用CSS表达式来设置使得页面拥有最小宽度。但是浏览器对于CSS表达式的频繁求值会导致CSS表达式的低下性能。在文章末尾提供的链接中的例子可以看到,CSS表达式的求值比想象中要频繁。

需要使用CSS表达式又怕降低性能,解决的方法有两个:一个是使用一次性表达式,在一次执行中重写它自身,这样可以将属性设定为一个确定的值,而避免了表达式的重复求值。

一次性表达式

另一种方法是使用事件处理器,这样已经脱离了CSS表达式的范畴,但使用这种方法也能达到同样的效果,而且不需要频繁求值,即将对表达式的求值与一个事件关联。

事件处理器

将事件与函数相关联

使用外部JavaScript和CSS

使用内联还是外部的js和CSS是一个很基本的问题,在HPWS中的说法是“纯粹而言,使用内联js和CSS更快一些”但是我个人实际的实验却表示使用外部的js和CSS更快,我想着其中很大一部分原因是并行下载,因为内联或者外部,组件的总大小是固定的,而内联将使得并行下载不能,所以反而会降低速度,虽然这样减少了HTTP请求。但实际上当然还是使用外部js和CSS较好,因为这涉及到缓存的问题,外部js和CSS可以保存在缓存中,这样用户下次就不必下载了,内联的文件则不然。

在内联比外部快的前提下,HPWS提出了两全其美的方法:加载后下载,即使用户下载内联文件,但是在页面中加入脚本,使得在用户加载页面结束后开始下载外部的js和CSS,同时还可以再服务器端添加动态内联,判断用户是否有当前组件的缓存,这可以通过cookie实现。

下载后加载的代码:

精简JavaScript

减小HTTP相应包的方法,除了压缩组件之外,精简js也是一个有效的方法,精简的含义是从代码中移除不必要的字符以减小其大小,进而改善加载时间的实践。在代码被精简后,所有的注释以及不必要的空白字符都将被移除。这样可以减小需要下载的js脚本。混淆是可以应用在源代码上的另外一种方式。和精简一样,它也会移除注释和空白,同时它还会改写代码。作为改写的一部分,函数和变量的名字将被转换为更短的字符串,这是的代码更加精炼,也更难阅读。但混淆有可能引入错误,混淆会改变js符号,这些都是混淆带来的风险,所以精简是最常用的方式。

精简后的脚本

混淆后的脚本

并且不要忘记精简CSS脚本

移除重复脚本

重复脚本经常发生,因为不同的团队都会向项目贡献代码,但是他们相互之间可能不知道对方已经包含了自己想要的脚本,这样重复包含就发生了,重复的脚本会增加不必要的HTTP请求,从而损伤性能。解决的方法可以在后台添加一个脚本管理的模块,将已经包含的脚本假如一个列表,每次添加前加以检测,对于已经添加的脚本则不再添加。

配置ETag

ETag的作用于Expires头类似,都是标识了一个组件的缓存时间。不同的是Expires头是通过设置保存时间来设置缓存,而ETag是对一个特定版本的组件产生一个特定字符串,然后在以后的请求中通过比对此字符串来决定是否更新此组件。那么为什么要配置ETag呢,因为ETag通常由组件的某些属性来构造,这些属性相对于特定的、寄宿了网站的服务器来说是唯一的。当浏览器从一台服务器上获取了原始组件之后,又向另外一台不同的服务器发起条件GET请求时,ETag是不会匹配的。因此需要配置ETag的构造方法,使得ETag在不同的服务器上相一致。

ETag举例

使Ajax可缓存

由于Ajax是几种已有技术的组合,因而其优化方法大体可以依照前面的优化方法,如配置缓存,压缩组件,精简脚本,等等。

这就是前台优化的一些策略,每一个都很细节,但就是这些细节决定了页面的呈现速度。在阅读HPWS的过程中,我也不止一次感慨:只有对一种技术了解到细节层面才能称为是专家,大面上的东西很容易就了解了,但关键是能不能深入原理和细节。

相关链接

HPWS一书中各条规则实例

浅谈jQuery

jQuery Icon

第一次分享会开始,第一次分享的是表面上的东西。这里所说的表面上的东西并不是指十分浅显的东西,而是直接与用户交互的那一部分。我们重点来说一说JavaScript的一个比较流行的库——jQuery。

JavaScript和HTML还有CSS一样,都是下载到用户本地,由用户的浏览器进行解释和执行的。

1、jQuery是什么

刚才已经说过了,它是一个JavaScript的库。这些库被用来简化JavaScript的开发,利用这些库,可以降低开发人员JavaScript的技术门槛,使用一些简单的方法实现高兼容性,高移植性的功能。比如对于初学者来说,难以写出同时兼容IE,Firefox,Chrome,Opera的JavaScript代码来,因为需要考虑的方面比较多。但是利用第三方库,这些兼容性问题就可以忽略掉了。说着说着感觉有点像Java的样子。

第三方库是怎么个作用呢?打个比方吧。比如说你想吃饺子,就得活面,做饺子馅,然后包饺子,再煮饺子。经过了这么多过程才终于可以吃了。库呢,就是把活面,做馅,包饺子这些过程全都交给一个包饺子机去做。你只要把原料放到里面,一按按钮,饺子就出来了。生活一下子就简单了!

除了jQuery,还有其他一些比较优秀的JavaScript库。例如Prototype,YUI,MooTools等等。其中Prototype是最早成型的JavaScript库之一,因为成型年代比较早,对面向对象的编程思想把握不是很到位,所以结构比较松散,现在似乎在慢慢改进,仍旧有一些网站使用这个库。

YUI是由Yahoo公司开发的JavaScript工具集,封装了一系列比较丰富的功能。现在和大龙在做的Moodle开发中,其中的JavaScript库使用的便是YUI。看过YUI2的部分文档,YUI2虽然封装了很多工具,但是使用上还是不够方便,有很多类似JavaScript的原始方法。YUI3作出了很大改进,向着简单易用的方向,改善了许多。

jQuery如此流行的原因就是因为它简单易用,容易上手。拥有轻量级的库,强大的选择器,出色的DOM操作,各种事件处理,兼容性解决方案等等特性。

jQuery强调的理念是写得少,做得多。虚的东西不多说了,咱们主要是分享经验,不是给jQuery做广告。

2、代码示例

点我穿越

一般来说,一个button的点击不会出现任何效果。但是可以利用JavaScript来控制它的点击效果。比如,我点这个按钮,浏览器就显示出个OK。再点一下又出来个OK。这都是浏览器解释出来的,并没有写到html文档中。我再刷新一下这个页面,这些OK就消失了。

$(),美元符号,这个是jQuery的工厂函数。能做选择器来用,也能用来创建节点。示例代码中的$(“button”)就是选中下面文档里<button></button>这个节点。同理,$(“#box”)是选中ID为box的节点。这里括号里面的选择方法和CSS是一样的。Click还有append就是jQuery的一系列方法中的两个。前者是监测点击事件,后者是用来向HTML中添加字符串或者各种元素。

这里说到了节点,为什么说成是节点,我们往下看。

3、jQuery对象 VS DOM对象

初次接触jQuery,经常分不清jQuery对象和DOM对象。冷不丁写出来一段代码,很有可能就不知道用什么方法来进行下一步。对用惯了JavaScript直接编码的人来说,这种障碍可能更大一些。初学者直接跨过JavaScript而是用jQuery的话,也需要对这两个对象深入理解才能进行更深入的学习。

什么是DOM?英文全称是Document Object Model,即文档对象模型。每一份DOM都可以表示成一棵树。树中的每个节点都是DOM元素节点。通过JavaScript方法获得的DOM元素就是DOM对象。而通过jQuery选择器取得的DOM元素,是对DOM对象进行的一个包装,此时得到的就是jQuery对象。对于jQuery对象,就可以使用相应的jQuery方法。

DOM对象,jQuery对象,我想了很久,想到一个比较形象的比喻可以解释他们的关系。想在用户交互界面实现一个功能,可以使用JavaScript方法操作DOM,也可以使用jQuery方法来操作DOM实现同样的功能。这就像是煮饭。有米有水,一个人选择生火用大锅煮饭,另一个人选择用电饭锅来煮。这两个人呢相当于不同的选择器;使用的灶台大铁锅,或者是电饭锅就是不同的封装;灶台点火,电饭锅插电,这就是两种不同方法。当然最后的结果都是生米煮成熟饭。而灶台不能插电,电饭锅不能点火,两种不同的对象不能使用此对象不存在的方法。不知道说清楚了没有,大家还要自己主动去研究研究理解理解。

分清DOM对象和jQuery对象对以后的使用会有很大帮助。DOM对象和jQuery对象之间还可以进行转换,这里就不详细介绍了,很简单。

4、DOM操作

DOM操作是JavaScript控制里最常用的操作之一。做前端,那必然会对页面显示的样式做各种各样的控制。这就离不开DOM操作。我将示例代码1做了一点点小修改,于是就诞生了示例代码2。我当时学jQuery的时候基本就是这样一点点研究这东西怎么用的。其实特别容易上手,有一些地方跟Java的语法比较像。

示例代码1里面已经提到了DOM的选择器以及向文档中插入文本。插入节点也是同样的道理,只要把文本换成HTML代码就是了。插入这个节点以后呢,DOM那棵树里面就多了一个节点。

看代码示例2,点我穿越。这段代码比示例代码1多了一个变量,用于标记一下是第几次点击。JavaScript中的变量是用var来声明的,和PHP中的变量类似,也是弱类型的,随着使用,可以随时将它的类型改变成需要的类型。然后,我每点击一次 “It works!”,就向id为box的盒中的ul节点下面添加li节点。这就是基本的向DOM中添加节点的方法。很简单,和添加字符串一样。Append方法和appendTo方法,这俩挺有意思。这个我就不说了,你们如果有兴趣研究jQuery的话,查文档的时候就能看到,然后实际编码实现一下就知道怎么回事了。

既然有创建和添加节点,那肯定有删除节点对吧。删除一个节点是使用remove方法。$(“#box”).remove();只要执行这一句,ID为box的节点就被删除了。我在给Cybery Reader添加Ajax效果的时候我就想,用户点击一个订阅源,我要把中间的内容清空,然后显示这个订阅源下的文章。然后呢,我就用了remove()方法。结果发现,整个盒都被删了。难道我还得新建一个盒?然后我就把删掉的盒子又用前面创建节点的方法添加回去了……再把订阅源中的文章添加到这个节点下。想想这种方法挺对的是吧?

我就想,我这办法也太闹心了吧,就没有个简单点的办法?后来我翻了一下方法列表,给找到了。清空节点中的内容可以使用empty方法,empty方法只清除当前选中元素的子元素。我们试一下,在代码示例2里面,给$(“#box ul”)对象添加一个empty()方法是什么效果。注意,jQuery支持方法的连接操作。用“.”将一个jQuery对象要执行的方法依次连接起来,执行的时候浏览器就会按照方法连接的顺序依次执行。

DOM操作中还有属性操作,样式操作以及文本操作。以Cybery Readeractions.js中的代码为例说明一下属性操作的方法attr()。Attr()接收两个参数,第一个参数是属性名称,第二个参数是属性的值,这两个参数都是字符串。如果只有一个参数,返回值是DOM元素中这个属性的值。如果再加上第二个参数,则把第一个参数指定的属性的值设置为第二个参数的值。例如actions.js第100行这段代码里面。首先取src属性的值,判断一下它是什么,然后108行和116行就是对这个属性进行修改。

样式操作、设置和获取HTML、文本的示例可以参考jQuery手册,不外乎使用几个方法,可能对一些值进行转换,也是比较简单的内容。不再举例了。

5、事件绑定

看代码示例3,点我穿越。一般的理解,我点击“伤不起有木有!”以后呢,这个列表中会再添加一个“伤不起”的项。这都没问题。我点新添加的“伤不起”,看看会不会再向列表中添加一个“伤不起”呢?

啊哦,一点反应都没有。这是为什么呢?因为新添加的节点对于浏览器来说是一个全新的对象,而脚本中并没有给这个新对象绑定任何动作。就好象我新买个电饭锅放家里,但是不插电,那它就永远无法工作。

给这段代码加一点小改进,就可以让新建元素工作了。看代码示例4,点我穿越,用count变量记录添加节点的顺序,为每个节点制定一个ID,然后新建节点以后用选择器选取此ID的节点,为它绑定一个click事件。这样就可以工作了。看看效果。我再点击“伤不起”的时候,这个伤不起的后面就出现了“有木有!”。有木有!有木有!

在一个jQuery对象后面加上.click这样就给这个对象绑定了click事件。还可以显示地使用bind(“click”, function(){…})来绑定click事件。如果想要取消绑定,可以使用unbind方法。

6、Ajax

Ajax全称Asynchronous JavaScript and XML,即异步JavaScript和XML。具体的技术细节这里就不研究了,直接说说jQuery中Ajax的使用。第一次作业里有的同学使用的iframe方法在当前页面载入其他页面的内容。有兴趣可以换成Ajax方法试试。

最简单的是load()方法了,这个方法有3个参数,url, data, callback。其中后两个参数都是可选参数。url参数是请求HTML页面的URL地址,data是发送至服务器的数据,callback是请求完成时的回调函数,无论请求成功或失败都会执行这个函数。

看下一个代码示例,点我穿越。看这段代码能不能大致了解一下我要做什么?点击”It works!”,然后向服务器发出GET请求,获取test.html页面。把这个页面中的内容添加到box中。

Load()方法通常用来从Web服务器上获取静态的数据文件,但是在实际项目中,总是需要给服务器传递一些参数。或许只是悄悄地上传一些数据,并不涉及到DOM操作。这时就可以使用$.post()和$.get()方法了。

看一下Cybery Reader中的Feed列表。这里面每一项都是一个链接,理论上我点击这个链接页面会跳转到链接制定的页面。但是我点一下,它为什么没有跳转呢?在click事件的function函数的最后打一行return false,这样就阻止了页面的跳转。并且使用POST方式向服务器发送了这个链接。

$.get()方法使用GET方法来进行异步请求,$.post()方法使用POST方式来进行异步请求。参数有url,data,callback,type。比load()方法多了一个type,表示服务器端返回内容的格式,包括XML,HTML,Json等。使用这两种方法能够显示地表现出用何种方式来进行异步请求。以actions.js中的代码来解释$.post方法的使用。40行使用Ajax动态添加评论。请求的页面是/comment/,发送的数据是comment。试着发送一条评论,看看Firebug上监视到的结果,结合我们这个callback function就知道这里是如何工作的了。$.get()方法与$.post()方法使用上完全相同。

Ajax就说到这里不再深入了。有兴趣的话,可以继续研究如何使用XML或者JSON来传送数据。

7、没讲到的东西

动画没有讲,这部分我用到的不是很多,感觉和事件处理也十分相似,看看jQuery文档就能知道个大概。

表单、表格的操作没有说。这个和DOM的属性操作很像。

XML、JSON会在Ajax中有涉及。XML就是另一种文档格式了,十分强大,在很多地方都用得上。已经超出今天的范围了,有兴趣的到图书馆借本书看看。或者查在线文档。JSON这个用得比较多,也是用来传递大块数据的。比如你的PHP脚本想要和JS脚本进行数组的传递,用JSON就很方便。

各种细节问题。单纯分享经验嘛,细节问题就不讨论了,不然就变成讲课了。其实说着说着已经变成jQuery快速入门了。快餐一般都没营养,想吃点好的,多看书多研究吧!

8、示例代码目录