欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

canvas-demo

程序员文章站 2024-03-20 20:05:16
...

一、草稿思路(HTML + JS)

1. 创建画布背景

使用div标签,设置背景样式(height: 100vh;充满屏幕)
API(application programming interface)应用程序编程接口

2. 获取盒子id

// 按下鼠标

  • 鼠标点击事件(onmousedown)绑定背景div,点击触发事件:

创建一个黑点(创造div,用style设置div样式,其中开启绝对定位,使用clientX、clientY获取鼠标点击位置,添加到盒子的top、left上)

//移动鼠标
鼠标移动事件(onmousemove)绑定背景div,移动触发事件:

(同点击事件)

//形成类似画板命令

全局声明一个“开关按钮”,(painting=false)
当点击事件(onmousedown)发生,“开关按钮”变为true
在移动事件(onmousemove)中添加判断,当“开关按钮”为true时,开始移动事件

//松开鼠标
鼠标松开事件(onmouseup)绑定背景div,松开触发事件:

加入“开关按钮”,松开鼠标,“开关按钮”变为false,停止作画。

程序代码

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>画板</title>
    <style>
        body{
            margin: 0
        }
        #canvas{
            background: green;
            height: 100vh;
            position: relative;
        }
    </style>
    <script src="./main.js" type="text/javascript"></script>
</head>
<body>
    <div id="canvas"></div>
</body>
</html>
JS
window.onload = function(){
    var div = document.getElementById('canvas');
    var painting = false;
    //按下鼠标
    div.onmousedown = function(a){
        painting = true;
        var x = a.clientX;
        var y = a.clientY;
        
        var divA = document.createElement('div');
        divA.style = "width: 6px; height: 6px;" + "background: black; border-radius: 3px;" + 
        "position: absolute; left: " + (x-3) + "px;" + "top: " + (y-3) + "px;"
        div.appendChild(divA);
    };
    
    div.onmousemove = function(a){
        if (painting){
            var x = a.clientX;
            var y = a.clientY;
            
            var divA = document.createElement('div');
            divA.style = "width: 6px; height: 6px;" + "background: black; border-radius: 3px;" + 
            "position: absolute; left: " + (x-3) + "px;" + "top: " + (y-3) + "px;"
            div.appendChild(divA);
        }else{

        }
        
    };

    div.onmouseup = function(){
        painting = false;
    }
}

二、使用canvas标签制作画板,同时解决连续性问题

1. 获取 API 2D 上下文环境

var context = xxx.getcontext('2d');

2. 实现画板操作

//按下鼠标

在点击事件(onmousedown)中,获取到点击鼠标的位置信息,添加画圈函数

//移动鼠标

同理,在移动事件中添加画圈函数,获取鼠标移动到的位置;

为实现划线的连贯性,设置画线函数(连接前后两圈,实现连贯);
为此,在全局作用域声明一个哈希,var lastPoint = {x: undefined, y: undefined};

点击事件发生,onmousedown中的 lastPoint 初始化为鼠标点击位置;
在移动事件中,声明一个newPoint,接收鼠标移动到的位置;
接下来按顺序执行画圈函数、画线函数,其中画线函数的第一个坐标为lastPoint,第二个坐标为newPoint;

(重点+解题点)

当鼠标持续按下移动,为解决之后的每个点都与第一点连线的问题,需不停更新lastPoint

lastPoint = newPoint;

3. 将画圈、划线功能封装

//函数实现使用canvas的API

function drawCircle(x, y, radius){
        context.beginPath();
        context.fillStyle = 'black';
        context.arc(x, y, radius, 0, Math.PI * 2);
        context.fill();
 }

function drawLine(x1, y1, x2, y2) {

        context.beginPath();
        context.strokeStyle = 'black';
        context.moveTo(x1, y1); //起点
        context.lineWidth = 5;
        context.lineTo(x2, y2); //终点
    
        context.stroke();
        context.closePath();
 }

4. 程序代码

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="./style.css">
    <script src="./main.js"></script>
</head>
<body>
    <canvas id="xxx" width=300 height=300></canvas>
</body>
</html>
CSS
#xxx{
    background: green;
    display: block;
}
body{
    margin: 0;
}
JS
window.onload = function(){
    
    var yyy = document.getElementById('xxx');
    var context = yyy.getContext('2d');

    var painting = false;
    var lastPoint = {x: undefined, y: undefined};

    yyy.onmousedown = function(event){
       
        painting = true;
        var x = event.clientX;
        var y = event.clientY;
        lastPoint = {'x': x,'y': y};
        drawCircle(x, y, 1);

    }
  
    yyy.onmousemove = function(event){

        if(painting){
            painting = true;
            var x = event.clientX;
            var y = event.clientY;
            var newPoint = {'x': x,'y': y};
            drawCircle(x, y, 1);
            drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);

            //不停更新当前点,防止出现每个点都只和第一点连接
            lastPoint = newPoint;
        }

    }

    yyy.onmouseup = function(event){
        painting = false;
    }

    function drawCircle(x, y, radius){
        context.beginPath();
        context.fillStyle = 'black';
        context.arc(x, y, radius, 0, Math.PI * 2);
        context.fill();
    }

    function drawLine(x1, y1, x2, y2) {

        context.beginPath();
        context.strokeStyle = 'black';
        context.moveTo(x1, y1); //起点
        context.lineWidth = 5;
        context.lineTo(x2, y2); //终点
    
        context.stroke();
        context.closePath();

    }
    
}

三、 优化

1. 获取页面宽高

//获取页面宽高不能用CSS,会出现鼠标移位bug

var pageWidth = document.documentElement.clientWidth;
var pageHeight = document.documentElement.clientHeight;
yyy.width = pageWidth;(页面宽高添加到canvas盒子的宽高上)
yyy.height = pageHeight;

//由于用户移动页面后,画布宽高又发生变化,所以监听页面窗口大小
//监听页面窗口,获取实时宽高

window.onresize = function(){
        var pageWidth = document.documentElement.clientWidth;
        var pageHeight = document.documentElement.clientHeight;
        yyy.width = pageWidth;
        yyy.height = pageHeight;
 }

//可将重复用到的语句封装

2. 可去掉画圈函数

去掉后,依然可以画图,但点击不会有画图,移动会执行画线函数。

3. 添加橡皮擦功能

//增加橡皮擦按钮,设置浮动定位到画布上;
//用JS将橡皮擦也设为布尔转换函数;

var eraserEnabled = false;
eraser.onclick = function(){
     eraserEnabled = !eraserEnabled;       
}

//点击事件中,点击进入判断,若橡皮擦开启则为执行清除语句,否则执行原事件;
//此外,由于using存在if-else语句块中,只有两句都添加using=true,using=true才能继承到移动事件(onmousemove)中;

if(eraserEnabled){
            using = true;//点击鼠标才擦,为使移动事件的using=true,此句必须存在
            context.clearRect(x-5, y-5, 10, 10);
}else{
            using = true;
            lastPoint = {'x': x,'y': y};
            //drawCircle(x, y, 1);
}

//移动事件中,同样点击进入判断,若橡皮擦开启则为执行清除语句,否则执行原事件;
//但在进入开启橡皮擦的判断中后,要再进行判断,看是否鼠标被点击,点击则进行移动清除;
(此句是为了防止开启橡皮擦按钮后,移动鼠标直接进行了清除)

if(eraserEnabled){
            //开启橡皮擦,判断是否有在用橡皮擦
            if(using){
                context.clearRect(x-5, y-5, 10, 10);
            }
            
}else{
            if(using){
                //using = true;
                //console.log(using)
                
                var newPoint = {'x': x,'y': y};
                //drawCircle(x, y, 1);
                drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
    
                //不停更新当前点,防止出现每个点都只和第一点连接
                lastPoint = newPoint;
            }
}

4. 封装函数

window.onload = function(){
    
    var yyy = document.getElementById('xxx');
    var context = yyy.getContext('2d');

    autoSetCanvasSize(yyy);

    listenToMouse(yyy);

    /************************************/
    按钮部分
    var eraserEnabled = false;
    eraser.onclick = function(){

        eraserEnabled = !eraserEnabled;
           
    }

    /********************************/
    
    function autoSetCanvasSize(canvas){
        setCanvasSize();

        //监听页面窗口,获取实时宽高
        window.onresize = function(){
            setCanvasSize();
        }
    
        function setCanvasSize(){
            //获取页面宽高
            var pageWidth = document.documentElement.clientWidth;
            var pageHeight = document.documentElement.clientHeight;
            canvas.width = pageWidth;
            canvas.height = pageHeight;
        }
    }

    function listenToMouse(canvas){
        
        var using = false;
        var lastPoint = {x: undefined, y: undefined};
    
        canvas.onmousedown = function(event){
           
            
            var x = event.clientX;
            var y = event.clientY;
            
    
            if(eraserEnabled){
                using = true;//点击鼠标才擦,为使移动事件的using=true,此句必须存在
                context.clearRect(x-5, y-5, 10, 10);
            }else{
                using = true;
                lastPoint = {'x': x,'y': y};
                //drawCircle(x, y, 1);
            }
    
        }
      
        canvas.onmousemove = function(event){
            var x = event.clientX;
            var y = event.clientY;
    
            
    
            if(eraserEnabled){
                //开启橡皮擦,判断是否有在用橡皮擦
                if(using){
                    context.clearRect(x-5, y-5, 10, 10);
                }
                
            }else{
                if(using){
                    //using = true;
                    //console.log(using)
                    
                    var newPoint = {'x': x,'y': y};
                    //drawCircle(x, y, 1);
                    drawLine(lastPoint.x, lastPoint.y, newPoint.x, newPoint.y);
        
                    //不停更新当前点,防止出现每个点都只和第一点连接
                    lastPoint = newPoint;
                }
            }
    
        }
    
        canvas.onmouseup = function(event){
            using = false;
        }
    }

    function drawCircle(x, y, radius){
        context.beginPath();
        context.fillStyle = 'black';
        context.arc(x, y, radius, 0, Math.PI * 2);
        context.fill();
    }

    function drawLine(x1, y1, x2, y2) {

        context.beginPath();
        context.strokeStyle = 'black';
        context.moveTo(x1, y1); //起点
        context.lineWidth = 5;
        context.lineTo(x2, y2); //终点
    
        context.stroke();
        context.closePath();

    }
}

5. 优化函数

//添加橡皮画笔转换

思路:在一个盒子里设置两个按钮,通过改变class名实现按钮转换。

为橡皮和画笔按钮绑定按钮点击事件(onclick),由JS来改变盒子的类名,初始无 x 类名,’css中设置按钮初始状态为:只显示橡皮按钮,添加了类名 x 后的状态为:只显示画笔按钮。

<div id="actions" class="actions">
        <button id="eraser">橡皮擦</button>
        <button id="brush">画笔</button>
    </div>
.actions > #brush{
    display: none;
}

.actions.x > #brush{
    display: inline-block;
}

.actions.x > #eraser{
    display: none;
}

//同时把橡皮擦开关也放入各个点击按钮事件(以下程序替换按钮部分)

    var eraserEnabled = false;
    eraser.onclick = function(){
        eraserEnabled = true;
        actions.className = 'actions x';   
    }
    brush.onclick = function(){
        eraserEnabled = false;
        actions.className = 'actions';
    }

推荐阅读