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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章