Home > Web Front-end > H5 Tutorial > Share the code to implement water flow and pool animation using canvas

Share the code to implement water flow and pool animation using canvas

零下一度
Release: 2017-05-17 14:28:12
Original
6158 people have browsed it

Use the canvas tag of HTML 5 to draw water flow and pool animations

Before using the canvas tag of HTML 5 to draw animations, let’s first introduce it Basic knowledge, including canvas (if you are familiar with canvas, you can skip this section directly), oCanvas framework, and sprite animation. After understanding the above basic knowledge, you can start using canvas for animation.

Introduction to the canvas tag

In this part, there are many things and it is complicated. Related introductions can be found on various websites. I will not reinvent the wheel here. The tutorials for novices are It's pretty good. I also recommend a very good blog post about how to play with HTML 5 canvas drawing. This article introduces it in great detail. I recommend you read it carefully.

Introduction to canvas framework oCanvas

The canvas tag is very powerful. It can process pictures and perform pixel-level processing. It can completely replace the flash on the browser side. However, the development of canvas has not yet been perfected. , the API is not yet complete, and the event processing functions of elements have not yet provided interfaces. It still takes a lot of effort to implement some complex functions, so many third-party canvas-based frameworks have emerged. Compared with native frameworks, these frameworks The canvas tag has more simple and easy-to-use APIs, which greatly improves the efficiency of our coding. Here I choose the oCanvas framework. Related usage documents and demos can be viewed in the link above.

Introduction to Elf Animation

Elf animation generally consists of a set of custom attribute values ​​and 3 sub-functions (init, advance, draw).
The functions of the three functions are as follows:
init: Initialize the attribute value of the sprite animation
advance: Update the status value of the next frame before drawing the next frame
draw: Add the advance function to The updated status value is drawn into the canvas.
The execution order of the above three functions is: init->advance->draw->advance->draw->...and the loop continues. Let me illustrate the above execution process with an example of randomly generating rising bubbles.

var constructor_bubble = function (settings, core) {

    return oCanvas.extend({
        core: core,
        shapeType: "rectangular",//下面定义了上面我们提到的三个函数:init(),advance(),draw()//在init中,我们map对象组、一个空的数组和一个代表高度的属性值
        init: function () {
            this.map=[
                {r:2,speed:3},
                {r:3,speed:3},
                {r:4,speed:3},
                {r:5,speed:3},
                {r:6,speed:3},
                {r:7,speed:3},
                {r:8,speed:3},
                {r:9,speed:3},
                {r:10,speed:3}
            ];                this.points=[];                this.height=this.container.height_now;

        },//下面是advance函数,在函数中我们利用if逻辑判断是否添加新的气泡以及进行气泡的位置更新,points数组利用队列的先进先出来存储气泡的 
        advance: function () {
            this.height=this.container.height_now;                if(Math.random()>0.95){                    var new_point={
                        x:this.start.x+this.offset*2*(Math.random()-0.5),
                        y:this.start.y-this.map[0].r,
                        r:this.map[0].r
                    };                    this.points.push(new_point);

                }            if(this.points.length>0){                for(var i=0;i<this.points.length;i++){                    this.points[i].x+=this.offset*2*(Math.random()-0.5);                    this.points[i].y-=3;                    if(this.start.y-this.points[i].y>this.height-this.points[i].r-33){                        this.points.shift();
                    }
                }
            }
        },//draw函数中,利用canvas的圆弧绘制指令,将points数组中存储的气泡依次画出        
        draw: function () {
            var canvas = this.core.canvas;

            canvas.lineJoin = &#39;round&#39;;
            canvas.lineWidth = this.GDwidth;
            canvas.strokeStyle = "#fff";            if(this.points.length>0){                for(var i=0;i<this.points.length;i++){
                    canvas.beginPath();
                    canvas.arc(this.points[i].x,this.points[i].y,5,0,2*Math.PI);
                    canvas.stroke();
                    canvas.closePath();
                }

            }

        }
    }, settings);
};
oCanvas.registerDisplayObject("bubble", constructor_bubble, "init");//下面是在应用中定义和添加上面定义的精灵动画,其中:start数组代表了气泡的产生点,container代表了气泡的存在区域var pp1=canvas.display.bubble({
     start:{x:425,y:566},
     container:SC02,
     width:50,
     offset:1,
     speed:5
 }).add();
Copy after login

Realization of water flow and pool animation

The following is a detailed introduction to the detailed steps of how to implement water flow animation and pool animation in the project:

Drawing of pool animation

var constructor_show = function (settings, core) {
    return oCanvas.extend({
        core: core,
        shapeType: "rectangular",//上面四行都是oCanvas框架的结构语法/*下面init()、advance()、draw()分别是上节中说的动画精灵三元素,第一个用来初始化,第二个用来
更新操作,第三个用来绘制图像/动画在管道对象中,定义了一些属性,包括:x、y、height、width、start、
height_now、full、speed、fill、trail_flag、[trail]。其中x、y分别代表水池参考点相对画布左
上角的位置,height、width是水池的宽高属性,start表征了动画是否开始,height_now代表了水池中水
位的高度,full表征了水池是否填满,speed水池上涨的速度,fill水的颜色,trail_flag表征了该水池
是否是一个标准的矩形,如果不是的话,配合trail属性,指定水池的轮廓*/
       init: function () {     
       //默认动画关闭,水池full为0,当前高度为0
            this.start=0;            this.full=0;            this.height_now=0;
        },
        advance: function () {
        //如果水池未满并且是开启状态,水位未满就更新当前高度,否则将full置为1
           if(this.start==1&&this.full!=1){               if (this.height_now < this.Height) {                   this.height_now += this.speed;
               }               else {                   this.full = 1;
               }
           }
        },
        draw: function () {
            var canvas = this.core.canvas,            //先获得水池的位置
                origin = this.getOrigin(),
                x = this.abs_x - origin.x,
                y = this.abs_y - origin.y;            //开始绘制
            canvas.beginPath();
            canvas.strokeStyle = "#000";            if (this.trail_flag == 1) {            //如果是不规则图形,描出轮廓
                canvas.moveTo(this.trail[0].x_t, this.trail[0].y_t);                for (var i = 1; i < this.trail.length; i++) {
                    canvas.lineTo(this.trail[i].x_t, this.trail[i].y_t);
                }
                canvas.lineTo(this.trail[0].x_t, this.trail[0].y_t);
                canvas.clip();
            }            if (this.fill !== "") {            //设置颜色,绘制矩形水池
                canvas.fillStyle = this.fill;
                canvas.fillRect(x, y + this.Height - this.height_now, this.Width, this.height_now);
            }
            canvas.closePath();
        }
    }, settings);
};//将上面的动画精灵注册进oCanvas的display图形库中oCanvas.registerDisplayObject("SC_show", constructor_show, "init");
Copy after login

Drawing of pipeline water flow animation

In the pipeline water flowmodel, the following attributes are defined:
destination: the position of the front end of the current water flow
cells: Pipe path array
deta: Direction hypotenuse length
deta_x: Direction x side length
deta_y: Direction y side length
flag_x: Cosine value
flag_y:Sine value
cellIndex: index of the current drawing edge
Speed: water flow speed
GDwidth: water flow width
LineHeight: water flow length
x_now: x coordinate of the current drawing point
y_now: y coordinate of the current drawing point
firstX: The coordinate x of the first point of the pipeline
firstY: The coordinate y of the first point of the pipeline
beginHeight: The starting point of the first section of water flow
endHeight: The unfinished water line left in the previous section of the pipeline
legacyHeight: the length left by the front end point in the previous pipe
paused: whether to start
fill: water flow color
full: whether to fill up

init function

//init函数主要完成初始化工作init: function () {
            this.x_now = this.cells[0].x_cell;
            this.y_now = this.cells[0].y_cell;
            this.firstX = this.x_now;
            this.firstY = this.y_now;
            this.endHeight = 0;
            this.beginHeight = 0;
            this.paused=0;
            this.full=0;

            this.cellIndex = 0;
            this.destination.x_d = this.cells[0].x_cell;
            this.destination.y_d = this.cells[0].y_cell;

            this.legacyHeight = -1;
            this.LineHeight=10;
            this.Speed=2*this.LineHeight/20;
        }
Copy after login

advance function

//advance函数主要实现每次动画的刷新步进操作
 advance: function () {
            if(this.paused==1){
                if (this.cellIndex < this.cells.length - 1) {
                    this.deta_x = this.cells[this.cellIndex + 1].x_cell - this.cells[this.cellIndex].x_cell;
                    this.deta_y = this.cells[this.cellIndex + 1].y_cell - this.cells[this.cellIndex].y_cell;
                    this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y);
                    this.flag_x = this.deta_x / this.deta;
                    this.flag_y = this.deta_y / this.deta;
                    if (this.legacyHeight >= 0) {
                        this.cellIndex++;
                        if (this.cellIndex < this.cells.length - 1) {
                            this.deta_x = this.cells[this.cellIndex + 1].x_cell - this.cells[this.cellIndex].x_cell;
                            this.deta_y = this.cells[this.cellIndex + 1].y_cell - this.cells[this.cellIndex].y_cell;
                            this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y);
                            this.flag_x = this.deta_x / this.deta;
                            this.flag_y = this.deta_y / this.deta;
                            this.destination.x_d = this.cells[this.cellIndex].x_cell + this.flag_x * this.legacyHeight;
                            this.destination.y_d = this.cells[this.cellIndex].y_cell + this.flag_y * this.legacyHeight;
                            if (Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) > this.Speed * Math.abs(this.flag_x) || Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) > this.Speed * Math.abs(this.flag_y)) {
                                this.legacyHeight = -1;
                                this.destination.x_d += this.flag_x * this.Speed;
                                this.destination.y_d += this.flag_y * this.Speed;
                            }
                            else {
                                if (this.flag_x == 0) {
                                    this.legacyHeight = this.Speed - Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) / Math.abs(this.flag_y);
                                }
                                else {
                                    this.legacyHeight = this.Speed - Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) / Math.abs(this.flag_x);
                                }
                            }
                        }
                    }
                    else {
                        this.destination.x_d += this.flag_x * this.Speed;
                        this.destination.y_d += this.flag_y * this.Speed;
                        if (Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) >= this.Speed * Math.abs(this.flag_x) && Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) >= this.Speed * Math.abs(this.flag_y)) {
                            this.legacyHeight = -1;
                        }
                        else {
                            if (this.flag_x == 0) {
                                this.legacyHeight = this.Speed - Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) / Math.abs(this.flag_y);
                            }
                            else {
                                this.legacyHeight = this.Speed - Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) / Math.abs(this.flag_x);
                            }

                        }
                    }
                }else{
                    this.full=1;
                }
                this.deta_x = this.cells[1].x_cell - this.cells[0].x_cell;
                this.deta_y = this.cells[1].y_cell - this.cells[0].y_cell;
                this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y);
                this.flag_x = this.deta_x / this.deta;
                this.flag_y = this.deta_y / this.deta;
                if (this.paused == 1) {
                    if (Math.abs(this.firstX - this.cells[0].x_cell) >= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.firstY - this.cells[0].y_cell) >= this.LineHeight * Math.abs(this.flag_y)) {
                        this.firstX = this.cells[0].x_cell;
                        this.firstY = this.cells[0].y_cell;
                        this.beginHeight = 0;
                    }
                    else {
                        if (this.beginHeight < this.LineHeight) {
                            if (this.beginHeight + this.Speed >= this.LineHeight) {
                                this.beginHeight = this.LineHeight;
                            }
                            else {
                                this.beginHeight += this.Speed;
                            }
                            this.firstX = this.cells[0].x_cell;
                            this.firstY = this.cells[0].y_cell;
                        }
                        else if (this.beginHeight == this.LineHeight) {
                            this.firstX += this.flag_x * this.Speed;
                            this.firstY += this.flag_y * this.Speed;
                        }
                    }
                }
            }

        }
Copy after login

draw function

//draw函数在每次advance之后执行,将每次的步进更新重新绘制到画布上
 draw: function () {
            var canvas = this.core.canvas;            this.x_now = this.firstX;            this.y_now = this.firstY;            this.deta_x = this.cells[1].x_cell - this.cells[0].x_cell;            this.deta_y = this.cells[1].y_cell - this.cells[0].y_cell;            this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y);            var myEnd = false;            this.flag_x = this.deta_x / this.deta;            this.flag_y = this.deta_y / this.deta;

            canvas.beginPath();
            canvas.lineJoin = &#39;round&#39;;
            canvas.lineCap="round";            this.endHeight = 0;
            canvas.lineWidth = this.GDwidth / 4;
            canvas.strokeStyle = this.fill;            if (this.beginHeight > 0) {
                canvas.moveTo(this.x_now, this.y_now);
                canvas.lineTo(this.x_now + this.flag_x * this.beginHeight, this.y_now + this.flag_y * this.beginHeight);
            }            this.x_now += this.flag_x * (this.beginHeight + this.LineHeight);            this.y_now += this.flag_y * (this.beginHeight + this.LineHeight);            for (var i = 1; i <= this.cellIndex; i++) {
                myEnd = false;                this.deta_x = this.cells[i].x_cell - this.cells[i - 1].x_cell;                this.deta_y = this.cells[i].y_cell - this.cells[i - 1].y_cell;                this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y);                this.flag_x = this.deta_x / this.deta;                this.flag_y = this.deta_y / this.deta;                if (this.endHeight > 0) {
                    canvas.moveTo(this.cells[i - 1].x_cell, this.cells[i - 1].y_cell);
                    canvas.lineTo(this.cells[i - 1].x_cell + this.flag_x * (this.endHeight ), this.cells[i - 1].y_cell + this.flag_y * this.endHeight);                    this.x_now = this.cells[i - 1].x_cell + this.flag_x * (this.LineHeight + this.endHeight);                    this.y_now = this.cells[i - 1].y_cell + this.flag_y * (this.LineHeight + this.endHeight);
                }                if (this.endHeight < 0) {                    this.endHeight = Math.abs(this.endHeight);                    this.x_now = this.cells[i - 1].x_cell + this.flag_x * (this.endHeight);                    this.y_now = this.cells[i - 1].y_cell + this.flag_y * (this.endHeight);
                }                if (this.endHeight == 0 && i != 1) {                    this.x_now = this.cells[i - 1].x_cell;                    this.y_now = this.cells[i - 1].y_cell;
                }                while (Math.abs(this.x_now - this.cells[i].x_cell) >= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.cells[i].y_cell) >= this.LineHeight * Math.abs(this.flag_y)) {
                    canvas.moveTo(this.x_now, this.y_now);
                    canvas.lineTo(this.x_now + this.flag_x * this.LineHeight, this.y_now + this.flag_y * this.LineHeight);                    this.x_now += this.flag_x * this.LineHeight;                    this.y_now += this.flag_y * this.LineHeight;                    if (Math.abs(this.x_now - this.cells[i].x_cell) <= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.cells[i].y_cell) <= this.LineHeight * Math.abs(this.flag_y)) {                        if (this.flag_x == 0) {                            this.endHeight = Math.abs(this.y_now - this.cells[i].y_cell) / Math.abs(this.flag_y) - this.LineHeight;
                        }                        else {                            this.endHeight = Math.abs(this.x_now - this.cells[i].x_cell) / Math.abs(this.flag_x) - this.LineHeight;
                        }                        //this.endHeight = (Math.abs(this.y_now - this.cells[i].y_cell) + Math.abs(this.x_now - this.cells[i].x_cell) - this.LineHeight * (Math.abs(this.flag_y) + Math.abs(this.flag_x)))/2;
                        myEnd = true;                        break;
                    }                    else {                        this.x_now += this.flag_x * this.LineHeight;                        this.y_now += this.flag_y * this.LineHeight;
                    }
                }                if (myEnd == false && Math.abs(this.x_now - this.cells[i].x_cell) <= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.cells[i].y_cell) <= this.LineHeight * Math.abs(this.flag_y)) {
                    canvas.moveTo(this.x_now, this.y_now);
                    canvas.lineTo(this.cells[i].x_cell, this.cells[i].y_cell);                    //this.endHeight = this.LineHeight - Math.abs(this.x_now - this.cells[i].x_cell)*flag.x_flag - Math.abs(this.y_now - this.cells[i].y_cell)*flag.y_flag;
                    if (this.flag_x == 0) {                        this.endHeight = this.LineHeight - Math.abs(this.y_now - this.cells[i].y_cell) / Math.abs(this.flag_y);
                    }                    else {                        this.endHeight = this.LineHeight - Math.abs(this.x_now - this.cells[i].x_cell) / Math.abs(this.flag_x);
                    }                    //this.endHeight = ( this.LineHeight * (Math.abs(this.flag_y) + Math.abs(this.flag_x)) - Math.abs(this.y_now - this.cells[i].y_cell) + Math.abs(this.x_now - this.cells[i].x_cell)) / 2;
                }
            }            if (this.cellIndex < this.cells.length - 1) {

                myEnd = false;                this.deta_x = this.cells[this.cellIndex+1].x_cell-this.destination.x_d;                this.deta_y = this.cells[this.cellIndex+1].y_cell-this.destination.y_d;                this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y);                if (this.deta > 0) {                    this.flag_x = this.deta_x / this.deta;                    this.flag_y = this.deta_y / this.deta;                    if (this.endHeight > 0) {
                        canvas.moveTo(this.cells[this.cellIndex].x_cell, this.cells[this.cellIndex].y_cell);
                        canvas.lineTo(this.cells[this.cellIndex].x_cell + this.flag_x * (this.endHeight ), this.cells[this.cellIndex].y_cell + this.flag_y * this.endHeight);                        this.x_now = this.cells[this.cellIndex].x_cell + this.flag_x * ( this.endHeight);                        this.y_now = this.cells[this.cellIndex].y_cell + this.flag_y * ( this.endHeight);                        if(Math.abs(this.destination.x_d-this.x_now)>this.LineHeight*Math.abs(this.flag_x)||Math.abs(this.destination.y_d-this.y_now)>this.LineHeight*Math.abs(this.flag_y)){                            this.x_now+=this.LineHeight*this.flag_x;                            this.y_now+=this.LineHeight*this.flag_y;
                        }                        else{                            this.x_now=this.destination.x_d;                            this.y_now=this.destination.y_d;
                        }                    if (this.endHeight < 0) {                        this.endHeight = Math.abs(this.endHeight);                        this.x_now = this.cells[this.cellIndex].x_cell + this.flag_x * (this.endHeight);                        this.y_now = this.cells[this.cellIndex].y_cell + this.flag_y * (this.endHeight);
                    }                    if (this.endHeight == 0 && this.cellIndex > 0) {                        this.x_now = this.cells[this.cellIndex].x_cell;                        this.y_now = this.cells[this.cellIndex].y_cell;
                    }                    if (this.cellIndex == 0) {                        this.x_now = this.firstX;                        this.y_now = this.firstY;                        if (this.beginHeight > 0) {
                            canvas.moveTo(this.x_now, this.y_now);
                            canvas.lineTo(this.x_now + this.flag_x * this.beginHeight, this.y_now + this.flag_y * this.beginHeight);
                        }                        this.x_now += this.flag_x * (this.beginHeight + this.LineHeight);                        this.y_now += this.flag_y * (this.beginHeight + this.LineHeight);
                    }                    while ((Math.abs(this.x_now - this.destination.x_d) >= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.destination.y_d) >this.LineHeight * Math.abs(this.flag_y))||(Math.abs(this.x_now - this.destination.x_d) > this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.destination.y_d) >=this.LineHeight * Math.abs(this.flag_y))) {
                        canvas.moveTo(this.x_now, this.y_now);
                        canvas.lineTo(this.x_now + this.flag_x * this.LineHeight, this.y_now + this.flag_y * this.LineHeight);                        this.x_now += this.flag_x * this.LineHeight;                        this.y_now += this.flag_y * this.LineHeight;                        if (Math.abs(this.x_now - this.destination.x_d)<= this.LineHeight * Math.abs(this.flag_x)&&Math.abs(this.y_now - this.destination.y_d) <= this.LineHeight * Math.abs(this.flag_y)) {
                            myEnd = true;                            break;
                        }                        else {                            this.x_now += this.flag_x * this.LineHeight;                            this.y_now += this.flag_y * this.LineHeight;
                        }
                    }                    if (myEnd == false && Math.abs(this.x_now - this.destination.x_d) < this.LineHeight * Math.abs(this.flag_x) || Math.abs(this.y_now - this.destination.y_d) < this.LineHeight * Math.abs(this.flag_y)) {
                        canvas.moveTo(this.x_now, this.y_now);
                        canvas.lineTo(this.destination.x_d, this.destination.y_d);
                    }
                }
            }
            canvas.stroke();
            canvas.closePath();
        }
Copy after login

Example of water pump room

The following code defines a pool object and assigns corresponding attribute values. Finally Add the defined object to the canvas canvas.

var SC01 = canvas.display.SC_show({        x: 326,        y: 200,
        Width: 181,
        Height: 438,
        height_now: 0,
        trail_flag: 0,
        t: 0,
        fill: color_SC,
        speed:speed_SC,
        full:0,
        start:0
    });canvas.addChild(SC01);
Copy after login

The following code defines a pipeline object, assigns some initial values ​​to the pipeline object, and finally adds it to the canvas.

var GD01 = canvas.display.GD({        x: 0,        y: 0,
        destination: {
            x_d: 0, y_d: 0
        },
        cells: [
            {x_cell: 195, y_cell: 587},
            {x_cell: 335, y_cell: 587}
        ],
        deta: 1,
        deta_x: 1,
        deta_y: 0,
        flag_x: 1,
        flag_y: 0,
        cellIndex: 0,        
        Speed: speed_all,     
        GDwidth: width_all,     
        LineHeight: 10,     
        x_now: 0,      
        y_now: 0,       
        firstX: 0,     
        firstY: 0,      
        beginHeight: 0,    
        endHeight: 0,     
        legacyHeight: 0,
        paused: 1,
        fill:color_GD,
        full:0
    });
 canvas.addChild(GD01);
Copy after login

The process control of the specific animation is as follows:

 canvas.setLoop(function () {
        //下面6个advance函数实现每一帧中的动画对象的更新操作
        GD01.advance();
        SC01.advance();
        SC02.advance();
        GD02.advance();
        SC03.advance();
        GD03.advance();
        //下面几个if语句实现动画的流程控制
        if(GD01.full==1){
            SC01.start = 1;
            SC02.start = 1;
        }
        if(SC02.full==1){
            GD02.paused = 1;
        }
        if(GD02.full==1) {
            SC03.start = 1;
            arrow_1.start();
            arrow_2.start();
        }
        if(SC03.full==1) {
            GD03.paused = 1;
        }
        canvas.redraw();  //重绘画布
    }).start();       //循环开始
Copy after login

[Related recommendations]

1. Special recommendation "php Programmer Toolbox" V0.1 version download

2. h5canvas realizes the special effect code of falling snowflakes

3. h5 Example of Fill and Stroke text effects in Canvas

The above is the detailed content of Share the code to implement water flow and pool animation using canvas. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template