網頁版小畫家筆記

利用 canvas 做出簡易版小畫家。

一、建立 HTML

<!DOCTYPE html>
<html>
<head>
    <title>Draw</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css">
    <link rel="stylesheet" href="css/draw.css">
</head>
<body>
    <nav class="navbar navbar-default navbar-fixed-top">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>

            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li>
                        <a href="#" class="clear"><i class="fas fa-brush"></i> 清除</a>
                    </li>
                    <li>
                        <a href="#" class="save"><i class="fas fa-save"></i> 儲存</a>
                    </li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">顏色 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#" class="color" data-color="#000000"><i class="fas fa-circle" style="color: #000000;"></i></a></li>
                            <li><a href="#" class="color" data-color="#7f7f7f"><i class="fas fa-circle" style="color: #7f7f7f;"></i></a></li>
                            <li><a href="#" class="color" data-color="#ed1c24"><i class="fas fa-circle" style="color: #ed1c24;"></i></a></li>
                            <li><a href="#" class="color" data-color="#ff7f27"><i class="fas fa-circle" style="color: #ff7f27;"></i></a></li>
                            <li><a href="#" class="color" data-color="#fff200"><i class="fas fa-circle" style="color: #fff200;"></i></a></li>
                            <li><a href="#" class="color" data-color="#22b14c"><i class="fas fa-circle" style="color: #22b14c;"></i></a></li>
                            <li><a href="#" class="color" data-color="#00a2e8"><i class="fas fa-circle" style="color: #00a2e8;"></i></a></li>
                            <li><a href="#" class="color" data-color="#3f48cc"><i class="fas fa-circle" style="color: #3f48cc;"></i></a></li>
                            <li><a href="#" class="color" data-color="#a349a4"><i class="fas fa-circle" style="color: #a349a4;"></i></a></li>
                            <li><a href="#" class="color" data-color="#ffffff"><i class="far fa-circle"></i></a></li>
                        </ul>
                    </li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown">大小 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#" class="size" data-size="small">小</a></li>
                            <li><a href="#" class="size active" data-size="normal">一般</a></li>
                            <li><a href="#" class="size" data-size="large">大</a></li>
                            <li><a href="#" class="size" data-size="huge">特大</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container">
        <div class="row margin-bottom">
            <div class="col-xs-6">
                <div id="current-pen">
                    目前畫筆樣式:
                    <div id="current-pen-style"></div>
                </div>
            </div>
            <div class="col-xs-6">
                目前儲存圖片:
                <div id="output-image"></div>
            </div>
        </div>
        <div id="canvas-div"></div>
    </div>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="js/draw.js"></script>
</body>
</html>
#7~8、#75~76 搭配使用 bootstrap、fontawesome 讓畫面好看一點
#77 主要 JavaScript 程式

二、建立 CSS 程式

body {
    font-family: Arial,Helvetica,"source-han-sans-traditional","微軟正黑體","Microsoft JhengHei","sans-serif";
    padding-top: 70px;
}

.margin-bottom {
    margin-bottom: 15px;
}

#current-pen {
    height: 50px;
}

#current-pen-style {
    background: #000000;
    border-radius: 50%;
    border: solid 2px #bbb;
    box-sizing: content-box;
    display: inline-block;
    height: 5px;
    width: 5px;
}

#output-image {
    height: 150px;
    position: relative;
}

#output-image img {
    bottom: 0;
    height : auto;
    left: 0;
    margin: auto;
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    right: 0;
    top: 0;
    width: auto;
}

#canvas-div {
    border: solid 1px #ccc;
    box-shadow: 0 6px 12px rgba(0,0,0,.175);
    /*height: calc(100vh - 260px);*/
    margin: 0 auto;
}

#canvas-div #canvas {
    cursor: crosshair;
}

三、建立 JavaScript 程式

$(function() {
    $('#canvas-div').css({
        'height': window.innerHeight - 267
    });

    var canvas;
    var context;
    var canvasWidth = $('#canvas-div').width();
    var canvasHeight = $('#canvas-div').height();
    var clickX = [];
    var clickY = [];
    var clickColor = [];
    var clickTool = [];
    var clickSize = [];
    var clickDrag = [];
    var paint = false;
    var curColor = '#000000';
    var curTool = "crayon";
    var curSize = "normal";
    var fileName = '';

    prepareCanvas();

    /**
    * Creates a canvas element, loads images, adds events, and draws the canvas for the first time.
    */
    function prepareCanvas()
    {
        var canvasDiv = document.getElementById('canvas-div');
        canvas = document.createElement('canvas');
        canvas.setAttribute('width', canvasWidth);
        canvas.setAttribute('height', canvasHeight);
        canvas.setAttribute('id', 'canvas');
        canvasDiv.appendChild(canvas);
        if(typeof G_vmlCanvasManager != 'undefined') {
            canvas = G_vmlCanvasManager.initElement(canvas);
        }
        context = canvas.getContext("2d");

        $('#canvas')
            .mousedown(function(e){
                paint = true;
                addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
                redraw();
            })
            .mousemove(function(e){
                if(paint){
                    addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
                    redraw();
                }
            })
            .mouseup(function(e){
                paint = false;
            })
            .mouseleave(function(e){
                paint = false;
            });

        $('#canvas')
            .on('touchstart', function (e) {
                paint = true;
                var touch = e.touches[0];
                addClick(touch.pageX - this.offsetLeft, touch.pageY - this.offsetTop);
                redraw();
            })
            .on('touchmove', function (e) {
                e.preventDefault();
                if (paint) {
                    var touch = e.touches[0];
                    addClick(touch.pageX - this.offsetLeft, touch.pageY - this.offsetTop, true);
                    redraw();
                }
            })
            .on('touchend', function (e) {
                paint = false;
            });

        $('nav a').click(function () {
            var obj = $(this);
            if (obj.hasClass('clear')) {
                clearCanvas();
                clickX = [];
                clickY = [];
                clickTool = [];
                clickColor = [];
                clickSize = [];
                clickDrag = [];
            } else if (obj.hasClass('save')) {
                var imageUrl = canvas.toDataURL();

                $('#output-image').html('');
                $('<img>').attr('src', imageUrl).addClass('img-responsive').appendTo('#output-image');

                // $.ajax({
                //     url: 'draw.php',
                //     method: "POST",
                //     data: {
                //         'image': imageUrl,
                //         'fileName': fileName
                //     }
                // }).done(function (result) {
                //     fileName = result;
                // });
            } else if (obj.hasClass('color')) {
                curColor = obj.data('color');

                $('#current-pen-style').css('background', obj.data('color'));
            } else if (obj.hasClass('size')) {
                curSize = obj.data('size');

                if(obj.data('size') == "small"){
                    radius = 2;
                }else if(obj.data('size') == "large"){
                    radius = 10;
                }else if(obj.data('size') == "huge"){
                    radius = 20;
                }else{
                    radius = 5;
                }
                $('#current-pen-style').css({
                    'height': radius + 'px',
                    'width': radius + 'px'
                });
            } else if (obj.hasClass('tool')) {
                curTool = obj.data('tool');
            }
        });

        $(window).on('beforeunload', function(){
            // 聽說這個自訂訊息因為怕人家詐騙所以沒用了
            return '你確定要離開嗎?';
        });
    }

    /**
     * Adds a point to the drawing array.
     * @param x
     * @param y
     * @param dragging
     */
    function addClick(x, y, dragging) {
        clickX.push(x);
        clickY.push(y);
        clickDrag.push(dragging);
        clickTool.push(curTool);
        if(curTool == "eraser"){
            clickColor.push("white");
        }else{
            clickColor.push(curColor);
        }
        clickSize.push(curSize);
    }

    /**
     * Clears the canvas.
     */
    function clearCanvas()
    {
        context.clearRect(0, 0, canvasWidth, canvasHeight);
    }

    /**
     * Redraws the canvas.
     */
    function redraw(){
        clearCanvas();

        context.lineJoin = "round";

        for(var i=0; i < clickX.length; i++) {
            if(clickSize[i] == "small"){
                radius = 2;
            }else if(clickSize[i] == "large"){
                radius = 10;
            }else if(clickSize[i] == "huge"){
                radius = 20;
            }else{
                radius = 5;
            }

            context.beginPath();
            if(clickDrag[i] && i){
                context.moveTo(clickX[i-1], clickY[i-1]);
            }else{
                context.moveTo(clickX[i]-1, clickY[i]);
            }
            context.lineTo(clickX[i], clickY[i]);
            context.closePath();
            context.strokeStyle = clickColor[i];
            context.lineWidth = radius;
            context.stroke();
        }
    }
});

四、結果參考

留言