HTML5边玩边学(6):汽车人,变形......

简介:

一、状态及其保存和恢复

在这一节开始之前,我们要先理解一下什么是状态以及状态的保存和恢复。玩过 MFC 编程的人经常能碰到这样的代码:

pOldPen=pDC->SelectObject(pNewPen)

我们在选择一个新画笔对象的同时,总是要保存住旧画笔对象,为什么要这样做呢?因为新画笔对象只是临时用一下,等用完了,我们想恢复到原来的画笔配置时,如果旧的配置事先没有被保存,这些配置就丢失了,也就没办法恢复了。

在 HTML5 绘图中,某一刻的状态就是当前这一刻上下文对象的一系列属性的配置值,只是,决定一个画笔状态的属性比较少,如颜色、粗细、线型之类的,而确定上下文状态的属性比较多,包括下面这些:

1、当前上下文对象的移动、旋转、缩放配置

2、当前上下文对象的 strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 属性值

3、当前上下文对象的裁剪路径配置

上面这一系列配置决定了当前这一刻上下文对象的状态,其中移动、旋转、缩放、globalCompositeOperation(组合)、裁剪下面我们马上会讲到。

 

二、状态的保存与恢复

上面我们说某一刻的状态由那么多属性决定,我们要保存这一刻的状态就要把这些属性值一个一个都保存,恢复的时候在一个一个都设置回去,那也太麻烦了。确实是这样的,所以上下文对下提供了了两个简单的方法,对状态进行保存和恢复,他们是:

save( ) 和 restore( )

save 和 restore 方法可以多次调用,每调用一次 save 方法,调用时的状态(即一系列属性值)就压入一个栈中。

每调用一次 restore 方法,最后一次 save 的状态就被恢复,即出栈。

想象一下弹匣,第一颗被发射出去的子弹,总是最后一个被压入弹匣的。

 

三、变型

1、移动:translate(dx,dy)

这个方法看上去很简单,其实它包含了一定的数学含义,你可以认为是整个坐标系的原点发生了移动,新坐标系下任意一点(x,y)相当于原坐标系下的坐标为:

x'=x+dx
y'=y+dy

如果我们调用 ctx.translate(5,8) 改变上下文对象的坐标系状态,然后在新状态下的点(3,2)绘图,相当于图像被绘制到了原状态下的点(8,10)处,即

x'=5+3=8
y'=5+2=10

也许你会问,为什么要那么麻烦,直接在(8,10)处绘制比行吗?比如把

ctx.translate(5,8)
ctx.drawImage(img,3,2)

改成

ctx.drawImage(img,8,10)

这样不是更简单、更直接吗?

我的理解是,移动更多的情况下是为其他图形变换服务的,恰当的改变坐标原点可以让图形学计算更好理解,并带来很大方便,下面我举个简单的例子,假如:

有一条线段 ,是 x 轴正向上的一小段

y = 0 (1 <= x <= 3),

如果以坐标原点为圆心,逆时针旋转90度,则线段与 y 轴正向重合,旋转后的线段为:

x = 0 (1 <= y <= 3)

但是我们不可能每次旋转都以原点为圆心进行旋转,假如我们以线段的一个端点(1,0)为圆心进行旋转,我们怎么才能得到旋转后线段上每一点的坐标值呢?其实这个过程可以分为三步:

第一步:移动原点坐标到(1,0),新的线段依然在 x 轴上,但是方程变为了:y = 0 (0 <= x <= 2)

第二步:以新坐标系的原点为圆心进行旋转,得到新坐标系下的线段 x = 0 (0 <= y <= 2)

第三步:将新坐标系的原点移动到新坐标系下(-1,0)处,即将原点恢复到原来的位置,此时的线段为:x = 1 (0 <= y <= 2)

第三步所得到的线段就是最后需要绘制的线段。

从这个例子我们可以看出来,即使在这么简单的情况下,如果不移动坐标原点来直接计算旋转后的图形,也是比较困难的。

提示当你移动坐标原点之前,千万别忘了保存状态,当然,绘制完毕后也别放了恢复状态。

2、缩放 scale(sx, sy)

这个同样很简单,sx, sy 是缩放比例因子,缩放后新坐标系下任意一点 (x,y) 相当于原坐标系下的坐标为:

x' = x * sx
y' = y * sy

同样,改变坐标系统总是不要忘记保存和恢复状态

3、旋转 rotate(A)

angle 是旋转角度,旋转后新坐标系下任意一点 (x,y) 相当于原坐标系下的坐标为:

x' = x cosA - y sinA
y' = x sinA + y cosA

同样,改变坐标系统总是不要忘记保存和恢复状态

4、变形 transform(m11, m12, m21, m22, dx, dy)

其实前面讲的移动、缩放、旋转都是变形的特例,transform 方法的六个参数组成了一个变形矩阵,如下

m11 m21 dx
m12 m22 dy
0 0 1

调用 transform 方法就相当于用这些参数为上下文对象设置了新的变形矩阵,关于变形矩阵的具体内容可以参照图形学相关资料,下面给出几个简单特例:

移动 translate(dx,dy):相当于 transform(1,0,0,1,dx,dy)

缩放 scale(sx,xy):相当于 transform(sx,0,0,sy,0,0)

旋转 rotate(A):相当于 transform(cosA,sinA,-sinA,cosA,0,0)

以 (dx,dy) 为基准点旋转角度 A:transform(cosA, sinA, -sinA, cosA, dx(1-cosA) + dysinA, dy(1-cosA) - dxsinA)

以 (dx,dy) 为基准点进行 (sx,sy)比例缩放:transform(sx, 0, 0, sy, dx(1-sx), dy(1-sy))

还有很多其他更复杂的变形,大家可以参考图形学相关资料。

下面给出一个基准点变形的例子,鼠标在图像上的某点保持按下状态,图像就会以该点为基准点进行缩放或者旋转,松开按钮后图像复原。

提示:即缩放又旋转这个例子,并没有使用变形矩阵,而是用了四步简单变形复合而成。效果如下:

你的浏览器不支持 <canvas>标签,请使用 Chrome 浏览器 或者 FireFox 浏览器
移动——在图像上按住鼠标并移动
基准点缩放——在图像某点处按住鼠标
基准点旋转——在图像某点处按住鼠标
基准点缩放同时旋转——在图像某点处按住鼠标

 

四、组合

所谓组合就是一个图形绘制在另一个图形之上,会出现什么效果。默认的情况下是上面的图形覆盖了下面的图形,称之为 source-over 。

上下文对象总共十二中组合类型,属性 globalCompositeOperation 被用来设置组合类型,如下:

globalCompositeOperation = type

type 是下面 12 种字符串值之一:

注意:上面所有例子中,蓝色方块是先绘制的,即“已有的 canvas 内容”,红色圆形是后面绘制,即“新图形”。













 

五、裁剪路径

在第一篇文章中我们就介绍了上下文对象的两大类绘制方法,即绘制线条的 stroke 系列方法和填充区域的 fill 系列方法,其实,上下文对象还有一类绘制方法叫做裁剪 clip

什么是裁剪呢?打个不恰当的比方吧,你用一块布把电视机屏幕遮住了,这时候电视机屏幕上的任何变化你都看不见了。

但是,如果你布上裁剪出一块区域,那么至少这块区域里的屏幕变化你能看见。

当然裁剪区域之外的屏幕也在不停地变化(即也在重新绘制),只是你看不见罢了。这就是所谓的裁剪,平常在处理图像时经常会遇到这种需求。

那么什么又是裁剪路径呢?上面说要在布上裁剪出一块区域,这块区域是怎么来的呢?

这块区域是在裁剪动作 clip 之前,由绘图路径设定的,他可以是方形、圆形、五星形和其他任何可以绘制的轮廓形状。

所以,裁剪路径其实就是绘图路径,只不过这个路径不是拿来绘图的,而是设定显示区域和遮挡区域的一个分界线。

如果你不明白什么是绘图路径,在前的文章 HTML5边玩边学(2):基础绘图 中有介绍。

下面的例子用了两种方法进行裁剪。第一种方法显示一来回移动的圆形裁剪区域,大体流程如下:

1、清空画布

2、改变圆心位置

3、在新的圆心位置处设置一个圆形的裁剪区域

4、在画布上绘制美女图像

由于我们不停地在新位置处设定裁剪区域,我们就能看见裁剪区域在移动,而裁剪区域之外的图像并没有显示出来

 

我们用 clip 方法设置裁剪区域,之后绘制的图形只能显示裁剪区域内的一部分,而裁剪区域之外总是显示画布的背景色。

假如并不想完全遮挡裁剪区域之外的图像,比如我们想让裁剪区域之内的图像完全显示出来,但是裁剪区域之外的图像以半透明的方式显示出来,该怎么做呢?

这就要用到我们上面说的的组合知识了。第二中方法显示半透明的遮挡,大体思路如下:

1、清空画布

2、将画布的所有区域用一种半透明的颜色填充,这里我用的是灰色,透明度0.9

3、改变圆心位置

4、在新的圆心位置处以 XOR 方式绘制圆,这样和圆形重叠的部分将被擦除掉
这时候我们得到的图形效果是一个半透明的画布,上面有一块完全透明的圆形区域

5、 在第 4 步的基础上,以 destination-over 方式绘制美女图像,这时候美女图像将会出现在第 4 步图形效果的下方,想象一下,正好是我们想要的效果吧?!

效果如下:

你的浏览器不支持 <canvas>标签,请使用 Chrome 浏览器 或者 FireFox 浏览器

 

所有程序的代码:

ExpandedBlockStart.gif 代码
< canvas id = " canvas1 "  width = " 250 "  height = " 300 "  
    onmousedown
= " trans.transform(event); "   
    onmouseup
= " trans.init(event); "  
    onmousemove
= " trans.translate(event); "  
    style
= " background-color:black " >
    你的浏览器不支持 
& lt;canvas & gt;标签,请使用 Chrome 浏览器 或者 FireFox 浏览器
</ canvas >< br />
< input type = " radio "  name = " r "  id = " r1 "   checked = " checked " > 移动——在图像上按住鼠标并移动 < br  />
< input type = " radio "  name = " r "  id = " r2 " > 基准点缩放——在图像某点处按住鼠标 < br  />
< input type = " radio "  name = " r "   id = " r3 " > 基准点旋转——在图像某点处按住鼠标 < br  />
< input type = " radio "  name = " r "   id = " r4 " > 基准点缩放同时旋转——在图像某点处按住鼠标 < br  />

< canvas id = " canvas3 "  width = " 250 "  height = " 300 "  style = " background-color:black " >
    你的浏览器不支持 
& lt;canvas & gt;标签,请使用 Chrome 浏览器 或者 FireFox 浏览器
</ canvas >< br />
< input type = " button "  onclick = " move(1); "  value = " 移动裁剪区域 " >
< input type = " button "  onclick = " move(2); "  value = " 移动蒙版 " >
< input type = " button "  onclick = " stop(); "  value = " 停止移动 " >< br  />

        
< div >
            
< table >
                
< tr >
                    
< td >< canvas id = " tut0 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab0 " ></ label ></ td >
                
< td >< canvas id = " tut1 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab1 " ></ label ></ td >
                
< td >< canvas id = " tut2 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab2 " ></ label ></ td >
                
< td >< canvas id = " tut3 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab3 " ></ label ></ td >
                
</ tr >
                
< tr >
                    
< td >< canvas id = " tut4 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab4 " ></ label ></ td >
                
< td >< canvas id = " tut5 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab5 " ></ label ></ td >
                
< td >< canvas id = " tut6 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab6 " ></ label ></ td >
                
< td >< canvas id = " tut7 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab7 " ></ label ></ td >
                
</ tr >
                
< tr >
                    
< td >< canvas id = " tut8 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab8 " ></ label ></ td >
                
< td >< canvas id = " tut9 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab9 " ></ label ></ td >
                
< td >< canvas id = " tut10 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab10 " ></ label ></ td >
                
< td >< canvas id = " tut11 "  width = " 125 "  height = " 125 " ></ canvas >< br />< label id = " lab11 " ></ label ></ td >
                
</ tr >
            
</ table >
        
</ div >


< script type = " text/javascript " >
    
// 美女图的 Base64 编码
    IMG_SRC = ' data:image/gif;base64,/9j/4QDfRXhpZgAASUkqAAgAAAAFABIBAwA...... ' ; // 省略四十字节

    
// ==========================================
    
// 基准点变形类
    
// ==========================================
    function Transform(){
        
// 获取画布对象
         this .ctx  =  document.getElementById( " canvas1 " ).getContext( " 2d " );
        
// 创建图像对象
         this .img = new  Image();
        
// 指定图像源
         this .img.src = IMG_SRC;
        
this .interval  =   null ;
        
// 鼠标按钮状态
         this .pressed = false ;
        
this .init();
    }
    
    
// 初始化图形
    Transform.prototype.init = function(){
        
// 鼠标按钮状态
         this .pressed = false ;
        
// 停止计时器
         if ( this .interval) clearInterval( this .interval);
        
// 变化值
         this .delta  =   0.06 ;
        
// 清空
         this .ctx.clearRect( 0 , 0 , 250 , 300 );
        
// 重绘
         this .paint();
    }
    
    
// 绘制图像
    Transform.prototype.paint = function(){
        var that
= this ;
        var img
= this .img
        
if (img.complete)
            that.ctx.drawImage(img,
0 , 0 );
        
else  
            var interval 
=  setInterval(function(){
                
if (img.complete){
                    that.ctx.drawImage(img,
0 , 0 );
                    clearInterval(interval);
                }
            },
300 );
    }
    
    
// 鼠标按钮按下后,开始变形
    Transform.prototype.transform  =  function(){
        
// 获取基准点
         this .dx = event .offsetX;
        
this .dy = event .offsetY;
        
// 获取基准点
         this .startx = event .offsetX;
        
this .starty = event .offsetY;
        
// 初始缩放比例
         this .sc = 1 ;
        
// 初旋转角度
         this .angle = 0 ;
        
        var that
= this ;
        
if (document.getElementById( " r1 " ). checked )
            
// 鼠标按钮状态
             this .pressed = true ;
        
else   if (document.getElementById( " r2 " ). checked )
            
this .interval  =  setInterval(function(){that.scale()}, 50 );
        
else   if ((document.getElementById( " r3 " ). checked ))
            
this .interval  =  setInterval(function(){that.rotate()}, 50 );
        
else  
            
this .interval  =  setInterval(function(){that.scaleAndRotate()}, 50 );
    }
    
    
// 移动
    Transform.prototype.translate  =  function(){
        
this .ddx = event .offsetX - this .startx;
        
this .ddy = event .offsetY - this .starty;
        
if ( this .pressed){
            
// 清空
             this .ctx.clearRect( 0 , 0 , 250 , 300 );
            
// 保存状态
             this .ctx.save();
            
// 平移
             this .ctx.translate( this .ddx, this .ddy);
            
// 重绘
             this .paint();
            
// 绘制基准点
             this .ctx.fillStyle = " red " ;
            
this .ctx.fillRect( this .dx - 5 , this .dy - 5 , 10 , 10 );
            
// 恢复状态
             this .ctx.restore();
        }
    }

    
// 缩放变形
    Transform.prototype.scale  =  function(){
        
// 清空
         this .ctx.clearRect( 0 , 0 , 250 , 300 );
        
// 改变缩放比例
         this .sc = this .sc  -   this .delta;
        
if ( this .sc < 0.2   ||   this .sc > 2
            
this .delta  =   - this .delta;
        
// 保存状态
         this .ctx.save();
        
// 以 (dx,dy) 为基准点进行 (sx,sy)比例缩放:transform(sx, 0, 0, sy, dx(1-sx), dy(1-sy))
         this .ctx.transform( this .sc,  0 0 this .sc,  this .dx * ( 1 - this .sc),  this .dy * ( 1 - this .sc))
        
// 用新的变形矩阵重绘
         this .paint();
        
// 绘制基准点
         this .ctx.fillStyle = " red " ;
        
this .ctx.fillRect( this .dx - 5 , this .dy - 5 , 10 , 10 );
        
// 恢复状态
         this .ctx.restore();
    }
    
    
// 旋转变形
    Transform.prototype.rotate  =  function(){
        
// 清空
         this .ctx.clearRect( 0 , 0 , 250 , 300 );
        
// 改变缩放比例
        var PI  =  Math.PI;
        
this .angle = this .angle  +  PI / 60 ;
        
// 保存状态
         this .ctx.save();
        
// 以 (dx,dy) 为基准点旋转角度 A:transform(cosA, sinA, -sinA, cosA, dx(1-cosA) + dysinA, dy(1-cosA) - dxsinA)
         this .ctx.transform(Math.cos( this .angle), Math.sin( this .angle), 
                
- Math.sin( this .angle), Math.cos( this .angle), 
                
this .dx * ( 1 - Math.cos( this .angle))  +   this .dy * Math.sin( this .angle), 
                
this .dy * ( 1 - Math.cos( this .angle))  -   this .dx * Math.sin( this .angle))
        
// 用新的变形矩阵重绘
         this .paint();
        
// 绘制基准点
         this .ctx.fillStyle = " red " ;
        
this .ctx.fillRect( this .dx - 5 , this .dy - 5 , 10 , 10 );
        
// 恢复状态
         this .ctx.restore();
    }
    
    
// 即缩放又旋转变形,没有使用变形矩阵
    Transform.prototype.scaleAndRotate  =  function(){
        
// 清空
         this .ctx.clearRect( 0 , 0 , 250 , 300 );
        
// 改变缩放比例
         this .sc = this .sc  -   this .delta;
        
if ( this .sc < 0.2   ||   this .sc > 2
            
this .delta  =   - this .delta;
        var PI 
=  Math.PI;
        
this .angle = this .angle  +  PI / 60 ;
        
// 保存状态
         this .ctx.save();
        
// 先移动原点到基点
         this .ctx.translate( this .dx, this .dy);
        
this .ctx.scale( this .sc, this .sc);
        
this .ctx.rotate( this .angle);
        
this .ctx.translate( - this .dx, - this .dy);
        
// 用新的变形矩阵重绘
         this .paint();
        
// 绘制基准点
         this .ctx.fillStyle = " red " ;
        
this .ctx.fillRect( this .dx - 5 , this .dy - 5 , 10 , 10 );
        
// 恢复状态
         this .ctx.restore();
    }
    
    var trans 
=   new  Transform();
    
    
// ==========================================
    function Clip(){
        var canvas 
=  document.getElementById( " canvas3 " );
        
this .ctx  =  canvas.getContext( " 2d " );
        
this .img = new  Image();
        
this .img.src = IMG_SRC;
        
// 移动方向
         this .delta = [ 3 , 3 ];
        
// 起始点
         this .pos_x  =   225 ;
        
this .pos_y  =   120 ;
        
// 半径
         this .radius  =   40 ;
        
// 画布的长和宽
         this .w  =  parseInt(canvas.getAttribute( " width " ));
        
this .h  =  parseInt(canvas.getAttribute( " height " ));
    }
    
    Clip.prototype.draw1
= function(){
        
// 碰撞检测
         if  ( this .pos_x  <   this .radius) {
            
this .delta[ 0 =  Math.random()  %   4   +   5 ;
        } 
else   if  ( this .pos_x  >   this .w  -   this .radius) {
            
this .delta[ 0 =   - (Math.random()  %   4   +   5 );
        }
        
if  ( this .pos_y  <   this .radius) {
            
this .delta[ 1 =  Math.random()  %   4   +   5 ;
        } 
else   if  ( this .pos_y  >   this .h  -   this .radius) {
            
this .delta[ 1 =   - (Math.random()  %   4   +   5 );
        }
        
this .pos_x  +=   this .delta[ 0 ];
        
this .pos_y  +=   this .delta[ 1 ];

        
this .ctx.clearRect( 0 0 this .w,  this .h);
        
// 保存状态
         this .ctx.save()
        
// 移动变形
         this .ctx.translate( this .pos_x, this .pos_y);
        
// 设置裁剪区域
         this .ctx.beginPath();
        
this .ctx.arc( 0  , 0 , this .radius, 0 ,Math.PI * 2 , true );
        
this .ctx.clip();         
        
//  将图片画到画布上
         this .ctx.drawImage( this .img,  - this .pos_x,  - this .pos_y, this .w,  this .h);
        
// 恢复状态
         this .ctx.restore();
    }
    
    Clip.prototype.draw2
= function(){
        
// 碰撞检测
         if  ( this .pos_x  <   this .radius) {
            
this .delta[ 0 =  Math.random()  %   4   +   5 ;
        } 
else   if  ( this .pos_x  >   this .w  -   this .radius) {
            
this .delta[ 0 =   - (Math.random()  %   4   +   5 );
        }
        
if  ( this .pos_y  <   this .radius) {
            
this .delta[ 1 =  Math.random()  %   4   +   5 ;
        } 
else   if  ( this .pos_y  >   this .h  -   this .radius) {
            
this .delta[ 1 =   - (Math.random()  %   4   +   5 );
        }
        
this .pos_x  +=   this .delta[ 0 ];
        
this .pos_y  +=   this .delta[ 1 ];

        
this .ctx.clearRect( 0 0 this .w,  this .h);
        
// 绘制灰色的半透明蒙版
         this .ctx.fillStyle = " rgba(125,125,125,0.9) "
        
this .ctx.fillRect( 0 0 this .w,  this .h);
        
// 保存状态
         this .ctx.save()
        
// 移动坐标
         this .ctx.translate( this .pos_x, this .pos_y);
        
// 裁剪透明的圆形区域
         this .ctx.globalCompositeOperation  =   " xor " ;   
        
this .ctx.fillStyle = " white "
        
this .ctx.beginPath();
        
this .ctx.arc( 0  , 0 , this .radius, 0 ,Math.PI * 2 , true );
        
this .ctx.fill();       
        
//  将图片画到蒙版的下面,即只露出透明区域
         this .ctx.globalCompositeOperation  =   " destination-over " ;   
        
this .ctx.drawImage( this .img,  - this .pos_x,  - this .pos_y, this .w,  this .h);
        
// 恢复状态
         this .ctx.restore();
    }

    var cl
= new  Clip();
    cl.interval
= null ;
    
    function move(id){      
        
if (cl.interval)
            clearInterval(cl.interval)
        
if (id == 1 ){
            cl.ctx.clearRect(
0 0 450 300 );    
            cl.interval
= setInterval(function(){cl.draw1()}, 20 );
        }
        
else {
            cl.ctx.clearRect(
0 0 450 300 );    
            cl.interval
= setInterval(function(){cl.draw2()}, 20 );
        }
    }

    function stop(){
        clearInterval(cl.interval)
    }
    
    var compositeTypes 
=  [
        
' source-over ' , ' source-in ' , ' source-out ' , ' source-atop ' ,
        
' destination-over ' , ' destination-in ' , ' destination-out ' , ' destination-atop ' ,
        
' lighter ' , ' darker ' , ' copy ' , ' xor '
    ];
    function drawComp(){
        
for  (i = 0 ;i < compositeTypes.length;i ++ ){
            var label 
=  document.createTextNode(compositeTypes[i]);
            document.getElementById(
' lab ' + i).appendChild(label);
            var ctx 
=  document.getElementById( ' tut ' + i).getContext( ' 2d ' );

            
//  draw rectangle
            ctx.fillStyle  =   " #09f " ;
            ctx.fillRect(
15 , 15 , 70 , 70 );

            
//  set composite property
            ctx.globalCompositeOperation  =  compositeTypes[i];

            
//  draw circle
            ctx.fillStyle  =   " #f30 " ;
            ctx.beginPath();
            ctx.arc(
75 , 75 , 35 , 0 ,Math.PI * 2 , true );
            ctx.fill();
        }
    }
    drawComp();
</ script >

 

 

 

//==========================================


本文转自左洸博客园博客,原文链接:http://www.cnblogs.com/myqiao/archive/2010/10/05/1844032.html,如需转载请自行联系原作者


目录
相关文章
|
1月前
|
数据采集 存储 JavaScript
PHP爬虫技术:利用simple_html_dom库分析汽车之家电动车参数
本文旨在介绍如何利用PHP中的simple_html_dom库结合爬虫代理IP技术来高效采集和分析汽车之家网站的电动车参数。通过实际示例和详细说明,读者将了解如何实现数据分析和爬虫技术的结合应用,从而更好地理解和应用相关技术。
PHP爬虫技术:利用simple_html_dom库分析汽车之家电动车参数
|
前端开发
web前端设计与开发期末作品_期末大作业【使用HTML制作汽车首页】
汽车网站、🚘汽车介绍、🚍汽车官网、汽车租赁、企业网页 、等网站的设计与制作。
183 0
|
移动开发 HTML5
HTML5汽车赛道飙车游戏免费源码下载
HTML5汽车赛道飙车游戏免费源码下载
404 0
HTML5汽车赛道飙车游戏免费源码下载
|
Web App开发 移动开发 前端开发
|
Web App开发 移动开发 前端开发
|
Web App开发 移动开发 前端开发
|
Web App开发 移动开发 JavaScript