HTML5游戏开发(八)

HTML5游戏开发(八)

一、剪辑区域

  剪辑区域它是在canvas之中由路径所定义的一块区域,浏览器会将所有的绘图操作都限制在本区域内执行。在默认情况下,剪辑区域的大小与canvas一致。除非你通过创建路径并调用cavas绘图环境对象的clip()方法来显示地设定剪辑区域,否则默认的剪辑区域不会影响canvas之中所绘制的内容。然而,一旦设置好剪辑区域,那么你在canvas之中绘制的所有内容都将局限在该区域内,这意味着在剪辑区域以外进行绘制是没有任何效果的。

1.图像擦除

(1)基本功能
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>图像剪辑</title>
        <style>
            #canvas{
                background-color: #99CC99;
                border: solid sandybrown;   
            }           
        </style>
    </head>
    <body>
        <canvas id="canvas" width="400" height="300"></canvas>
        <script type="text/javascript" src="js/clip.js" ></script>
    </body>
</html>

JS脚本

var canvas = document.querySelector("#canvas"),
    context = canvas.getContext('2d'),
    lastX, //擦除起始坐标X
    lastY, //擦除起始坐标Y
    drawingSurfaceImageData,
    dragging = false,
    ERASER_SHADOW_COLOR = 'rgb(0,0,0)',
    ERASER_SHADOW_STYLE = 'blue',
    ERASER_STROKE_STYLE = 'rgb(0,0,255)',
    ERASER_SHADOW_OFFSET = -5,
    ERASER_SHADOW_BLUR = 20,
    //设定直径
    eraserWidth = 20;
    ERASER_LINE_WIDTH = 1, //擦除线粗细
    mousedown = {};
//-------------------·1、基本功能    
//调用初始化方法   
draw();

function draw() {
    drawBackground();
    drawRect();
}
//绘制矩形
function drawRect() {
    context.fillStyle = "sandybrown";
    context.fillRect((canvas.width - 100) / 2, (canvas.height - 100) / 2, 100, 100);
}
//绘制背景
function drawBackground() {
    context.fillStyle = "#99CC99";
    context.fillRect(0, 0, canvas.width, canvas.height);
}

//坐标转换
function windowToCanvas(x, y) {
    var bbox = canvas.getBoundingClientRect();
    return {
        x: x - bbox.left * (canvas.width / bbox.width),
        y: y - bbox.top * (canvas.height / bbox.height)
    };
}
(2)
//-----------------------------2、绘制擦除
//绘制插除路径
function setDrawPathForEraser(loc) {
    //路径绘制
    context.beginPath();
    context.arc(loc.x, loc.y,
        eraserWidth / 2,
        0, Math.PI * 2, false);
    //剪辑
    context.clip();
}
//设置剪辑区域
function setErasePathForEraser() {
    //路径绘制
    context.beginPath();
    //绘制圆形
    context.arc(lastX, lastY,
        eraserWidth / 2 + ERASER_LINE_WIDTH, 0, Math.PI * 2, false);
    //进行剪辑,实际剪辑的是绘制的圆形
    context.clip();
}
//擦除结果
function eraseLast() {
    //保存原有画布,可注释此代码屯restore查看效果
    //总是在上一次剪辑的区域内进行操作
    //因为调用clip方法:将会把剪辑区域设置为当前剪辑区域与当前路径的交集。
    context.save();
    //设置剪辑区域
    setErasePathForEraser();
    //非常重要的代码:重绘背景
    drawBackground();
    //恢复原画布
    context.restore();
}
//设置擦除属性
function setEraserAttributes() {
    //路径粗细
    context.lineWidth = ERASER_LINE_WIDTH;
    //阴影
    context.shadowColor = ERASER_SHADOW_STYLE;
    context.shadowOffsetX = ERASER_SHADOW_OFFSET;
    context.shadowOffsetY = ERASER_SHADOW_OFFSET;
    context.shadowBlur = ERASER_SHADOW_BLUR;
    //路径样式
    context.strokeStyle = ERASER_STROKE_STYLE;
}
//给制擦除
function drawEraser(loc) {
    context.save();
    //设置擦除属性
    setEraserAttributes();
    //设置擦除路径
    setDrawPathForEraser(loc);
    //绘制跃升径
    context.stroke();

    context.restore();
}
(3)事件处理
//-----------------------------3、事件处理
canvas.onmousedown = function(e) {
    var loc = windowToCanvas(e.clientX, e.clientY);
    //禁止默认事件
    e.preventDefault();

    mousedown.x = loc.x;
    mousedown.y = loc.y;

    lastX = loc.x;
    lastY = loc.y;
    //设置拖拽状态
    dragging = true;
};
//鼠标移动
canvas.onmousemove = function(e) {
    var loc;
    //如果拖拽可用
    if(dragging) {
        //阻止默认行为
        e.preventDefault();
        //获取画布坐示
        loc = windowToCanvas(e.clientX, e.clientY);
        //擦除 
        eraseLast();
        //
        drawEraser(loc);
        //获取最后坐标
        lastX = loc.x;
        lastY = loc.y;
    }
};

canvas.onmouseup = function(e) {
    loc = windowToCanvas(e.clientX, e.clientY);
    eraseLast();
    dragging = false;
};

显终效果:
image

2.伸缩动画

(1)基本功能
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>伸缩动画</title>
        <style>
            #canvas{
                background-color: #99CC99;
                border: solid sandybrown;   
            }           
        </style>
    </head>
    <body>
        <canvas id="canvas" width="400" height="300"></canvas>
        <script type="text/javascript" src="js/Telescoping.js" ></script>
    </body>
</html>

JS脚本

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

context.lineWidth = 0.5;
context.font = '50pt Comic-sans';
drawText();
//----------------------1、基本功能
//文本绘制
function drawText() {
    context.save();
    //阴影
    context.shadowColor = 'rgba(100, 100, 150, 0.8)';
    context.shadowOffsetX = 5;
    context.shadowOffsetY = 5;
    context.shadowBlur = 10;

    context.fillStyle = 'cornflowerblue';
    context.fillText('跟我学动画', 20, 160);
    context.strokeStyle = 'yellow';
    context.strokeText('跟我学动画', 20, 160);
    context.restore();
}
(2)设置剪辑
//-------------------------2、设置剪辑
//设置剪辑区域
function setClippingRegion(radius) {
   context.beginPath();
   context.arc(canvas.width/2, canvas.height/2,
               radius, 0, Math.PI*2, false);
   context.clip();
}
//背景添充
function fillCanvas() {
    var r=Math.floor(Math.random()*255);
    var g=Math.floor(Math.random()*255);
    var b=Math.floor(Math.random()*255);
   context.fillStyle ="rgba("+r+","+g+","+b+",0.3)";
   context.fillRect(0, 0, canvas.width, canvas.height);
}
(3)动画处理
//-----------------------3、动画处理
//动画调用
function endAnimation(loop) {
    //停止动画
   clearInterval(loop);
    //设置时间间隔为1秒
   setTimeout( function (e) {
      //清空画布
      context.clearRect(0, 0, canvas.width, canvas.height);
      //绘制文本
      drawText();
   }, 1000);
}
function drawAnimationFrame(radius) {
   //设定剪辑区域
   setClippingRegion(radius);
   //绘制背景
   fillCanvas();
   //绘制文字
   drawText();
}

function animate() {
   var radius = canvas.width/2,
       loop;
    //动画播放,并返回loop值,作为停止的参数
   loop = window.setInterval(function() {
      radius -= canvas.width/100;
      //背景绘制
      fillCanvas();
      //当半径大于0时
      if (radius > 0) {
         context.save();
         //绘制动画
         drawAnimationFrame(radius);
         context.restore();
      }
      else {
         //停止动画
         endAnimation(loop);
      }
   }, 16);
};
(4)事件处理
//-------------------4、事件处理
canvas.onmousedown = function (e) {
   animate();
};

最终效果:
image

三、文本

属性描述
font设置或返回文本内容的当前字体属性
textAlign设置或返回文本内容的当前对齐方式
textBaseline设置或返回在绘制文本时使用的当前文本基线
方法描述
fillText()在画布上绘制“被填充的”文本
strokeText()在画布上绘制文本(无填充)
measureText()==返回==包含指定文本宽度的对象

1.绘制坐标轴文字

(1)基本功能
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            body {
                background: #eeeeee;
            }
            #canvas {
                background: #ffffff;
                cursor: pointer;
                margin-left: 10px;
                margin-top: 10px;
                box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
            }
        </style>
    </head>
    <body>
        <canvas id='canvas' width='650' height='450'>
         Canvas not supported
     </canvas>
        <script src='js/axis.js'></script>
    </body>
</html>

JS脚本

var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),

    HORIZONTAL_AXIS_MARGIN = 50,    //水平轴空白
    VERTICAL_AXIS_MARGIN = 50,      //垂直轴空白
    //原点
    AXIS_ORIGIN = { x: HORIZONTAL_AXIS_MARGIN,
                    y: canvas.height-VERTICAL_AXIS_MARGIN },

    AXIS_TOP   = VERTICAL_AXIS_MARGIN,      //顶部
    AXIS_RIGHT = canvas.width-HORIZONTAL_AXIS_MARGIN, //右边
    //刻度
    HORIZONTAL_TICK_SPACING = 10,   
    VERTICAL_TICK_SPACING = 10,
    //宽度
    AXIS_WIDTH  = AXIS_RIGHT - AXIS_ORIGIN.x,
    AXIS_HEIGHT = AXIS_ORIGIN.y - AXIS_TOP,
    //数字刻度
    NUM_VERTICAL_TICKS   = AXIS_HEIGHT / VERTICAL_TICK_SPACING,
    NUM_HORIZONTAL_TICKS = AXIS_WIDTH  / HORIZONTAL_TICK_SPACING,
    //刻度宽度
    TICK_WIDTH = 10,
    //标签间空白
    SPACE_BETWEEN_LABELS_AND_AXIS =  20;

//----------------------------1、基本功能
context.font = '13px Arial';
//设置阴影
context.shadowColor = 'rgba(100, 140, 230, 0.8)';
context.shadowOffsetX = 3;
context.shadowOffsetY = 3;
context.shadowBlur = 5;

//绘制坐标
function drawAxes() {
   context.save(); 
   context.lineWidth = 1.0;
   context.fillStyle = 'rgba(100, 140, 230, 0.8)';
   context.strokeStyle = 'navy';
   //绘制水平轴
   drawHorizontalAxis();
   //绘制垂直轴
   drawVerticalAxis();

   context.lineWidth = 0.5;
   context.strokeStyle = 'navy';

   context.strokeStyle = 'darkred';
   //绘制垂直刻度
   drawVerticalAxisTicks();
   //绘制水平刻度
   drawHorizontalAxisTicks();

   context.restore();
}

//绘制刻度
function drawVerticalAxisTicks() {
   var deltaY;

   for (var i=1; i < NUM_VERTICAL_TICKS; ++i) {
      context.beginPath();

      if (i % 5 === 0) deltaX = TICK_WIDTH;
      else             deltaX = TICK_WIDTH/2;

      context.moveTo(AXIS_ORIGIN.x - deltaX,
                     AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING);

      context.lineTo(AXIS_ORIGIN.x + deltaX,
                     AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING);

      context.stroke();
   }
}
//绘制水平刻度
function drawHorizontalAxisTicks() {
   var deltaY;

   for (var i=1; i < NUM_HORIZONTAL_TICKS; ++i) {
      context.beginPath();

      if (i % 5 === 0) deltaY = TICK_WIDTH;
      else             deltaY = TICK_WIDTH/2;

      context.moveTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING,
                     AXIS_ORIGIN.y - deltaY);

      context.lineTo(AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING,
                     AXIS_ORIGIN.y + deltaY);

      context.stroke();
   }
}
//绘制水平轴
function drawHorizontalAxis() {
   context.beginPath();
   context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y);
   context.lineTo(AXIS_RIGHT,    AXIS_ORIGIN.y)
   context.stroke();
}
//绘制垂直轴
function drawVerticalAxis() {
   context.beginPath();
   context.moveTo(AXIS_ORIGIN.x, AXIS_ORIGIN.y);
   context.lineTo(AXIS_ORIGIN.x, AXIS_TOP);
   context.stroke();
}
(2)标签绘制
//绘制标签
function drawAxisLabels() {
   context.fillStyle = 'blue';
   //绘制标记
   drawHorizontalAxisLabels();
   drawVerticalAxisLabels();
}
//绘制水平标签
function drawHorizontalAxisLabels() {
   context.textAlign = 'center';
   context.textBaseline = 'top';

   for (var i=0; i <= NUM_HORIZONTAL_TICKS; ++i) {
      if (i % 5 === 0) {
         //刻度绘制
         context.fillText(i,
            AXIS_ORIGIN.x + i * HORIZONTAL_TICK_SPACING,
            AXIS_ORIGIN.y + SPACE_BETWEEN_LABELS_AND_AXIS);
      }
   }
}
//绘制垂直标签
function drawVerticalAxisLabels() {
   context.textAlign = 'right';
   context.textBaseline = 'middle';

   for (var i=0; i <= NUM_VERTICAL_TICKS; ++i) {
      if (i % 5 === 0) {
        //绘制刻度
         context.fillText(i,
                     AXIS_ORIGIN.x - SPACE_BETWEEN_LABELS_AND_AXIS,
                     AXIS_ORIGIN.y - i * VERTICAL_TICK_SPACING);
      }
   }
}


//绘制轴
drawAxes();
//绘制刻度与标签
drawAxisLabels();

显示效果:
image

2.绘制弧形文本

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>弧形文本</title>
            <style>
      body {
         background: #eeeeee;
      }
      #canvas {
         background: #ffffff;
         margin-left: 10px;
         margin-top: 10px;
         box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
      }
    </style>
    </head>

    <body>
        <canvas id='canvas' width='650' height='450'>

      </canvas>
        <script type="text/javascript" src="js/arctext.js"></script>
    </body>

</html>

JS脚本:

var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),

    CENTROID_RADIUS = 10,                           //中心点半径
    CENTROID_STROKE_STYLE = 'rgba(0, 0, 0, 0.5)',   //中心点路径样式
    CENTROID_FILL_STYLE ='rgba(80, 190, 240, 0.6)', //中心点填充色

    TEXT_FILL_STYLE = 'rgba(100, 130, 240, 0.5)',   //文本样式式
    TEXT_STROKE_STYLE = 'rgba(200, 0, 0, 0.7)',     //文本路径样式
    TEXT_SIZE = 64,                                 //文本字体大小
    //圆形参数
    circle = { x: canvas.width/2,
               y: canvas.height/2,
               radius: 180
             };

//绘制弧形文本
function drawCircularText(string, startAngle, endAngle) {
   var radius = circle.radius,
       angleDecrement = (startAngle - endAngle)/(string.length-1),
       angle = parseFloat(startAngle),
       index = 0,
       character;

   context.save();
   context.fillStyle = TEXT_FILL_STYLE;
   context.strokeStyle = TEXT_STROKE_STYLE;
   //设置字体
   context.font = TEXT_SIZE + 'px Lucida Sans'; 
    //循环绘制
   while (index < string.length) {
      //获取字符
      character = string.charAt(index);
      //保存画布,目的是让其坐标重新开始
      context.save();
      context.beginPath();
      //移动
      context.translate(
         circle.x + Math.cos(angle) * radius,
         circle.y - Math.sin(angle) * radius);
      //旋转
      context.rotate(Math.PI/2 - angle);
      //绘制文本
      context.fillText(character, 0, 0);
      //绘制文本边线
      context.strokeText(character, 0, 0);
      //角度变化
      angle -= angleDecrement;
      index++;
      //恢复画布
      context.restore();
   }
   context.restore();
}

//绘制中心点
function drawCentroid() {
   context.beginPath();
   context.save();
   context.strokeStyle = CENTROID_STROKE_STYLE;
   context.fillStyle = CENTROID_FILL_STYLE;
   context.arc(circle.x, circle.y, CENTROID_RADIUS, 0, Math.PI*2, false);
   context.stroke();
   context.fill();
   context.restore();
}
//阴影
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.shadowBlur = 5;

//文本居中对齐
context.textAlign = 'center';
//基线居中
context.textBaseline = 'middle';

//中心点绘制
drawCentroid();
//绘制弧形文本
drawCircularText("这是一个弧形文本示例", Math.PI*2, Math.PI/8);

最终效果:
image

3.绘制文本编辑器

(1)创建文本光标类
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>简单文本编辑</title>
            <style>
      body {
         background: #eeeeee;
      }
      #canvas {
         background: #ffffff;
         margin-left: 10px;
         margin-top: 10px;
         box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
      }
    </style>
    </head>
    <body>
        <canvas id='canvas' width='650' height='450'>
      </canvas>
        <script type="text/javascript" src="js/textedit.js"></script>
    </body>

</html>

JS脚本

//-----------------------------1、文本光标类
//绘制光标文本
TextCursor = function (fillStyle, width) {
   this.fillStyle   = fillStyle || 'rgba(0, 0, 0, 0.7)';
   this.width       = width || 2;
   this.left        = 0;
   this.top         = 0;
};
//使用原型方法添加方法
TextCursor.prototype = {
    //获取高度
   getHeight: function (context) {
      var w = context.measureText('W').width;
      return w + w/6;
   },
    //创建路径 
   createPath: function (context) {
      context.beginPath();
      context.rect(this.left, this.top,
                   this.width, this.getHeight(context));
   },
   //绘制光标
   draw: function (context, left, bottom) {
      context.save();
      this.left = left;
      this.top = bottom - this.getHeight(context);
      this.createPath(context);
      context.fillStyle = this.fillStyle;
      //填充当前路径
      context.fill();    
      context.restore();
   },
   //擦除
   erase: function (context, imageData) {
      //恢复数据
      context.putImageData(imageData, 0, 0,
         this.left, this.top,
         this.width, this.getHeight(context));
   }
};
(2)文本行类
//----------------------2、文本行类
//文本行类
TextLine = function (x, y) {
   this.text = '';
   this.left = x;
   this.bottom = y;
   this.caret = 0;  //字符长度
};
//使用原型方式添加方法
TextLine.prototype = {
    //插入文本
   insert: function (text) {
      var first = this.text.slice(0, this.caret),
          last = this.text.slice(this.caret);

      first += text;
      this.text = first;
      this.text += last;
      this.caret += text.length;
   },
   //获取字符x坐标
   getCaretX: function (context) {
      var s = this.text.substring(0, this.caret),
          //获取文本宽度
          w = context.measureText(s).width;
      return this.left + w;
   },
   //移除前面字符
   removeCharacterBeforeCaret: function () {
      if (this.caret === 0)
         return;
      //截取文本
      this.text = this.text.substring(0, this.caret-1) +
                  this.text.substring(this.caret); 
      //改变字符长度
      this.caret--;
   },
   //移除最后字符
   removeLastCharacter: function () {
      this.text = this.text.slice(0, -1);
   },
    //获取字宽
   getWidth: function(context) {
      return context.measureText(this.text).width;
   },
   //获取字高
   getHeight: function (context) {
      var h = context.measureText('W').width;
      return h + h/6;
   },
   //绘制字符
   draw: function(context) {
      context.save();
      context.textAlign = 'start';
      context.textBaseline = 'bottom';

      context.strokeText(this.text, this.left, this.bottom);
      context.fillText(this.text, this.left, this.bottom);

      context.restore();
   },
   //擦除字符
   erase: function (context, imageData) {
      context.putImageData(imageData, 0, 0);
   }
};
(3)
//----------------------------3、段落类
//段落类
Paragraph = function (context, left, top, imageData, cursor) {
   this.context = context;
   this.drawingSurface = imageData; //画布
   this.left = left;
   this.top = top;
   this.lines = []; //行数,用于保存所有文本数据的集合
   this.activeLine = undefined; //激活行
   this.cursor = cursor;
   this.blinkingInterval = undefined; //行高
};
//使用原型方式添加方法
Paragraph.prototype = {
   //
   isPointInside: function (loc) {
      var c = this.context;

      c.beginPath();
      c.rect(this.left, this.top, 
             this.getWidth(), this.getHeight());

      return c.isPointInPath(loc.x, loc.y);
   },
   //获取高
   getHeight: function () {
      var h = 0;
      //读取所有行
      this.lines.forEach( function (line) {
         h += line.getHeight(this.context); 
      });
      return h;
   },
   //获取宽
   getWidth: function () {
      var w = 0,
          widest = 0;
      //读取每行字符宽度
      this.lines.forEach( function (line) {
         w = line.getWidth(this.context); 
         if (w > widest) {
            widest = w;
         }
      });

      return widest;
   },
   //绘制文本
   draw: function () {
      this.lines.forEach( function (line) {
         line.draw(this.context);
      });
   },
   //擦除文本
   erase: function (context, imageData) {
      context.putImageData(imageData, 0, 0);
   },
   //添加行
   addLine: function (line) {
      this.lines.push(line);
      this.activeLine = line;
      this.moveCursor(line.left, line.bottom);
   },
    //插入文本
   insert: function (text) {
     this.erase(this.context, this.drawingSurface);
     this.activeLine.insert(text);

     var t = this.activeLine.text.substring(0, this.activeLine.caret),
         w = this.context.measureText(t).width;
      //移除光标
     this.moveCursor(this.activeLine.left + w,
                     this.activeLine.bottom);
     //绘制文本
     this.draw(this.context);
   },
   //光标闪烁
   blinkCursor: function (x, y) {
      var self = this,
          BLINK_OUT = 200,  //间隔时长
          BLINK_INTERVAL = 900; //间隔周期
      //光标闪烁周期
      this.blinkingInterval = setInterval( function (e) {
         cursor.erase(context, self.drawingSurface);
         //设置绘制时间
         setTimeout( function (e) {
            cursor.draw(context, cursor.left,
                        cursor.top + cursor.getHeight(context));
         }, BLINK_OUT);
      }, BLINK_INTERVAL);
   },
   //移动并关闭光标
   moveCursorCloseTo: function (x, y) {
      var line = this.getLine(y);

      if (line) {
         line.caret = this.getColumn(line, x);
         this.activeLine = line;
         this.moveCursor(line.getCaretX(context),
                         line.bottom);
      }
   },
   //移动光标到指定位置
   moveCursor: function (x, y) {
      this.cursor.erase(this.context, this.drawingSurface);
      this.cursor.draw(this.context, x, y);

      if ( ! this.blinkingInterval)
         this.blinkCursor(x, y);
   },
   //移动线
   moveLinesDown: function (start) {
      for (var i=start; i < this.lines.length; ++i) {
         line = this.lines[i];
         line.bottom += line.getHeight(this.context);
      }
   },
   //产生新行
   newline: function () {
      var textBeforeCursor = this.activeLine.text.substring(0, this.activeLine.caret),
          textAfterCursor = this.activeLine.text.substring(this.activeLine.caret),
          height = this.context.measureText('W').width +
                   this.context.measureText('W').width/6,
          bottom  = this.activeLine.bottom + height,
          activeIndex,
          line;

      this.erase(this.context, this.drawingSurface);     // Erase paragraph
      this.activeLine.text = textBeforeCursor;           // Set active line's text

      line = new TextLine(this.activeLine.left, bottom); // Create a new line
      line.insert(textAfterCursor);                      // containing text after cursor

      activeIndex = this.lines.indexOf(this.activeLine); // Splice in new line
      this.lines.splice(activeIndex+1, 0, line);

      this.activeLine = line;                            // New line is active with
      this.activeLine.caret = 0;                         // caret at first character

      activeIndex = this.lines.indexOf(this.activeLine); // Starting at the new line...

      for(var i=activeIndex+1; i < this.lines.length; ++i) { //...loop over remaining lines
         line = this.lines[i];
         line.bottom += height; //移动到另一行
      }

      this.draw();
      this.cursor.draw(this.context, this.activeLine.left, this.activeLine.bottom);
   },
   //获取行
   getLine: function (y) {
      var line;

      for (i=0; i < this.lines.length; ++i) {
         line = this.lines[i];
         if (y > line.bottom - line.getHeight(context) &&
             y < line.bottom) {
            return line;
         }
      }
      return undefined;
   },
   //获取列
   getColumn: function (line, x) {
      var found = false,
          before,
          after,
          closest,
          tmpLine,
          column;

      tmpLine = new TextLine(line.left, line.bottom);
      tmpLine.insert(line.text);

      while ( ! found && tmpLine.text.length > 0) {
         before = tmpLine.left + tmpLine.getWidth(context);
         tmpLine.removeLastCharacter();
         after = tmpLine.left + tmpLine.getWidth(context);

         if (after < x) {
            closest = x - after < before - x ? after : before;
            column = closest === before ?
                     tmpLine.text.length + 1 : tmpLine.text.length;
            found = true;
         }
      }
      return column;
   },
   //激活行是否为最后行
   activeLineIsOutOfText: function () {
      return this.activeLine.text.length === 0;
   },
   //激活行是否为最后行
   activeLineIsTopLine: function () {
      return this.lines[0] === this.activeLine;
   },
   //移动到上一行
   moveUpOneLine: function () {
      var lastActiveText, line, before, after;

      lastActiveLine = this.activeLine;
      lastActiveText = '' + lastActiveLine.text;

      activeIndex = this.lines.indexOf(this.activeLine);
      this.activeLine = this.lines[activeIndex - 1];
      this.activeLine.caret = this.activeLine.text.length;
      //复制行内容
      this.lines.splice(activeIndex, 1);
      //移动光标      
      this.moveCursor(
         this.activeLine.left + this.activeLine.getWidth(this.context),
         this.activeLine.bottom);
      //激活行
      this.activeLine.text += lastActiveText;

      for (var i=activeIndex; i < this.lines.length; ++i) {
         line = this.lines[i];
         line.bottom -= line.getHeight(this.context);
      }
   },
   //退格键
   backspace: function () {
      var lastActiveLine,
          activeIndex,
          t, w;

      this.context.save();
      //如果激活行为0
      if (this.activeLine.caret === 0) {
         if ( ! this.activeLineIsTopLine()) {
            //擦除内容
            this.erase(this.context, this.drawingSurface);
            //移动到上一行
            this.moveUpOneLine();
            this.draw();
         }
      }
      else {  
        // 激活文本
         this.context.fillStyle = "red";
         this.context.strokeStyle = "red";;
        //擦除文本
         this.erase(this.context, this.drawingSurface);
         //移除光标前的字
         this.activeLine.removeCharacterBeforeCaret();
         //复制文本
         t = this.activeLine.text.slice(0, this.activeLine.caret),
         //获取文本宽度
         w = this.context.measureText(t).width;
        //移动光标
         this.moveCursor(this.activeLine.left + w,
                     this.activeLine.bottom);
        //绘制内容
         this.draw(this.context);

         context.restore();
      }
   }
};
(4)基本功能
var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),

    GRID_STROKE_STYLE = 'lightgray',
    GRID_HORIZONTAL_SPACING = 10,
    GRID_VERTICAL_SPACING = 10,

    drawingSurfaceImageData,
    //光标
    cursor = new TextCursor(),
    paragraph;

//--------------------------------4、基本功能
 //设置字符样式
cursor.fillStyle = "red";
cursor.strokeStyle = "red";
//路径粗细
context.lineWidth = 2.0;
setFont();
//保存数据
saveDrawingSurface();

//坐标转换
function windowToCanvas(canvas, x, y) {
   var bbox = canvas.getBoundingClientRect();

   return { x: x - bbox.left * (canvas.width  / bbox.width),
            y: y - bbox.top  * (canvas.height / bbox.height)
          };
}
(5)保存画布数据
//-------------------------5、保存画布
//保存画布数据
function saveDrawingSurface() {
   drawingSurfaceImageData = context.getImageData(0, 0,
                             canvas.width,
                             canvas.height);
}

//--------------------------设置文本
function setFont() {
   context.font ='48px 宋体';
}
(6)鼠标事件处理
//----------------------6、鼠标事件处理
//鼠标按下
canvas.onmousedown = function (e) {
   var loc = windowToCanvas(canvas, e.clientX, e.clientY),
       fontHeight,
       line;
   //光标擦除
   cursor.erase(context, drawingSurfaceImageData);
   saveDrawingSurface();

   if (paragraph && paragraph.isPointInside(loc)) {
      paragraph.moveCursorCloseTo(loc.x, loc.y);
   }
   else {
      fontHeight = context.measureText('W').width,
      fontHeight += fontHeight/6;

      paragraph = new Paragraph(context, loc.x, loc.y - fontHeight,
                               drawingSurfaceImageData,
                               cursor);

      paragraph.addLine(new TextLine(loc.x, loc.y));
   }
};
(7)键盘事件处理
//---------------------------7、键盘事件处理
//按键按下
document.onkeydown = function (e) {
   if (e.keyCode === 8 || e.keyCode === 13) {
      //阻止退格与回车
      e.preventDefault();
   }

   if (e.keyCode === 8) {  // 退格键
      //进行退格处理,删除前字符
      paragraph.backspace();
   }
   else if (e.keyCode === 13) { //回车键
      //输出换行
      paragraph.newline();
   }
}
//按键释放
document.onkeypress = function (e) {
    //将获取的值转为字符串
   var key = String.fromCharCode(e.which);

   //如果按下的不是空格,ctrl,alt键
   if (e.keyCode !== 8 && !e.ctrlKey && !e.metaKey) {
     //阻止默认行为
     e.preventDefault();
     //设置字符样式
     context.fillStyle = "red";
     context.strokeStyle = "red";
     //插入字符
     paragraph.insert(key);
   }
}

显示效果:
image

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值