canvas學寫一個字


第一步:畫一個米字格,先畫一個矩形,再畫中間的米字。

<script>
window.onload = function(){
  var canvas = document.getElementById('canvas');
  var context = canvas.getContext('2d');

  canvas.width = 600;
  canvas.height = canvas.width;
  var color ="black";
  //畫出田字格
  drawGrid();


  //田字格
  function drawGrid(){
    context.save();
    context.strokeStyle = "rgb(230,11,9)";
    context.beginPath();
    context.moveTo(3,3);
    context.lineTo(canvas.width - 3,3);
    context.lineTo(canvas.width - 3,canvas.height -3);
    context.lineTo(3,canvas.height -3);
    context.closePath();

    context.lineWidth = 6;
    context.stroke();

    context.beginPath();
    context.moveTo(0,0);
    context.lineTo(canvas.width,canvas.height);

    context.moveTo(canvas.width,0);
    context.lineTo(0,canvas.height);

    context.moveTo(canvas.width/2,0);
    context.lineTo(canvas.width/2,canvas.height);

    context.moveTo(0,canvas.width/2);
    context.lineTo(canvas.width,canvas.height/2);
    context.lineWidth=1;
    context.stroke();
    context.restore();

  }
}
</script>

效果圖

第二步.鼠標的四種狀態:onmousedown、onmouseup、onmouseout、onmousemove。

根據分析寫字的主要操作操作在onmousemove事件下進行的。鼠標onmouseup、onmouseout的時候應該停止寫字。鼠標onmousedown觸發寫字。

所以需先判斷鼠標是否按下,如果按下則onmousemove開始執行寫字操作。否則不執行。

var isMouseDown = false; //初始化鼠標是否按下

canvas.onmousedown=function(e){//鼠標按下
      e.preventDefault();
      isMouseDown = true;
      console.log("...onmousedown");
}
canvas.onmouseup=function(e){//鼠標起來
      e.preventDefault();
      isMouseDown = false;
      console.log("...onmouseup");
}
canvas.onmouseout=function(e){//鼠標離開
      e.preventDefault();
      isMouseDown = false;
      console.log("...onmouseout");
}
canvas.onmousemove=function(e){//鼠標移動
     e.preventDefault();
         if(isMouseDown){
             console.log("...onmousemove");
         }    
}

第三步:在canvas中寫字,相當於鼠標移動的時候不停地畫直線。那么問題來了,畫直線就需要獲得起始坐標。e.clientX,e.clientY只能獲得當前屏幕的坐標,而我們需要的是canvas里面的坐標。

接下來我們需要想辦法得到canvas的坐標了。在canvas中有一個方法getBoundingClientRect()可以獲得canvas距離屏幕的距離。

我們可以通過獲得光標屏幕坐標 - canvas距離屏幕的距離來得到光標在canvas中的坐標。

但是怎么確認哪個是起始位置,哪個是結束位置呢?所以一開始會初始化一個一開始的位置,lastLoc = {x:0,y:0};當鼠標落下,記錄光標位置賦值給lastLoc。鼠標移動的時候獲得當前坐標curLoc作為結束位置。

繪制結束后,將curLoc的值賦給lastLoc。所以每一次鼠標移動畫直線的起始坐標為上一次的結束坐標,結束坐標為當前鼠標坐標。

  var isMouseDown = false; //鼠標是否按下
  var lastLoc = {x:0,y:0};//初始化鼠標上一次所在位置

  canvas.onmousedown=function(e){
      e.preventDefault();
      isMouseDown = true;
      lastLoc = windowToCanvas(e.clientX,e.clientY);//上一次的坐標
  }

 canvas.onmousemove=function(e){
         e.preventDefault();
         if(isMouseDown){
                //draw
                var curLoc = windowToCanvas(e.clientX,e.clientY);//獲得當前坐標
                
                var lineWidth = 5;
                context.lineWidth=lineWidth;

                context.beginPath();
                context.moveTo(lastLoc.x,lastLoc.y);//起始位置為鼠標落下的位置
                context.lineTo(curLoc.x,curLoc.y);//結束位置為當前位置

                context.strokeStyle=color;
                context.stroke();
            
                lastLoc = curLoc;//將當前坐標賦值給上一次坐標
                lastLineWidth = lineWidth;
          }
            
     }


     //獲得canvas坐標
     function windowToCanvas(x,y){
            var bbox = canvas.getBoundingClientRect();
            return {x:Math.round(x-bbox.left),y:Math.round(y-bbox.top)};
     }

 

 現在寫字功能已經完成了,但是我們需要對他進行優化。

優化一:當把context.lineWidth改大一些的時候,我們會發現,寫字功能變得很不光滑了。

              

    這是什么原因呢,我們可以嘗試畫兩條寬度很大的直線看一下,兩條直線之間確實是有缺口存在的,並且跟線的寬度有關。所以學寫一個字會出現毛糙現象。

            

    解決方法:設定線段端點的形狀(線帽)

canvas.onmousemove=function(e){
            e.preventDefault();
            if(isMouseDown){
                //draw
                var curLoc = windowToCanvas(e.clientX,e.clientY);//獲得當前坐標
                
                var lineWidth = 30;
                context.lineWidth=lineWidth;

                context.beginPath();
                context.moveTo(lastLoc.x,lastLoc.y);
                context.lineTo(curLoc.x,curLoc.y);

                context.strokeStyle=color;
                context.lineCap = "round"
                context.lineJoin = "round"
                context.stroke();
            
                lastLoc = curLoc;
                lastTimestamp = curTimestamp;
                lastLineWidth = lineWidth;
            }
            
        }
View Code

             

優化二:可以選擇字的顏色,在頁面上做一個色盤。

        

優化三:我們的字lineWidth是固定的,不能夠向真正的毛筆字一樣有粗細之分。

解決方法:通過運筆速度設置lineWidth的大小,運筆速度=距離 / 時間。時間可以通過時間戳獲得。做法類似於lastLoc。

     距離=當前坐標 - 上一次坐標。根據兩點之間距離公式

       設置一個做大lineWidth和一個最小的lineWidth,

 

var isMouseDown = false; //鼠標是否按下
var lastLoc = {x:0,y:0};//鼠標上一次所在位置
var lastTimestamp = 0;//時間戳
var lastLineWidth=-1;//上一次線條寬度
canvas.onmousemove=function(e){
      e.preventDefault();
      if(isMouseDown){
           //draw
           var curLoc = windowToCanvas(e.clientX,e.clientY);//獲得當前坐標
           var curTimestamp = new Date().getTime();//當前時間
           var s = calcDistance(curLoc,lastLoc);//獲得運筆距離
           var t = curTimestamp-lastTimestamp;//運筆時間
           var lineWidth = calcLineWidth(t,s);

           var lineWidth = 30;
           context.lineWidth=lineWidth;

           context.beginPath();
           context.moveTo(lastLoc.x,lastLoc.y);
           context.lineTo(curLoc.x,curLoc.y);

           context.strokeStyle=color;
           context.lineCap = "round"
           context.lineJoin = "round"
           context.stroke();
            
           lastLoc = curLoc;
           lastLineWidth = lineWidth;
       }
            
  }


  //獲得canvas坐標
  function windowToCanvas(x,y){
       var bbox = canvas.getBoundingClientRect();
       return {x:Math.round(x-bbox.left),y:Math.round(y-bbox.top)};
  }
  //求兩點之間距離
  function calcDistance(loc1,loc2){
       return Math.sqrt((loc1.x - loc2.x)*(loc1.x - loc2.x)+(loc1.y - loc2.y)*(loc1.y - loc2.y));
  }
  //求速度
  function calcLineWidth(t,s){
       var v = s/t;
       var resultLineWidth;
       if(v<=0.1){
           resultLineWidth=30;
       }else if(v>=10){
           resultLineWidth=1;
       }else{
           resultLineWidth=30-(v-0.1)/(10-0.1)*(30-1);
       }
       if(lastLineWidth==-1){
            return resultLineWidth;
       }
       return lastLineWidth*2/3+resultLineWidth*1/3;
  }

優化三:將項目改為移動端,touchstart,touchmove,touchend。函數封裝,手機端跟pc端獲得屏幕位置的方法不一樣

//函數封裝--開始
function beginStroke(point){
  isMouseDown = true
  //console.log("mouse down!")
  lastLoc = windowToCanvas(point.x, point.y)
  lastTimestamp = new Date().getTime();
}
function endStroke(){
  isMouseDown = false
}
function moveStroke(point){
  var curLoc = windowToCanvas(point.x , point.y);//獲得當前坐標
  var curTimestamp = new Date().getTime();//當前時間
  var s = calcDistance(curLoc,lastLoc);//獲得運筆距離
  var t = curTimestamp-lastTimestamp;//運筆時間
  var lineWidth = calcLineWidth(t,s);
  context.lineWidth=lineWidth;

  context.beginPath();
  context.moveTo(lastLoc.x,lastLoc.y);
  context.lineTo(curLoc.x,curLoc.y);

  context.strokeStyle=color;
  context.lineCap = "round"
  context.lineJoin = "round"
  context.stroke();

  lastLoc = curLoc;
  lastTimestamp = curTimestamp;
  lastLineWidth = lineWidth;
}


//手機端事件
canvas.addEventListener('touchstart',function(e){
    e.preventDefault()
    touch = e.touches[0] //獲得坐標位置
    beginStroke( {x: touch.pageX , y: touch.pageY} )
});
canvas.addEventListener('touchmove',function(e){
    e.preventDefault()
    if( isMouseDown ){
         touch = e.touches[0]
         moveStroke({x: touch.pageX , y: touch.pageY})
    }
});
canvas.addEventListener('touchend',function(e){
     e.preventDefault()
     endStroke()
});

 源碼:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>學寫一個字</title>
    <meta   name="viewport"
            content="height=device-height,
            width = device-width,
            initial-scale = 1.0,
            minimum-scale = 1.0,
            maxmum - scale = 1.0,
            user - scalable =no"/>

            
    <style>
       
        ul{
            overflow:hidden;
            cursor:pointer;
            width:400px;
            text-align:center;
            margin:20px auto;
        }
        ul li{
            float:left;
            width:40px;
            height:40px;
            border-radius:50%;
            margin-right:10px;
            border:4px solid transparent;
            list-style:none;
        }
        ul li:hover{
            border:4px solid violet;
        }
        .red{
            background-color:red;
        }
        .black{
            background-color:black;
        }
        .green{
            background-color:green;
        }
        .yellow{
            background-color:yellow;
        }
        .blue{
            background-color:blue;
        }
        button{
            width:90px;
            height:40px;
            line-height:40px;
            border:none;
            background:#ddd;
            margin-left:50px;
        }
        img{
            width:100px;
            margin-top:20px;
            text-align:left;
        }
    </style>
</head>

<body style="text-align:center;">
    <canvas id="canvas" style="border:1px solid #ddd;"></canvas>
  
    <!---取色盤---->
    <ul>
        <li class="red" name="red"></li>
        <li class="black" name="black"></li>
        <li class="green" name="green"></li>
        <li class="yellow" name="yellow"></li>
        <li class="blue" name="blue"></li>
    </ul>
    <div style="text-align: center;"><button class="save" >保存</button><button class="clear">清除</button></div>
    <div class="img"></div>
   
</body>
<script src="js/jquery-2.1.4.min.js"></script>
<script>
    window.onload = function(){
        var canvas = document.getElementById('canvas');
        var context = canvas.getContext('2d');
        var isMouseDown = false; //鼠標是否按下
        var lastLoc = {x:0,y:0};//鼠標上一次所在位置
        var lastTimestamp = 0;//時間戳
        var lastLineWidth=-1;//上一次線條寬度


        canvas.width = Math.min( 600 , window.innerWidth - 20 );
        canvas.height = canvas.width;
        var color ="black";
        //畫出田字格
        drawGrid();

        //選擇顏色
        $('ul').on('click','li',function(){
            color = $(this).attr('name');
        });

        //清除田字格的內容
        $('body').on('click','button.clear',function(){
            context.clearRect( 0 , 0 , canvas.width, canvas.height );
            drawGrid();
        });

        //將canvas保存成圖片
        $('body').on('click','button.save',function(){
              var dataurl = canvas.toDataURL('image/png');
              
              var a = document.createElement('a');
              a.href = dataurl;
              a.download = "我的書法";
              a.click();
              
             $('.img').append('<img src="'+dataurl+'"/>');
        });

        //函數封裝--開始
        function beginStroke(point){
            isMouseDown = true
            //console.log("mouse down!")
            lastLoc = windowToCanvas(point.x, point.y)
            lastTimestamp = new Date().getTime();
        }
        function endStroke(){
            isMouseDown = false
        }
        function moveStroke(point){
            var curLoc = windowToCanvas(point.x , point.y);//獲得當前坐標
                var curTimestamp = new Date().getTime();//當前時間
                var s = calcDistance(curLoc,lastLoc);//獲得運筆距離
                var t = curTimestamp-lastTimestamp;//運筆時間
                var lineWidth = calcLineWidth(t,s);
                context.lineWidth=lineWidth;

                context.beginPath();
                context.moveTo(lastLoc.x,lastLoc.y);
                context.lineTo(curLoc.x,curLoc.y);

                context.strokeStyle=color;
                context.lineCap = "round"
                context.lineJoin = "round"
                context.stroke();
            
                lastLoc = curLoc;
                lastTimestamp = curTimestamp;
                lastLineWidth = lineWidth;
        }

        //手機端事件
        canvas.addEventListener('touchstart',function(e){
            e.preventDefault()
            touch = e.touches[0] //獲得坐標位置
            beginStroke( {x: touch.pageX , y: touch.pageY} )
        });
        canvas.addEventListener('touchmove',function(e){
            e.preventDefault()
            if( isMouseDown ){
                touch = e.touches[0]
                moveStroke({x: touch.pageX , y: touch.pageY})
            }
        });
        canvas.addEventListener('touchend',function(e){
            e.preventDefault()
            endStroke()
        });

        canvas.onmousedown=function(e){
            e.preventDefault();
            beginStroke( {x: e.clientX , y: e.clientY} )
        }
        canvas.onmouseup = function(e){
            e.preventDefault();
            endStroke();
        }
        canvas.onmouseout = function(e){
            e.preventDefault();
            endStroke();
        }
        canvas.onmousemove = function(e){
            e.preventDefault();
            if(isMouseDown){
                //draw
               var curLoc = windowToCanvas(e.clientX,e.clientY);//獲得當前坐標
               moveStroke({x: e.clientX , y: e.clientY})
            }
        }

        
        //獲得canvas坐標
        function windowToCanvas(x,y){
            var bbox = canvas.getBoundingClientRect();
            return {x:Math.round(x-bbox.left),y:Math.round(y-bbox.top)};
        }
        //求兩點之間距離
        function calcDistance(loc1,loc2){
            return Math.sqrt((loc1.x - loc2.x)*(loc1.x - loc2.x)+(loc1.y - loc2.y)*(loc1.y - loc2.y));
        }
        //求速度
        function calcLineWidth(t,s){
            var v = s/t;
            var resultLineWidth;
            if(v<=0.1){
                resultLineWidth=30;
            }else if(v>=10){
                resultLineWidth=1;
            }else{
                resultLineWidth=30-(v-0.1)/(10-0.1)*(30-1);
            }
            if(lastLineWidth==-1){
                return resultLineWidth;
            }
            return lastLineWidth*2/3+resultLineWidth*1/3;
        }
        //田字格
        function drawGrid(){
            context.save();
            context.strokeStyle = "rgb(230,11,9)";
            context.beginPath();
            context.moveTo(3,3);
            context.lineTo(canvas.width - 3,3);
            context.lineTo(canvas.width - 3,canvas.height -3);
            context.lineTo(3,canvas.height -3);
            context.closePath();

            context.lineWidth = 6;
            context.stroke();

            context.beginPath();
            context.moveTo(0,0);
            context.lineTo(canvas.width,canvas.height);

            context.moveTo(canvas.width,0);
            context.lineTo(0,canvas.height);

            context.moveTo(canvas.width/2,0);
            context.lineTo(canvas.width/2,canvas.height);

            context.moveTo(0,canvas.width/2);
            context.lineTo(canvas.width,canvas.height/2);
            context.lineWidth=1;
            context.stroke();
            context.restore();

        }
        
    }
        




    
</script>
</html>
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM