细说正则表达式,HTML5重力感应小球冲撞动画实现教程

 

利用VS的查找功能和正则表达式统计代码行数:

今天我们来分享一款很酷的HTML5重力感应动画教程,这款动画可以让你甩动页面中的小球,小球的大小都不同,并且鼠标点击空白区域时又可以生成一定数量的小球。当我们甩动小球时,各个小球之间就会发生互相碰撞的效果,并且在运动过程中模拟了重力感应的物理效果。你可以在DEMO演示中来尝试一下。

本文目标

30分钟内让你明白正则表达式是什么,并对它有一些基本的了解,让你可以在自己的程序或网页里使用它。

  1. CTRL+SHIFT+F (Find in files)
  2. 勾选 正则表达式
  3. 然后输入搜索内容:^:b*[^:b#/]+.*$
  4. #开头和/开头或者空行都不计入代码量。如果需要只统计代码文件的代码量,可以选择查找文件的类型,比如什么*.xml,
    *.resx….可以不检查,只查*.cs,*.c,*.h…
  5. 搜索出来以后最后一行就是代码行数了。

图片 1

如何使用本教程

最重要的是——请给我30分钟,如果你没有使用正则表达式的经验,请不要试图在30内入门——除非你是超人
🙂

别被下面那些复杂的表达式吓倒,只要跟着我一步一步来,你会发现正则表达式其实并没有你想像中的那么困难。当然,如果你看完了这篇教程之后,发现自己明白了很多,却又几乎什么都记不得,那也是很正常的——我认为,没接触过正则表达式的人在看完这篇教程后,能把提到过的语法记住80%以上的可能性为零。这里只是让你明白基本的原理,以后你还需要多练习,多使用,才能熟练掌握正则表达式。

除了作为入门教程之外,本文还试图成为可以在日常工作中使用的正则表达式语法参考手册。就作者本人的经历来说,这个目标还是完成得不错的——你看,我自己也没能把所有的东西记下来,不是吗?

清除格式 文本格式约定:专业术语元字符/语法格式 正则表达式
正则表达式中的一部分(用于分析) 对其进行匹配的源字符串
对正则表达式或其中一部分的说明

隐藏边注
本文右边有一些注释,主要是用来提供一些相关信息,或者给没有程序员背景的读者解释一些基本概念,通常可以忽略。

图片 2

你也可以在这里查看在线演示

正则表达式到底是什么东西?

字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。字符串是0个或更多个字符的序列。文本也就是文字,字符串。说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。

在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。

很可能你使用过Windows/Dos下用于文件查找的通配符(wildcard),也就是*和?。如果你想查找某个目录下的所有的Word文档的话,你会搜索*.doc。在这里,*会被解释成任意的字符串。和通配符类似,正则表达式也是用来进行文本匹配的工具,只不过比起通配符,它能更精确地描述你的需求——当然,代价就是更复杂——比如你可以编写一个正则表达式,用来查找所有以0开头,后面跟着2-3个数字,然后是一个连字号“-”,最后是7或8位数字的字符串(像010-12345678或0376-7654321)。

 

接下来我们来分析一下这款超酷的HTML5重力动画实现的思路及源码,主要由HTML代码和Javascript代码组成。

入门

学习正则表达式的最好方法是从例子开始,理解例子之后再自己对例子进行修改,实验。下面给出了不少简单的例子,并对它们作了详细的说明。

假设你在一篇英文小说里查找hi,你可以使用正则表达式hi。

这几乎是最简单的正则表达式了,它可以精确匹配这样的字符串:由两个字符组成,前一个字符是h,后一个是i。通常,处理正则表达式的工具会提供一个忽略大小写的选项,如果选中了这个选项,它可以匹配hi,HI,Hi,hI这四种情况中的任意一种。

不幸的是,很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b。

\b是正则表达式规定的一个特殊代码(好吧,某些人叫它元字符,metacharacter),代表着单词的开头或结尾,也就是单词的分界处。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置

如果需要更精确的说法,\b匹配这样的位置:它的前一个字符和后一个字符不全是(一个是,一个不是或不存在)\w。

假如你要找的是hi后面不远处跟着一个Lucy,你应该用\bhi\b.*\bLucy\b。

这里,.是另一个元字符,匹配除了换行符以外的任意字符。*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。现在\bhi\b.*\bLucy\b的意思就很明显了:先是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词。

换行符就是’\n’,ASCII编码为10(十六进制0x0A)的字符。

如果同时使用其它元字符,我们就能构造出功能更强大的正则表达式。比如下面这个例子:

0\d\d-\d\d\d\d\d\d\d\d匹配这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。当然,这个例子只能匹配区号为3位的情形)。

这里的\d是个新的元字符,匹配一位数字(0,或1,或2,或……)。-不是元字符,只匹配它本身——连字符(或者减号,或者中横线,或者随你怎么称呼它)。

为了避免那么多烦人的重复,我们也可以这样写这个表达式:0\d{2}-\d{8}。
这里\d后面的{2}({8})的意思是前面\d必须连续重复匹配2次(8次)。

最后返回的结果:第一个就是要统计的行数

HTML代码:

<div id="canvas"></div>

还是很简单,HTML仅仅是列出了一个canvas容器,今后我们将在这里生成一些列canvas元素,这些小球就在canvas中运动。

另外由于该动画利用了box2d的js脚本库,所以在页面上你也需要引用它:

<script src="box2d.js"></script>

接下来是Javascript代码,在canvas上动态创建大小和样式不一的小球,并发生碰撞效果。

测试正则表达式

其它可用的测试工具:

  • RegexBuddy
  • Javascript正则表达式在线测试工具

如果你不觉得正则表达式很难读写的话,要么你是一个天才,要么,你不是地球人。正则表达式的语法很令人头疼,即使对经常使用它的人来说也是如此。由于难于读写,容易出错,所以找一种工具对正则表达式进行测试是很有必要的。

不同的环境下正则表达式的一些细节是不相同的,本教程介绍的是微软 .Net
Framework 4.0
下正则表达式的行为,所以,我向你推荐我编写的.Net下的工具正则表达式测试器。请参考该页面的说明来安装和运行该软件。

下面是Regex Tester运行时的截图:

图片 3

Javascript代码:

var canvas;    var delta = [ 0, 0 ];  var stage = [ window.screenX, window.screenY, window.innerWidth, window.innerHeight ];  getBrowserDimensions();    var themes = [ [ "#10222B", "#95AB63", "#BDD684", "#E2F0D6", "#F6FFE0" ],          [ "#362C2A", "#732420", "#BF734C", "#FAD9A0", "#736859" ],          [ "#0D1114", "#102C2E", "#695F4C", "#EBBC5E", "#FFFBB8" ],          [ "#2E2F38", "#FFD63E", "#FFB54B", "#E88638", "#8A221C" ],          [ "#121212", "#E6F2DA", "#C9F24B", "#4D7B85", "#23383D" ],          [ "#343F40", "#736751", "#F2D7B6", "#BFAC95", "#8C3F3F" ],          [ "#000000", "#2D2B2A", "#561812", "#B81111", "#FFFFFF" ],          [ "#333B3A", "#B4BD51", "#543B38", "#61594D", "#B8925A" ] ];  var theme;    var worldAABB, world, iterations = 1, timeStep = 1 / 15;    var walls = [];  var wall_thickness = 200;  var wallsSetted = false;    var bodies, elements, text;    var createMode = false;  var destroyMode = false;    var isMouseDown = false;  var mouseJoint;  var mouse = { x: 0, y: 0 };  var gravity = { x: 0, y: 1 };    var PI2 = Math.PI * 2;    var timeOfLastTouch = 0;    init();  play();    function init() {        canvas = document.getElementById( 'canvas' );        document.onmousedown = onDocumentMouseDown;      document.onmouseup = onDocumentMouseUp;      document.onmousemove = onDocumentMouseMove;      document.ondblclick = onDocumentDoubleClick;        document.addEventListener( 'touchstart', onDocumentTouchStart, false );      document.addEventListener( 'touchmove', onDocumentTouchMove, false );      document.addEventListener( 'touchend', onDocumentTouchEnd, false );        window.addEventListener( 'deviceorientation', onWindowDeviceOrientation, false );        // init box2d        worldAABB = new b2AABB();      worldAABB.minVertex.Set( -200, -200 );      worldAABB.maxVertex.Set( window.innerWidth + 200, window.innerHeight + 200 );        world = new b2World( worldAABB, new b2Vec2( 0, 0 ), true );        setWalls();      reset();  }      function play() {        setInterval( loop, 1000 / 40 );  }    function reset() {        var i;        if ( bodies ) {            for ( i = 0; i < bodies.length; i++ ) {                var body = bodies[ i ]              canvas.removeChild( body.GetUserData().element );              world.DestroyBody( body );              body = null;          }      }        // color theme      theme = themes[ Math.random() * themes.length >> 0 ];      document.body.style[ 'backgroundColor' ] = theme[ 0 ];        bodies = [];      elements = [];        createInstructions();        for( i = 0; i < 10; i++ ) {            createBall();        }    }    //    function onDocumentMouseDown() {        isMouseDown = true;      return false;  }    function onDocumentMouseUp() {        isMouseDown = false;      return false;  }    function onDocumentMouseMove( event ) {        mouse.x = event.clientX;      mouse.y = event.clientY;  }    function onDocumentDoubleClick() {        reset();  }    function onDocumentTouchStart( event ) {        if( event.touches.length == 1 ) {            event.preventDefault();            // Faking double click for touch devices            var now = new Date().getTime();            if ( now - timeOfLastTouch  < 250 ) {                reset();              return;          }            timeOfLastTouch = now;            mouse.x = event.touches[ 0 ].pageX;          mouse.y = event.touches[ 0 ].pageY;          isMouseDown = true;      }  }    function onDocumentTouchMove( event ) {        if ( event.touches.length == 1 ) {            event.preventDefault();            mouse.x = event.touches[ 0 ].pageX;          mouse.y = event.touches[ 0 ].pageY;        }    }    function onDocumentTouchEnd( event ) {        if ( event.touches.length == 0 ) {            event.preventDefault();          isMouseDown = false;        }    }    function onWindowDeviceOrientation( event ) {        if ( event.beta ) {            gravity.x = Math.sin( event.gamma * Math.PI / 180 );          gravity.y = Math.sin( ( Math.PI / 4 ) + event.beta * Math.PI / 180 );        }    }    //    function createInstructions() {        var size = 250;        var element = document.createElement( 'div' );      element.width = size;      element.height = size;          element.style.position = 'absolute';      element.style.left = -200 + 'px';      element.style.top = -200 + 'px';      element.style.cursor = "default";        canvas.appendChild(element);      elements.push( element );        var circle = document.createElement( 'canvas' );      circle.width = size;      circle.height = size;        var graphics = circle.getContext( '2d' );        graphics.fillStyle = theme[ 3 ];      graphics.beginPath();      graphics.arc( size * .5, size * .5, size * .5, 0, PI2, true );      graphics.closePath();      graphics.fill();        element.appendChild( circle );        text = document.createElement( 'div' );      text.onSelectStart = null;      text.innerHTML = 'Hello!<br /><br /><strong>This is how it works:</strong><br /><br />1. Drag a ball.<br />2.&nbsp;Click&nbsp;on&nbsp;the&nbsp;background.<br />3. Shake your browser.<br />4. Double click.<br />5. Play!';      text.style.color = theme[1];      text.style.position = 'absolute';      text.style.left = '0px';      text.style.top = '0px';      text.style.fontFamily = 'Georgia';      text.style.textAlign = 'center';      element.appendChild(text);        text.style.left = ((250 - text.clientWidth) / 2) +'px';      text.style.top = ((250 - text.clientHeight) / 2) +'px';            var b2body = new b2BodyDef();        var circle = new b2CircleDef();      circle.radius = size / 2;      circle.density = 1;      circle.friction = 0.3;      circle.restitution = 0.3;      b2body.AddShape(circle);      b2body.userData = {element: element};        b2body.position.Set( Math.random() * stage[2], Math.random() * -200 );      b2body.linearVelocity.Set( Math.random() * 400 - 200, Math.random() * 400 - 200 );      bodies.push( world.CreateBody(b2body) );      }    function createBall( x, y ) {        var x = x || Math.random() * stage[2];      var y = y || Math.random() * -200;        var size = (Math.random() * 100 >> 0) + 20;        var element = document.createElement("canvas");      element.width = size;      element.height = size;      element.style.position = 'absolute';      element.style.left = -200 + 'px';      element.style.top = -200 + 'px';      element.style.WebkitTransform = 'translateZ(0)';      element.style.MozTransform = 'translateZ(0)';      element.style.OTransform = 'translateZ(0)';      element.style.msTransform = 'translateZ(0)';      element.style.transform = 'translateZ(0)';        var graphics = element.getContext("2d");        var num_circles = Math.random() * 10 >> 0;        for (var i = size; i > 0; i-= (size/num_circles)) {            graphics.fillStyle = theme[ (Math.random() * 4 >> 0) + 1];          graphics.beginPath();          graphics.arc(size * .5, size * .5, i * .5, 0, PI2, true);           graphics.closePath();          graphics.fill();      }        canvas.appendChild(element);        elements.push( element );        var b2body = new b2BodyDef();        var circle = new b2CircleDef();      circle.radius = size >> 1;      circle.density = 1;      circle.friction = 0.3;      circle.restitution = 0.3;      b2body.AddShape(circle);      b2body.userData = {element: element};        b2body.position.Set( x, y );      b2body.linearVelocity.Set( Math.random() * 400 - 200, Math.random() * 400 - 200 );      bodies.push( world.CreateBody(b2body) );  }    //    function loop() {        if (getBrowserDimensions()) {            setWalls();        }        delta[0] += (0 - delta[0]) * .5;      delta[1] += (0 - delta[1]) * .5;        world.m_gravity.x = gravity.x * 350 + delta[0];      world.m_gravity.y = gravity.y * 350 + delta[1];        mouseDrag();      world.Step(timeStep, iterations);        for (i = 0; i < bodies.length; i++) {            var body = bodies[i];          var element = elements[i];            element.style.left = (body.m_position0.x - (element.width >> 1)) + 'px';          element.style.top = (body.m_position0.y - (element.height >> 1)) + 'px';            if (element.tagName == 'DIV') {                var style = 'rotate(' + (body.m_rotation0 * 57.2957795) + 'deg) translateZ(0)';              text.style.WebkitTransform = style;              text.style.MozTransform = style;              text.style.OTransform = style;              text.style.msTransform = style;              text.style.transform = style;            }        }    }      // .. BOX2D UTILS    function createBox(world, x, y, width, height, fixed) {        if (typeof(fixed) == 'undefined') {            fixed = true;        }        var boxSd = new b2BoxDef();        if (!fixed) {            boxSd.density = 1.0;        }        boxSd.extents.Set(width, height);        var boxBd = new b2BodyDef();      boxBd.AddShape(boxSd);      boxBd.position.Set(x,y);        return world.CreateBody(boxBd);    }    function mouseDrag()  {      // mouse press      if (createMode) {            createBall( mouse.x, mouse.y );        } else if (isMouseDown && !mouseJoint) {            var body = getBodyAtMouse();            if (body) {                var md = new b2MouseJointDef();              md.body1 = world.m_groundBody;              md.body2 = body;              md.target.Set(mouse.x, mouse.y);              md.maxForce = 30000 * body.m_mass;              // md.timeStep = timeStep;              mouseJoint = world.CreateJoint(md);              body.WakeUp();            } else {                createMode = true;            }        }        // mouse release      if (!isMouseDown) {            createMode = false;          destroyMode = false;            if (mouseJoint) {                world.DestroyJoint(mouseJoint);              mouseJoint = null;            }        }        // mouse move      if (mouseJoint) {            var p2 = new b2Vec2(mouse.x, mouse.y);          mouseJoint.SetTarget(p2);      }  }    function getBodyAtMouse() {        // Make a small box.      var mousePVec = new b2Vec2();      mousePVec.Set(mouse.x, mouse.y);        var aabb = new b2AABB();      aabb.minVertex.Set(mouse.x - 1, mouse.y - 1);      aabb.maxVertex.Set(mouse.x + 1, mouse.y + 1);        // Query the world for overlapping shapes.      var k_maxCount = 10;      var shapes = new Array();      var count = world.Query(aabb, shapes, k_maxCount);      var body = null;        for (var i = 0; i < count; ++i) {            if (shapes[i].m_body.IsStatic() == false) {                if ( shapes[i].TestPoint(mousePVec) ) {                    body = shapes[i].m_body;                  break;                }            }        }        return body;    }    function setWalls() {        if (wallsSetted) {            world.DestroyBody(walls[0]);          world.DestroyBody(walls[1]);          world.DestroyBody(walls[2]);          world.DestroyBody(walls[3]);            walls[0] = null;           walls[1] = null;          walls[2] = null;          walls[3] = null;      }        walls[0] = createBox(world, stage[2] / 2, - wall_thickness, stage[2], wall_thickness);      walls[1] = createBox(world, stage[2] / 2, stage[3] + wall_thickness, stage[2], wall_thickness);      walls[2] = createBox(world, - wall_thickness, stage[3] / 2, wall_thickness, stage[3]);      walls[3] = createBox(world, stage[2] + wall_thickness, stage[3] / 2, wall_thickness, stage[3]);            wallsSetted = true;    }    // BROWSER DIMENSIONS    function getBrowserDimensions() {        var changed = false;        if (stage[0] != window.screenX) {            delta[0] = (window.screenX - stage[0]) * 50;          stage[0] = window.screenX;          changed = true;        }        if (stage[1] != window.screenY) {            delta[1] = (window.screenY - stage[1]) * 50;          stage[1] = window.screenY;          changed = true;        }        if (stage[2] != window.innerWidth) {            stage[2] = window.innerWidth;          changed = true;        }        if (stage[3] != window.innerHeight) {            stage[3] = window.innerHeight;          changed = true;        }        return changed;    }

上面mouseDrag方法就实现了鼠标拖拽甩动小球的功能,这也是该动画最重要的方法。全部代码可以下载源码来研究。源码下载>>


元字符

现在你已经知道几个很有用的元字符了,如\b,.,*,还有\d.正则表达式里还有更多的元字符,比如\s匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等。\w匹配字母或数字或下划线或汉字等。

对中文/汉字的特殊处理是由.Net提供的正则表达式引擎支持的,其它环境下的具体情况请查看相关文档。

下面来看看更多的例子:

\ba\w*\b匹配以字母a开头的单词——先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)。

好吧,现在我们说说正则表达式里的单词是什么意思吧:就是不少于一个的连续的\w。不错,这与学习英文时要背的成千上万个同名的东西的确关系不大
🙂

\d+匹配1个或更多连续的数字。这里的+是和*类似的元字符,不同的是*匹配重复任意次(可能是0次),而+则匹配重复1次或更多次。

\b\w{6}\b 匹配刚好6个字符的单词。

表1.常用的元字符
代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

正则表达式引擎通常会提供一个“测试指定的字符串是否匹配一个正则表达式”的方法,如JavaScript里的RegExp.test()方法或.NET里的Regex.IsMatch()方法。这里的匹配是指是字符串里有没有符合表达式规则的部分。如果不使用^和$的话,对于\d{5,12}而言,使用这样的方法就只能保证字符串里包含5到12连续位数字,而不是整个字符串就是5到12位数字。

元字符^(和数字6在同一个键位上的符号)和$都匹配一个位置,这和\b有点类似。^匹配你要用来查找的字符串的开头,$匹配结尾。这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的QQ号必须为5位到12位数字时,可以使用:^\d{5,12}$。

这里的{5,12}和前面介绍过的{2}是类似的,只不过{2}匹配只能不多不少重复2次,{5,12}则是重复的次数不能少于5次,不能多于12次,否则都不匹配。

因为使用了^和$,所以输入的整个字符串都要用来和\d{5,12}来匹配,也就是说整个输入必须是5到12个数字,因此如果输入的QQ号能匹配这个正则表达式的话,那就符合要求了。

和忽略大小写的选项类似,有些正则表达式处理工具还有一个处理多行的选项。如果选中了这个选项,^和$的意义就变成了匹配行的开始处和结束处。


相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注