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
  • 相关阅读:
    LeetCode 79. 单词搜索
    LeetCode 1143. 最长公共子序列
    LeetCode 55. 跳跃游戏
    LeetCode 48. 旋转图像
    LeetCode 93. 复原 IP 地址
    LeetCode 456. 132模式
    LeetCode 341. 扁平化嵌套列表迭代器
    LeetCode 73. 矩阵置零
    LeetCode 47. 全排列 II
    LeetCode 46. 全排列
  • 原文地址:https://www.cnblogs.com/wanf/p/7238600.html
Copyright © 2011-2022 走看看