zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    Shell 脚本读取文件中的每行
    Linux中的内存管理机制
    CPU Cache 学习(一)
    linux系统层次(转)
    linux下的一些常用命令
    几个关于Linux进程fork()的题目
    GDB调试工具
    POSIX thread library 简介I
    Scheme Programming language II
    Google Chrome浏览器标签页之间的自动切换
  • 原文地址:https://www.cnblogs.com/wanf/p/7238600.html
Copyright © 2011-2022 走看看