文章分类 » 我要编程

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);
}

PHP开发框架浅析

PHP开发框架是什么

开发框架的定义我没有找到很准确的描述,下面几句话基本概括了开发框架的的功能和用途

  • 框架是一种应用程序的半成品;
  • 框架就像是人的骨骼一样;
  • 框架是一组可复用的组件;
  • 框架是一个可复用的设计构件……

简而言之,框架就是制定一套规范或者规则(思想),大家(程序员)在该规范或者规则(思想)下工作。或者说就是使用别人搭好的舞台,你来做表演。

PHP开发框架有哪些优缺点

优点:上面说过,框架其实就是别人把一些基础的代码给封装成库,让程序员来调用,例如表单验证、文件上传、验证码之类的基础功能;框架还把程序的设计架构给确定了,所以程序员只需按照框架规定的方法来写上自己的核心代码,程序就能基本成型了。所以应用PHP开发框架可以使程序员只需专注于应用的核心代码,基础代码可直接调用框架的类库,而且框架的设计架构可以保证团队开发时代码的一致性,所以可以大大提高WEB应用开发的效率。

缺点:这世界什么东西都不是完美的,所以利用框件架开发有利也有弊。PHP框架缺点就是过分封装了PHP的基础函数,对与一个PHP新手来说,他可能不需要学习PHP的任何基础函数,仅靠翻阅框架的说明文档和调用框架封装好的接口就能完成一个完整的PHP应用程序,从某方面说这样可以降低PHP应用开发的技术要求,但是这样对于学习来说是不利的,最后会导致过于依赖开发框架。在一个论坛里看到这样一句话评论PHP开发框架:

学之者生,用之者死

MVC架构

什么是MVC架构

MVC是软件工程中的一种软件架构模式,MVC把软件系统分成三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

模型(Model)“数据模型”(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。

视图(View) 视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。

控制器(Controller) 控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。

mvc

MVC架构在WEB开发中的应用

MVC在WEB应用程序中应用较广泛,右图是一个基于MVC的WEB请求处理过程。

首先,用户发送一个请求给控制器(Controller),控制器根据用户的请求向数据模型(Model)发送数据需求,数据模型根据需求进行相应的数据处理(如访问数据库、进行相应的计算等)后,把数据结果传回控制器,控制器得到数据后,就可以调用视图(View),生成一个页面,返回给用户。

开发一个简单的PHP框架

上面说了那么多,下面我们动手开发一个简单的PHP框架程序,框架听起来好像挺复杂的,其实还是很简单的,只要弄懂了它的思想就没有问题。我们框架的名字就叫small-framework吧。

粗略的分析用户请求的处理过程

我们知道PHP每次接收到请求时都要初始化全部资源,处理完毕后再释放全部的资源,PHP框架也是如此。框架接收到用户的请求后,需要一个初始化的过程,在初始化时实例化框架的核心模块,然后在把请求传送给框架的相应模块进行处理。因为不可能所有的请求都使用同一个控制器,除非程序功能非常简单,所以在初始化完成后,我们还需要根据用户的请求来调用相应的控制器,所以我们需要一个分发器(Dispatcher)来对用户的请求进行分发。在控制器里,我们就可以调用数据模型和视图来处理用户的请求了。

mvc-1

细致的分析用户请求的处理过程

上面粗略的分析了用户请求的处理流程,下面进行更加精细的分析

从上面的分析可以知道,要处理用户的请求需要先初始化框架的核心模块,如分发器模块,所以用户的请求首先需要被重定向至一个初始化页面,重定向可以使用.htaccess文件来实现,在我们这个框架里,我们首先把所有的请求都重定向至index.php里,在index.php里面完成初始化操作:初始化核心模块,我们还可以在初始化时读入框架的配置文件信息,然后调用分发器把请求分发到相应的控制器,实例化这个控制器,并调用控制器中的方法来处理用户的请求。在控制器里,我们可以获取用户的输入,判断用户的请求,然后调用相应的数据模型进行数据处理,控制器得到数据后,把数据传给视图,视图根据得到的数据返回一个页面给用户,请求结束。

mvc-2

框架的文件结构

根据上面的分析,我们可以列出small-framework的文件结构

  • Core/                    框架的核心模块

  • Dispatcher.php

  • Controller.php
  • Model.php
  • View.php

  • Controller/          自定义的控制器

  • View/                   自定义的视图
  • Model/                自定义的数据模型
  • index.php           框架的单一入口
  • config.php          框架的配置文件
  • .htaccess             将请求重定向至index.php

开始编码

有了以上的分析,框架工作的基本流程我们基本清楚了,下面我们就按照请求的处理顺序来开始编码

首先是.htaccess文件,

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /small-framework                  #基于框架的根目录进行重定向
    RewriteCond %{REQUEST_FILENAME} !-f    #如果用户请求的不是一个文件
    RewriteCond %{REQUEST_FILENAME} !-d   #如果用户请求的不是一个目录
    RewriteRule . index.php [L]                       #则重定向至index.php
</IfModule>

现在如果用户不是在请求js,css或者图片等静态文件时,用户的请求都会被重定向至index.php,下面我们编写index.php

define('ROOT_PATH', str_replace('\', '/', dirname(__FILE__)));//定义根目录 
/* 加载核心模块 */ 
require ROOT_PATH.'/config.php';//主要设置 
require ROOT_PATH.'/core/Dispatcher.php';//分发器模块 
$dpt = new Dispatcher();//实例化请求分发器 
$return_status = $dpt->run();

echo $return_status;

exit(0);

用户的请求在框架初始化完成后被传送到分发器中,分发器其实就是确定对用户请求分发的依据,我们可以根据URI分段来确定控制器,例如用户请求http://www.example.com/aaa/bbb,那么分发器就认为需要调用控制器aaa里面bbb方法来处理用户请求;还可以通过查询串的方式,例如对于请求http://www.example.com?controller=aaa&method=bbb,分发器就知道需要调用aaa控制器里面的bbb方法。在我们的small-framework里,我们采用URI分段的形式来对请求进行分发。分发器的代码如下:

class Dispatcher {
    private $path; 

    public function __construct() 
    {
        //实例化分发器时得到用户请求的URI 
        $this->path = $_SERVER['PATH_INFO'];
    }

    public function run()
    {
        //分析URI,得到相应的控制器和方法
        $this->path = trim($this->path, '/');
        $paths = explode('/', $this->path);

        //得到控制器类名和方法名
        $control = array_shift($paths);
        $method = array_shift($paths);

        //如果控制器类名和方法名为空,则默认为“index”
        if($control == '') $control = 'index';
        if($method == '') $method= 'index';

        //根据框架的文件结构,得到控制器类的文件路径
        $control_file_name = ROOT_PATH.'/controller/'.$control.'.php';

        if(file_exists($control_file_name))
        {
            include_once($control_file_name);

            $controller_name = $control.'_Controller';
            if(class_exists($controller_name))
            {
                //实例化控制器
                $control = new $controller_name();
                if(method_exists($controller_name, $method))
                {
                    //如果用户请求的方法存在,则调用之
                    $control->$method();
                    return OK_200;
                }
                else return ERROR_404';
            }
            else return ERROR_404;
        }
        else return ERROR_404;
    }
};

分发器通过$contorl->$method()调用了请求指定的控制器方法,现在我们就可以试验一下,在controller/下面建立一个aaa.php,在里面声明一个名为aaa_Controller的类和名为bbb的访问权限为public的方法,就可以通过 [你的测试地址]/aaa/bbb 来访问这个控制器了。

现在我们的small-framework实际上还不能被称作是一个框架,因为它还没有定义一些基本的操作,如调用数据模型、视图等,这些基本操作我们定义在core/下的Controller.php、Model.php、View.php里,用户自定义的控制器、模型和视图需要继承自这三个父类。我们需要在index.php中加载者三个父类。

在index.php中,除了加载核心模块以外,我们还加载了用户自己写的Model、Controller和View需要继承的父类,在父类里面我们需要定义一些框架的基本操作供用户调用

/* 加载Controller需要继承的父类 */
require ROOT_PATH.'/core/Controller.php';

/* 加载Model需要继承的父类 */
require ROOT_PATH.'/core/Model.php';

/* 加载View需要继承的父类 */
require ROOT_PATH.'/core/View.php';

首先是Controller.php,我们默认在实例化Controller时就实例化View,用户在控制器中可直接使用View中的函数;当然也可以让用户来手动载入View。代码如下

class Controller{

    protected $view = NULL;
    protected $model = NULL;

    public function __construct()
    {
        //默认实例化Controller时就实例化View
        $this->view = new View();
    }

    //用户通过model_name来手动载入相应的模型
    protected function load_model($model_name)
    {
        $model_file_name = ROOT_PATH.'/model/'.$model_name.'.php';
        require_once($model_file_name);
        $this->model = new $model_name();
    }
};

在View里我们定义了show方法,show方法的参数是视图文件名和传给视图文件的数据,用户在控制器里可以调用show方法来输出指定的视图

class View{
    public function show($view_file, $data=array())
    {
        $view_file_name = ROOT_PATH.'/view/'.$view_file.'.php';
        if(!file_exists($view_file_name)) return FALSE;

        //把数组展开,键名做变量名,键值做变量值
        extract($data);

        //引入view文件
        include($view_file_name);
        return TRUE;
    }
};

在Model.php里面我们可以封装一些数据库查询,在所有的查询之前对sql语句进行过滤,以保证数据库的安全

class Model{

    //数据库连接
    protected $link = NULL;

    public function __construct()
    {
        $this->link = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS);
        mysql_select_db(MYSQL_DB, $this->link);
        mysql_query("SET NAMES ".MYSQL_CHARSET);
    }

    /** 检查输入的sql语句,过滤敏感字符*/
    private function _check($sql)
    {
        /*此处添加对$sql的过滤*/
        return $sql;
    }

    /** 执行sql命令,成功返回结果集和TRUE,失败返回FALSE   */
    protected function query($sql)
    {
        return mysql_query($this->_check($sql));
    }

    /** 执行sql查询,返回结果数组,查询失败返回false*/
    protected function fetch_array($sql)
    {
        $res = $this->query($sql);
        return mysql_fetch_array($res);
    }

    /** 还可以添加其他的数据处理操作*/
};

到现在,我们的small-framework的就基本完成了,small-frame的很简单,但是作为学习软件的mvc架构和php框架的入门应该足够了,而且其他的大型PHP框架(这里的大型是相对于small-framework来说的)的工作原理于small-framework也都是大同小异的,大型框架中的分发器设置的会更加合理,还会有很多使用的类库供用户调用,有时间的话建议看看大型框架的源代码,学习其中的思想,大型框架里面还有许多代码优化可供学习。所以不要被某一种框架约束了思想,对于php框架来说

学之者生,用之者死

微博创新应用大赛参赛作品:围脖贴

项目背景及概述

围脖贴是我们参与新浪微博创新应用大赛的作品。其主要设计目标是将微博上的杂乱的信息进行有序化,用户可以以贴贴纸的方式很好的整理自己的微博。

现在新浪微博上的信息量比较大,每日都会有大量的针对某一话题的微博信息发出,但是这些微博在首页显示的相对分散,用户浏览起来也不是非常方便;而新浪的收藏功能也是没有分类,依然是将各种类型的微博都混杂在一起,同时也不够直观。

围脖贴可很好的解决这类的问题,用户可针对某一话题或某类型的微博在围脖贴上建立一个画布,再将这类的微博张贴到画布上进行保存,很好的将相对杂乱无章的信息进行了整理。

围脖贴的使用介绍

登录

首先就是登录了,该项目属于新浪微博应用,直接用新浪帐号进行链接后就可使用了。

查看最新画布

登录后进入首页,该页面会列出最近刚刚更新过的公开画布。可点击画布图标查看画布内容。进入画布后可以拖动画布背景来浏览整个画布的所有贴纸。也可以通过点击左下角的画布预览框定位窗口的位置。

创建自己的画布

点击画布右上角的首页就可回到首页,页面右侧可新建画布。填写并设置名称描述等相关内容后点击新建就创建属于自己的画布了。

查看属于自己所有的画布

创建新画布后就会自动跳转到自己的画布列表。在这里可以对画布进行查看,贴贴纸,编辑个性化设置和删除。

用画布管理自己的微博

在自己的画布列表中,点击贴贴纸操作即可对画布进行设计。进入画布页面后,其右侧会列出用户在新浪上的微博,可以选择罗列首页微博,个人微博或收藏微博等。拖拽右侧列表中的微博到画布上就会将其贴到画布上,之后还可以用鼠标点击拖动贴纸改变其位置或大小。

编写新的贴纸

除了可以管理已有的微博意外,画布页面左侧的工具条的最下面还有有张贴文字便签和图片便签的功能。点击后就可编写内容或上传图片来 张贴新贴纸。同时还可以将新编辑的贴纸的内容以微博的形式发布到新浪上。

个性化自己的画布

左侧工具条当中提供了设置画布信息和背景图片等功能。可以点击设置画布的背景图片,颜色和其他的一些属性,可以设计出自己喜欢的画布样式。

将画布分享到微博上

在查看和编辑画布的页面右上角有分享到微博按钮,单击后就可将该画布的链接分享到微博上,让你的朋友也能看到这个画布。

项目开发过程

遇到过的问题:

  • 之前从来没写过文件上传功能,是现到网上学的,写起来稍微费点事儿。
  • 对git不是很熟悉,起初经常pull或push失败也不知道为什么,还有次把所有文件的权限都给改成777了。
  • 对整体的框架不是很熟悉,开始的时候只是简单的知道增加一个功能就是大概那么个步骤但并不知道为什么,所以也因此出现了很多特别纠结问题。

一点收获:

  • 假期仔细阅读了整个项目的代码,了解了框架的运行流程,学习了.htaccess文件的功能。试着去学习其他一些主流的php框架
  • 一个应用如果想要有用户,用户体验是那么的重要。
  • 多人合作开发时,git的确是个非常好的工具。

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。然而点击链接,会没有任何反应。解决办法一是在委托的代码中对链接进行处理,二是阻止链接的事件冒泡。

源代码

源代码打包下载

HIT ACM Group新版网站上线

概述

HIT ACM Group的新版网站已经上线,欢迎大家积极使用,帮助开发组发现和解决bug,让我们的HOJ更加好用,更加强大。

尽管开发组的同学们付出了很大的努力,但是因为全面改版的工作量很大,依然有很多细节之处没有做好,如果给大家带来了使用上的不便,希望大家能够多多理解。

主要改进

新版网站主要做了如下改进:
  • 全面改写网站前端,使用DIV+CSS布局,替换掉原来的Table布局
  • 在保持网站基本风格不变的前提下,对一些样式细节做了调整,包括字体、顶部导航、侧边栏等
  • 新增了相册模块
  • 新增了资源下载模块
  • 新增了队伍Profile模块(该模块给每个队伍建立一个页面,记录该队伍的参赛记录、照片、荣誉等信息,目前正在进行数据的整理和录入,录入完毕后会开放)
  • 新增了Wiki模块(使用PmWiki搭建)
  • 重写了论坛模块(简化了论坛的功能,去掉了之前Joomla Fireboard中的很多花哨的功能,针对贴代码进行了优化,具体可参考怎样在论坛中贴代码)
  • 优化了Online Judge的大量页面,着重改进了界面样式和用户体验
  • 对侧边栏做了全面的优化,将不常用的链接完全移除出Online Judge的侧边栏,添加了大量常用功能链接
  • 对Online Judge后台题目的存储做了重新设计
  • 新增了管理员界面,方便管理员进行对比赛、题目的管理操作
  • 针对IE6做了优化,使得IE6下的体验基本不受影响

Bug提交

如果你在使用新版网站的过程中发现了bug,可以通过以下两种方式把bug提交给我们
  • 快速提交:在Online Judge模块中,右上角有一个”Report Bug…”的链接,直接在出现bug的页面上点击该链接,在弹出的窗口中输入bug的详细信息,然后点击”Submit”即可
  • 论坛提交:如果希望就某些bug或者功能需求进行讨论,可以到论坛的OJ Development 版块发贴

关于HOJ开发组

过去,对HOJ的开发都是由HOJ的管理员来负责,由于管理员有很多日常事务要处理,而且有些管理员本身也有参赛任务,因此,在时间和人力上都不是很充足。考虑到这种情况,我们从参与了 程序设计与实践 II – Web 开发 课程 的同学中挑选出了一批,组成了HOJ开发组,专门负责HOJ网站部分的开发和维护。一方面让OJ管理员从繁杂的开发任务中脱离出来,另一方面也让OJ有了一个长期稳定的开发团队。这对我们HIT ACM Group的长期发展是大有裨益的。

参与了此次网站升级工作的同学有

  • 付建宇
  • 田大龙
  • 刘一佳
  • 王洋
  • 张雄
  • 张志桐
,感谢他们的辛苦工作。

参与开发

目前HOJ开发组由 段志岩 主持,如果你想参与HOJ网站的开发,可发送邮件到 dzy0451@gmail.com

参与开发工作需要你具备一定的web开发技能,如果熟悉以下技术的一项或几项,那么对开发工作是很有帮助的

  • HTML & CSS
  • Javascript
  • PHP
  • Mysql
  • Apache
  • Linux系统管理

访问旧版

考虑到刚刚切换到新版本,有些同学可能使用起来不太习惯,这些同学可以访问旧版,在首页侧边栏中,点击 Online Judge(Old) 即可进入旧版的OJ。

请大家注意,旧版OJ仅是在网站新改版的这段之间中帮助大家过渡,不久后就将停止支持并下线。因此,我们鼓励大家使用新版。当在网络上分享指向HOJ的链接时,请使用新版的链接。

Cybery Reader v1.0 Released

主界面全部操作采用Ajax技术,具体内容:

  1. 点击订阅列表中的链接,就可以在中央区域看到订阅源中的文章。
  2. 未读文章数量实时显示:点击一篇未读过的文章,该订阅源在列表中的未读文章数会自减1。
  3. 可以向文章添加评论了:打开一篇文章,会在右侧边栏中看到本站其他用户对该篇文章做出的评论。用户也可以自由地向文章添加评论。注意:不会评论到原文章。评论只供站内用户查看。
  4. 可以为文章添加收藏标记了:每篇文章前面都有一颗五角星,点击以后五角星会点亮,系统会记录下这是用户喜欢的文章。
  5. 同一个订阅源内,同时只展开一篇文章供用户查看。
  6. 将添加订阅的功能移动到了主界面中,在主界面即可完成新订阅源的订阅工作。点击“添加订阅”会弹出文本框要求用户输入订阅地址。如果用户添加订阅时提交了一个含有订阅点的页面的链接,系统会发现该页面中的订阅点,并给出用户提示让用户选择需要订阅的RSS。
  7. 用户成功添加订阅源后,自动刷新Feed列表。

体验新版的Cybery Reader

PureWeber Goo Shortener – 学生作品

简介

这里是 bearzx(张雄) 和 7lemon(付建宇) 同学共同开发的 PureWeber Goo Shortener :http://goo.gl/a92Yc

欢迎大家参观、拍砖。

该程序是一个Goo.gl的包装,通过调用Goo.gl服务将长网址变换为短网址。

Goo.gl的API库由7lemon同学开发,界面设计由dzy0451同学山寨,界面实现和主程序由bearzx开发。

下载源代码

下载

WP Goo Shortener – 基于Goo.gl的WordPress短网址插件

WordPress插件WP Goo Shortener正式发布啦!下载地址:http://goo.gl/JE58N

插件介绍

WP Goo Shortener的作用是将文章链接转换成goo.gl短链接并按照指定格式附加到文章末尾,还可以将文章中指定的链接转换为goo.gl短链接后发布。此次发布的版本为1.0版。此插件还会有后续版本发布,敬请关注。

安装方法

进入Wordpress后台,在左边栏的“插件”菜单中点击“添加新插件”,直接选择上传WP-Goo-Shortener.zip即可。也可以在服务器端进入Wordpress的wp-content/plugins/目录,将WP-Goo-Shortener.zip中的文件解压到该文件夹内。然后到Wordpress后台激活插件即可。

使用说明

激活插件后,再次查看文章,就会将该文章链接的goo.gl短链接显示在文章末尾。您可以通过“GooShortener设置”页面来决定是否显示该链接,或者设置其显示为不同的格式。格式说明:通配符%url和%text在1.0版本中均会被替换成相应的goo.gl短链接。暂不支持自定义%text显示的内容。

使用此插件将文章中的长链接转换为短链接可使用如下形式:

<a href="http://goo.gl/eAzYi">http://goo.gl/eAzYi</a>

文章发布后会自动将链接缩短。缩短样式可参见文章开头的下载链接。

如果想在主题中自定义短链接显示位置,请关闭自动在文章末尾添加短链接的选项。然后在编辑主题时,在The Loop中调用以下两个函数:

  • the_goo_short_url():打印文章短链接(仅输出URL)
  • the_goo_short_link():按照设定的格式打印文章短链接

WP-Goo-Shortener 还提供两个函数,开发者可以在其他位置调用这两个函数:

  • get_goo_short_url($post_id):获取ID为$post_id的文章的短链接(仅输出URL)
  • get_goo_short_link($post_id):按照设定格式获取ID为$post_id的文章的短链接

联系信息

  • 作者:7lemon
  • 博客:http://www.7lemon.net
  • 邮箱:7lemonsoul (at) gmail.com

学做网页这件小事

神马都是浮云,神马都在云端。

在这个时代,很多人都会面临网页制作这个事情。特别是对广大程序猿们,网页制作应该是很基本的技能了——你也不用会得太多,但你也不能一点不会。

但是对于到底怎么学做网页这个小事,对新手来说却是个大事。遵循“复用”的原则,我希望这一篇文章能解除多个人的疑惑。

俗话说,师父领进门,修行靠个人。这里不会教你怎样做美工怎样编码怎样制作表单怎样控制样式,这里只会教你怎样去学习制作网页,怎样从一个菜鸟成长为一个老鸟。

我只是一个掌灯的NPC,打怪练级还得靠你自己。

好了,闲话不多说了,开始谈点正经的吧~以下的内容请循序渐进,一步一步来。

P.S. 为了降低学习的难度提高学习的速度,这里给出的基本都是中文资料。

1. 首先, 明确概念

做网页 vs 做网站

  • 做网页:这里就只代表制作静态网页。静态网页是一切网站的基础。制作静态网页非常之简单,少看个电影就能入门了。而且很多童鞋很多时候其实就只需要几个静态网页。本文会集中火力讲解怎样学习静态网页的制作。
  • 做网站:这里就指动态网页了。这个需要有高级程序语言的基础,这里不讲,等下篇吧。

设计网页 vs 制作网页

  • 设计网页:简单地说,就是指将网页画出来。这个属于美工的范畴,而不是程序。程序猿们还是先使劲学程序吧,你学的那点很难上得了厅堂。
  • 制作网页:简单地说,就是指将美工画出来的网页做成静态网页。目前国内这个还是程序猿的活。但国外很多美工都能做出代码质量很高的静态网页。因为静态网页的制作真的很好学,很简单。看到这里,你的信心增强不少了吧!

2. Web标准

这个部分第一次看的时候,不用看得太明白。等掌握了HTML和CSS之后应该再复习一下这个部分。

3. 唠叨几句

请同学们先暂时压抑一下自己写代码的冲动,请先看看以下的几点注意事项:

  • 用什么来写代码?不要使用自动生成代码的工具!不要用Dreamwaver的设计模式!如果你对自己要求不高,你可以使用Dreamwaver的代码模式。如果你对自己要求很高,推荐使用有代码高亮功能的文本编辑器,比如:Vim, UltraEdit,Emacs
  • 看教程时,所有示例代码必须自己实现
  • Dirty your hands. 实现示例时,所有的代码必须手动输入。从第一个示例程序就开始坚持!Ctrl+C Ctrl+V的话是学不会的。得真刀真枪才能锻炼出来
  • 当你等下看教程的时候,会发现会有很多内容,你或许会感叹内容太多甚至望而却步。不要慌!其实教程的很多东西都是目前不用屌的。先把这里罗列出的入门知识看完再去慢慢学其他的也不迟
  • 当你遇到问题的时候,首先应该思考——找到问题所在并尝试去找解决方案,其次应该查询参考手册(文后附下载地址),再次问Google(百度就免了),最后也可以到这里提问

4. HTML

HTML并非是单纯的一行一行的代码,而应该有严禁的结构——DOM。您写的不是代码,您写的也不是寂寞,您写的是一棵树。

HTML就是网页的骨骼,就是网页的结构。

关于HTML的教程,网上有很多,但很多都有些过时或者繁琐。个人比较推荐 网页设计师W3School (英文版)。

这个阶段需要看的内容为:

好了,对于HTML,就只需要学习这么多。不要惊讶,这就是基础,就这么少,但一定要全部掌握。

4. CSS

CSS就是网页的皮肤。学会CSS之后你就可以随心所欲地控制网页的样式了。

5. XHTML+CSS

这个部分将告诉你怎样将HTML和CSS结合起来使用,做出漂亮且实用的网页。一定要手动编写代码实践!

6. 回头复习 2. Web标准

7. 没有了

如果您能耐心地看完这里给出的资料,并且都实践了,那么恭喜您,入门成功!

这个时候你可以说自己会做静态网页了。

当然,要学的其实还有很多很多,比如如何切图,如何使用JavaScript等等。

有关这些的文章会在近期推出,有兴趣的同学可以关注一下,谢谢。

如有谬误,敬请斧正!

附. 手册下载

这两本手册很精简,但是绝对够用了,我一直就使用这两本手册。

HTML超精简手册:HTML参考手册

CSS2参考手册:CSS2参考手册

原文地址:http://www.fancycedar.info/2011/03/how-to-learn-web-programing/

Cybery-Reader低调上线

经过2010年末程序设计实践2——Web开发入门课程,几名同学组成了一个Web开发团队,在段志岩师兄的带领下,开始了Web开发寒假进阶班的培训。经过一个假期的努力,团队开发了一款简约的在线RSS Feed阅读器——Cybery-Reader。现已上线运行并开放注册,欢迎各位同学试用。

Cybery-Reader地址:http://www.pureweber.com/cybery-reader/

RSS Feed

可能有人不知道RSS Feed为何物。在Web2.0时代,各种网站都提供了Feed订阅服务。网站发布信息时,不仅仅把内容展示在网页上,还会把它们进行格式化后写入一个XML文件。这个XML文件就是Feed。根据格式不同还分为RSS Feed,Atom Feed。Feed阅读器应运而生。

Feed阅读器

你不再需要登录从前获取内容的网站查看是否有新的文章出现,只要网站提供Feed订阅,就可以使用阅读器订阅。阅读器会定时抓取Feed中的内容,一旦有更新就会在阅读器内显示。如果你每天有大量不同的网站需要看,如果你对新鲜事很敏感,利用阅读器订阅那些产生内容的站点吧!那将会省掉你大把的时间。只要打开阅读器,便能了解天下大事。

Cybery Reader使用教程

注册

初来乍到,没有身份是不行地!在首页点击“注册”按钮注册一个新用户吧。

邮箱认证

首先你要提供一个有效的电子邮件地址,用来收取注册验证码,以及方便我们以后联系你。 提交邮箱后请查看你的电子邮件,会有我们发给你的一封认证邮件。点击里面的链接进行下一步。

输入用户名和密码

用户名和密码是用来登录的凭证,请一定不要忘记,不然会很麻烦的。

完成注册

下面给自己取一个好听的昵称吧!

主界面

欢迎使用Cybery Reader。注册完成后你会自动订阅我们的课程网站http://www.pureweber.com。还能看到其他的热门订阅,看看其他人都订阅了什么。下面要开始自己的订阅啦!

如何订阅

在正式使用阅读器之前,你需要登录到想订阅的网站,查看该网站是否提供Feed订阅服务。网站是否提供订阅很明显的一个标志就是看它的页面上是否含有类似这样的图标: 如果有,那一定就提供订阅服务啦!一般来说,这个图标就指向一个Feed地址了。先把这个地址复制下来备用。

在Cybery Reader的主界面右上角,有一个“添加订阅”按钮,点击这个按钮进入订阅页面。将刚才复制下来的地址贴到图示内的文本框中。然后点击“订阅”按钮,耐心等待几秒钟,我们的后台正在抓取你订阅的Feed,一旦抓取完成就会返回主界面。

有人问了,怎么这么麻烦呢?我还要登录到那个网站?其实如果你懒得满网页找Feed链接的话,也可以直接把网站的地址贴到文本框里。Cybery Reader会替你在网站上查找Feed链接,如果找到的话,会在提示信息中显示该链接,复制下来粘进去再订阅就可以啦!

查看订阅

我的Feed

你订阅的所有Feed都会显示在左边栏“我的Feed”下。查看对应Feed的条目只要点击其标题,就能在中间看到Feed中的所有条目了。默认只显示每个条目的标题,点击相应标题,文章就会展开,显示其中的内容。

热门Feed

热门Feed顾名思义,就是最受欢迎的几个Feed。我们推荐给你这些Feed,供你阅读参考。如果喜欢,你也可以把他们专门添加进你的Feed以方便标记和分享(功能开发中)。

结语

Cybery Reader是几个初学者用不到一个月时间完成的成果,虽然功能上还不是很完善,还有很多需要改进的地方,但是做到这个程度便给了我们很大的自信。做一件事,不在于你有多么完美多么独特的想法,不在于你有多么深厚渊博的知识,而在于你是否坚持去做。做的时候发现问题,有了问题及时去学习和改正。有时候,一句“没时间”便可能磨灭了兴趣,丧失了机会。

当然,Cybery Reader做成现在这样是远远不够的,我们的团队会继续维护这个项目,努力使它能够真正应用于生产之中。