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;
            400px;
            text-align:center;
            margin:20px auto;
        }
        ul li{
            float:left;
            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{
            90px;
            height:40px;
            line-height:40px;
            border:none;
            background:#ddd;
            margin-left:50px;
        }
        img{
            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
原文地址:https://www.cnblogs.com/wanf/p/7238600.html