HTML5 Canvas笔记——编写可编辑和拖动的贝塞尔曲线

2020-05-19  本文已影响0人  没昔

HTML5 Canvas编写拖动和编辑图形的程序——贝塞尔曲线

2-28.js

var canvas = document.getElementById("canvas"),
    context = canvas.getContext("2d"),
    eraseAllButton = document.getElementById("eraseAllButton"),
    strokeStyleSelect = document.getElementById("strokeStyleSelect"),
    fillStyleSelect = document.getElementById("fillStyleSelect"),
    fillCheckbox = document.getElementById("fillCheckbox"),
    editCheckbox = document.getElementById('editCheckbox'),
    sidesSelect = document.getElementById('sidesSelect'),
 
    mousedown = {},
    rubberbandRect = {},
 
    dragging = false,
    draggingOffsetX,
    draggingOffsetY,
 
    sides = 8,
    startAngle = 0,
 
    guidewires = true,
    editing = false,
    polygons = [];

 
//Function..................................................................

function windowToCanvas(x, y) {
    var bbox = canvas.getBoundingClientRect();
    return { x: x - bbox.left * (canvas.width / bbox.width),
             y: y - bbox.top  * (canvas.height / bbox.height)
           };
}
 
//Save and restore drawing surface..........................................
 
function saveDrawingSurface() {
    drawingSurfaceImageData = context.getImageData(0, 0, canvas.width,
                                                   canvas.height);
}
 
function restoreDrawingSurface() {
    context.putImageData (drawingSurfaceImageData, 0, 0);
}
 
//Draw a polygon...........................................................
 
function drawPolygon(polygon) {
    context.beginPath();
    polygon.createPath(context);
    polygon.stroke(context);
 
    if (fillCheckbox.checked) {
        polygon.fill(context);
    }
}
 
//Rubber bands.............................................................
 
function updateRubberbandRectangle(loc) {
    rubberbandRect.width = Math.abs(loc.x - mousedown.x);
    rubberbandRect.height = Math.abs(loc.y - mousedown.y);
 
    if ( loc.x > mousedown.x) rubberbandRect.left = mousedown.x;
    else                      rubberbandRect.left = loc.x;
 
    if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y;
    else                     rubberbandRect.top = loc.y;
}
 
function drawRubberbandShape (loc, sides, startAngle) {
    var polygon = new Polygon(mousedown.x, mousedown.y,
                              rubberbandRect.width,
                              parseInt(sidesSelect.value),
                            (Math.PI/180)* parseInt(startAngleSelect.value),
                              context.strokeStyle,
                              context.fillStyle,
                              fillCheckbox.checked);
    drawPolygon(polygon);
 
    if(!dragging) {
        polygons.push(polygon);
    }
}
 
function updateRubberband(loc, sides, startAngle) {
    updateRubberbandRectangle(loc);
    drawRubberbandShape(loc, sides, startAngle);
}

//Guidewires...............................................................
 
function drawHorizontalLine (y) {
    context.beginPath();
    context.moveTo(0, y+0.5);
    context.lineTo(context.canvas.width, y+0.5);
    context.stroke();
}
 
function drawVerticalLine (x) {
    context.beginPath();
    context.moveTo(x+0.5, 0);
    context.lineTo(x+0.5, context.canvas.height);
    context.stroke();
}
 
function drawGuidewires(x, y) {
    context.save();
    context.strokeStyle = 'rgba(0,0,230,0.4)';
    context.lineWidth = 0.5;
    drawVerticalLine(x);
    drawHorizontalLine(y);
    context.restore();
}
 
function drawPolygons() {
    polygons.forEach(function (polygon) {
        drawPolygon(polygon);
    });
}
 
//Dragging..............................................................
 
function startDragging (loc) {
    saveDrawingSurface();
    mousedown.x = loc.x;
    mousedown.y = loc.y;
}
 
function startEditing() {
    canvas.style.cursor = "pointer";
    editing = true;
}
 
function stopEditing() {
    canvas.style.cursor = "crosshair";
    editing = false;
}
 
//Event handlers...........................................................
 
canvas.onmousedown = function (e) {
    var loc = windowToCanvas(e.clientX, e.clientY);
 
    e.preventDefault();
 
    if (editing) {
        polygons.forEach( function (polygon) {
            polygon.createPath(context);
            if (context.isPointInPath(loc.x, loc.y)){
                startDragging(loc);
                dragging = polygon;
                draggingOffsetX = loc.x - polygon.x;
                draggingOffsetY = loc.y - polygon.y;
                return;
            }
        });
    }
    else {
        startDragging(loc);
        dragging = true;
    }
};
 
canvas.onmousemove = function (e) {
    var loc = windowToCanvas(e.clientX, e.clientY);
 
    e.preventDefault();
 
    if (editing && dragging) {
        dragging.x = loc.x - draggingOffsetX;
        dragging.y = loc.y - draggingOffsetY;
        context.clearRect(0, 0, canvas.width,canvas.height);
        drawPolygons();
    }
    else {
        if(dragging) {
            restoreDrawingSurface();
            updateRubberband(loc, sides, startAngle);
 
            if (guidewires) {
                drawGuidewires(mousedown.x, mousedown.y);
            }
        }
    }
};
 
canvas.onmouseup = function (e) {
    var loc = windowToCanvas(e.clientX, e.clientY);
 
    dragging = false;
 
    if (editing) {
 
    }
    else {
        restoreDrawingSurface();
        updateRubberband(loc);
    }
};
 
eraseAllButton.onclick = function (e) {
    context.clearRect (0, 0, canvas.width, canvas.height);
    saveDrawingSurface();
};
 
strokeStyleSelect.onchange = function (e) {
    context.strokeStyle = strokeStyleSelect.value;
};
 
fillStyleSelect.onchange = function (e) {
    context.fillStyle = fillStyleSelect.value;
};
 
editCheckbox.onchange = function (e) {
    if (editCheckbox.checked){
        startEditing();
    }
    else {
        stopEditing();
    }
};

 
//Initialization............................................................

context.strokeStyle = strokeStyleSelect.value;
context.fillStyle = fillStyleSelect.value;
 
context.shadowColor = 'rgba(0,0,0,0.4)';
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.shadowBlur = 4;

2-29.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>可编辑的贝塞尔曲线</title>
    <style>
        body {
            background: #eeeeee;
        }

        .floatingControls {
            position: absolute;
            left: 150px;
            top: 100px;
            width: 300px;
            padding: 20px;
            border: thin solid rgba(0, 0, 0, 0.3);
            background: rgba(0, 0, 200, 0.1);
            color: blue;
            font: 14px Arial;
            -webkit-box-shadow: rgba(0, 0, 0, 0.2) 6px 6px 8px;
            -moz-box-shadow: rgba(0, 0, 0, 0.2) 6px 6px 8px;
            box-shadow: rgba(0, 0, 0, 0.2) 6px 6px 8px;
            display: none;
        }

        .floatingControls p {
            margin-top: 0px;
            margin-bottom: 20px;
        }


        #controls {
            position: absolute;
            left: 25px;
            top: 25px;
        }

        #canvas {
            background: #ffffff;
            cursor: pointer;
            margin-left: 10px;
            margin-top: 10px;
            -webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
            -moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
            box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
        }
    </style>
</head>
<body>
<canvas id="canvas" width="605" height="400">Canvas not supported</canvas>
<div id='controls'>
    Stroke color: <select id='strokeStyleSelect'>
    <option value='red'>red</option>
    <option value='green'>green</option>
    <option value='blue'>blue</option>
    <option value='orange'>orange</option>
    <option value='cornflowerblue'>cornflowerblue</option>
    <option value='goldenrod'>goldenrod</option>
    <option value='navy' selected>navy</option>
    <option value='purple'>purple</option>
</select>
    Guidewires: <input id='guidewireCheckbox' type='checkbox' checked/>
    <input id='eraseAllButton' type='button' value='Erase all'/>
</div>
<div id='instructions' class='floatingControls'>
    <p>拖动贝塞尔曲线的锚点和控制点以改变曲线的形状</p>

    <p>当你完成曲线形状的编辑后,点击曲线外的一点,以完成此图像</p>

    <input id='instructionsOkayButton' type='button' value='好的' autofocus/>
    <input id='instructionsNoMoreButton' type='button'
           value='不再提示'/>
           
</div>
<script src="2-30.js"></script>
</body>
</html>

2-30.js

var canvas = document.getElementById('canvas'),
    context = canvas.getContext('2d'),
    eraseAllButton = document.getElementById('eraseAllButton'),
    strokeStyleSelect = document.getElementById('strokeStyleSelect'),
    guidewireCheckbox = document.getElementById('guidewireCheckbox'),
    instructions = document.getElementById('instructions'),
    instructionsOkayButton = document.getElementById('instructionsOkayButton'),
    instructionsNoMoreButton = document.getElementById('instructionsNoMoreButton'),

    showInstructions = true,

    GRID_STROKE_STYLE = 'lightblue',
    GRID_SPACING = 10,

    CONTROL_POINT_RADIUS = 5,
    CONTROL_POINT_STROKE_STYLE = 'blue',
    CONTROL_POINT_FILL_STYLE = 'rgba(255, 255, 0, 0.5)',

    END_POINT_STROKE_STYLE = 'navy',
    END_POINT_FILL_STYLE = 'rgba(0, 255, 0, 0.5)',

    GUIDEWIRE_STROKE_STYLE = 'rgba(0,0,230,0.4)',

    drawingImageData,

    mousedown = {},
    rubberbandRect = {},

    dragging = false,
    draggingPoint = false,

    endPoints = [{}, {}],
    controlPoints = [{}, {}],
    editing = false,

    guidewires = guidewireCheckbox.checked;

//Function……

function drawGrid(color, stepX, stepY){
    context.save()

    context.strokeStyle = color;
    context.lineWidth = 0.5;
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);

    for (var i = stepX + 0.5; i < context.canvas.width; i += stepX) {
        context.beginPath();
        context.moveTo(i, 0);
        context.lineTo(i, context.canvas.height);
        context.stroke();
    }

    for (var i = stepY + 0.5; i < context.canvas.height; i += stepY) {
        context.beginPath();
        context.moveTo(0, i);
        context.lineTo(context.canvas.width, i);
        context.stroke();
    }

    context.restore();
};

function updateRubberbandRectangle(loc){
    rubberbandRect.width  = Math.abs(loc.x - mousedown.x);
    rubberbandRect.height = Math.abs(loc.y - mousedown.y);

    if (loc.x > mousedown.x) rubberbandRect.left = mousedown.x;
    else                     rubberbandRect.left = loc.x;

    if (loc.y > mousedown.y) rubberbandRect.top = mousedown.y;
    else                     rubberbandRect.top = loc.y;
};


function windowToCanvas(x, y){
    var bbox = canvas.getBoundingClientRect();

    return {
        x: x - bbox.left * (canvas.width / bbox.width),
        y: y - bbox.top * (canvas.height / bbox.height)
    };
};

//保存和恢复绘图表面

function saveDrawingSurface(){
    drawingImageData = context.getImageData(0, 0,
        canvas.width, canvas.height);
};

function restoreDrawingSurface(){
    context.putImageData(drawingImageData, 0, 0);
};

/**
 * 绘制贝塞尔曲线
 */
function drawBezierCurve (){
    context.beginPath();
    context.moveTo(endPoints[0].x, endPoints[0].y);
    context.bezierCurveTo(controlPoints[0].x, controlPoints[0].y,
        controlPoints[1].x, controlPoints[1].y,
        endPoints[1].x, endPoints[1].y);
    context.stroke();
};

function updateEndAndControlPoints(){
    endPoints[0].x = rubberbandRect.left;
    endPoints[0].y = rubberbandRect.top;

    endPoints[1].x = rubberbandRect.left + rubberbandRect.width;
    endPoints[1].y = rubberbandRect.top  + rubberbandRect.height

    controlPoints[0].x = rubberbandRect.left;
    controlPoints[0].y = rubberbandRect.top  + rubberbandRect.height

    controlPoints[1].x = rubberbandRect.left + rubberbandRect.width;
    controlPoints[1].y = rubberbandRect.top;
};

function drawRubberbandShape(loc){
    updateEndAndControlPoints();
    drawBezierCurve();
};

function updateRubberband(loc){
    updateRubberbandRectangle(loc);
    drawRubberbandShape(loc);
};

//辅助线

function drawHorizontalGuidewire(y){
    context.beginPath();
    context.moveTo(0, y + 0.5);
    context.lineTo(context.canvas.width, y + 0.5);
    context.stroke();
};

function drawVerticalGuidewire(x){
    context.beginPath();
    context.moveTo(x + 0.5, 0);
    context.lineTo(x + 0.5, context.canvas.height);
    context.stroke();
};

function drawGuidewires(x, y){
    context.save();
    context.strokeStyle = GUIDEWIRE_STROKE_STYLE;
    context.lineWidth = 0.5;
    drawVerticalGuidewire(x);
    drawHorizontalGuidewire(y);
    context.restore();
};

//绘制锚点和控制点

function drawControlPoint(index){
    context.beginPath();
    context.arc(controlPoints[index].x, controlPoints[index].y,
        CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);
    context.stroke();
    context.fill();
};

function drawControlPoints(){
    context.save();
    context.strokeStyle = CONTROL_POINT_STROKE_STYLE;
    context.fillStyle = CONTROL_POINT_FILL_STYLE;

    drawControlPoint(0);
    drawControlPoint(1);

    context.stroke();
    context.fill();
    context.restore();
};

function drawEndPoint(index){
    context.beginPath();
    context.arc(endPoints[index].x, endPoints[index].y,
        CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);
    context.stroke();
    context.fill();
};

function drawEndPoints(){
    context.save();
    context.strokeStyle = END_POINT_STROKE_STYLE;
    context.fillStyle = END_POINT_FILL_STYLE;

    drawEndPoint(0);
    drawEndPoint(1);

    context.stroke();
    context.fill();
    context.restore();
};

function drawControlAndEndPoints(){
    drawControlPoints();
    drawEndPoints();
};

function cursorInEndPoint(loc){
    var pt;

    endPoints.forEach(function (point) {
        context.beginPath();
        context.arc(point.x, point.y,
            CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);

        if (context.isPointInPath(loc.x, loc.y)) {
            pt = point;
        }
    });

    return pt;
};

function cursorInControlPoint(loc){
    var pt;

    controlPoints.forEach(function (point) {
        context.beginPath();
        context.arc(point.x, point.y,
            CONTROL_POINT_RADIUS, 0, Math.PI * 2, false);

        if (context.isPointInPath(loc.x, loc.y)) {
            pt = point;
        }
    });

    return pt;
};

function updateDraggingPoint(loc){
    draggingPoint.x = loc.x;
    draggingPoint.y = loc.y;
};

//事件


canvas.onmousedown = function(e){
    //获取当前位置
    var loc = windowToCanvas(e.clientX,e.clientY);
    e.preventDefault();

    if (!editing){

        saveDrawingSurface();
        mousedown.x =loc.x;
        mousedown.y = loc.y;
        updateRubberbandRectangle(loc);
        dragging=true;
    }
    else{
        draggingPoint = cursorInControlPoint(loc);
        if (!draggingPoint){

            draggingPoint=cursorInEndPoint(loc);
        }
    }
};
canvas.onmousemove=function(e){

    var loc = windowToCanvas(e.clientX,e.clientY);
    if (dragging||draggingPoint){
        e.preventDefault();
        restoreDrawingSurface();
        if (guidewires){
            drawGuidewires(loc.x,loc.y);
        }
    }
    if (dragging) {
        updateRubberband(loc);
        drawControlAndEndPoints();
    }
    else if (draggingPoint) {
        updateDraggingPoint(loc);
        drawControlAndEndPoints();
        drawBezierCurve();
    }
};

canvas.onmouseup=function(e){
    var loc = windowToCanvas(e.clientX,e.clientY);

    restoreDrawingSurface();

    if (!editing) {
        updateRubberband(loc);
        drawControlAndEndPoints();
        dragging = false;
        editing = true;
        if (showInstructions) {
            instructions.style.display = 'inline';
        }
    }
    else {
        if (draggingPoint) drawControlAndEndPoints();
        else editing = false;

        drawBezierCurve();
        draggingPoint = undefined;
    }
}

// 给控件添加监听

eraseAllButton.onclick =function() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    drawGrid(GRID_STROKE_STYLE, GRID_SPACING, GRID_SPACING);

    saveDrawingSurface();

    editing = false;
    dragging = false;
    draggingPoint = undefined;
};

strokeStyleSelect.onchange = function() {
    context.strokeStyle = strokeStyleSelect.value;
};

guidewireCheckbox.onchange = function() {
    guidewires = guidewireCheckbox.checked;
};

// 给介绍框添加监听

instructionsOkayButton.onclick = function() {
    instructions.style.display = 'none';
};

instructionsNoMoreButton.onclick =function() {
    instructions.style.display = 'none';
    showInstructions = false;
};

// 初始化
// 

context.strokeStyle = strokeStyleSelect.value;
drawGrid(GRID_STROKE_STYLE, GRID_SPACING, GRID_SPACING);

效果如图:


贝塞尔曲线
上一篇下一篇

猜你喜欢

热点阅读