HTML5画布扇贝形状

3
我真的需要帮助,想知道如何使用Canvas创建扇形。 我试着模仿云朵示例,但很难做到自己想要的效果。
我只是想知道矩形和圆形扇形的代码。
这是我想要的图片链接: Scallop Design 图案不一定要完全相同,但尽可能接近即可。
万分感谢!
3个回答

2

这是一个来自较早回答的代码段,但问题描述不够清晰且有许多额外的内容。以下是该回答的一部分代码,其中包含一些可能有用但与主题无关的额外代码。

函数“display”(在中间位置)完成了大部分工作,将弧形添加到对象框中。

请参见运行演示以获取说明。

const pointSize = 4;
const pointCol = "#4AF";
var arcDepth = -0.5; // depth of arc as a factor of line seg length
                       // Note to have arc go the other (positive) way you have
                       // to change the ctx.arc draw call by adding anticlockwise flag 
                       // see drawArc for more
                       
const arcCol = "#F92";
const arcWidth = 8;


// Find a circle that fits 3 points.
function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) {
    var vx,
    vy,
    c,
    c1,
    u;

    c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2
    c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3
    // This will not happen in this example
    if (c === c1) { // if slope is the same they must be on the same line
        return null; // points are in a line
    }
    // locate the center
    if (p1y === p2y) { // special case with p1 and p2 have same y
        vx = (p1x + p2x) / 2;
        vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2));
    } else
        if (p2y === p3y) { // special case with p2 and p3 have same y
            vx = (p2x + p3x) / 2;
            vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2));
        } else {
            vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1);
            vy = c * vx + u;
        }
    arc.x = vx;
    arc.y = vy;
    vx = p1x - vx;
    vy = p1y - vy;
    arc.rad = Math.sqrt(vx * vx + vy * vy);
    return arc;
}

var points = [];
var arcs = [];
function addArc(p1, p2, depth) {
    var arc = {
        p1 : p1,
        p2 : p2,
        depth : depth,
        rad : null, // radius
        a1 : null, // angle from
        a2 : null, // angle to
        x : null,
        y : null,
    }
    arcs.push(arc);
    return arc;
}
function calcArc(arc, depth) {
    var p = points[arc.p1]; // get points
    var pp = points[arc.p2];
    // change depth if needed
    depth = arc.depth = depth !== undefined ? depth : arc.depth;
    var vx = pp[0] - p[0]; // vector from p to pp
    var vy = pp[1] - p[1];
    var cx = (pp[0] + p[0]) / 2; // center point
    var cy = (pp[1] + p[1]) / 2; // center point
    var len = Math.sqrt(vx * vx + vy * vy); // get length
    cx -= vy * depth; // find 3 point at 90 deg to line and dist depth
    cy += vx * depth;

    // To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above.
    //var nx = vx / len;  // normalise vector
    //var ny = vy / len;
    //cx -= ny * depth; // find 3 point at 90 deg to line and dist depth
    //cy += nx * depth;


    fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits
    arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point
    arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point

}
function addPoint(x, y) {
    points.push([x, y]);
}
function drawPoint(x, y, size, col) {
    ctx.fillStyle = col;
    ctx.beginPath();
    ctx.arc(x, y, size, 0, Math.PI * 2);
    ctx.fill();
}
function drawArcStart(width,col){
    ctx.lineCap = "round";
    ctx.strokeStyle = col;
    ctx.lineJoin = "round";
    ctx.lineWidth = width;
    ctx.beginPath();
    
}
function drawArc(arc){
    ctx.arc(arc.x,arc.y,arc.rad,arc.a1,arc.a2);
}    
function drawArcDone(){
    ctx.closePath();
    ctx.stroke();
}
function findClosestPoint(x, y, dist) {
    var index = -1;
    for (var i = 0; i < points.length; i++) {
        var p = points[i];
        var vx = x - p[0];
        var vy = y - p[1];
        var d = Math.sqrt(vx * vx + vy * vy);
        if (d < dist) {
            dist = d;
            index = i;
        }
    }
    return index;
}

var dragging = false;
var drag = -1;
var dragX, dragY;
var recalcArcs = false;
var box;
//========================================================================
// New box code from here down

// creates the box when canvas is ready
var onResize = function(){
    box = {
        x : canvas.width * (1/8),
        y : canvas.height * (1/8),
        w : canvas.width * (6/8),
        h : canvas.height * (6/8),
        recalculate : true,
        arcCount : 20, // number of arcs to try and fit. Does not mean that it will happen
    }
}

function display() {
    ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
    ctx.globalAlpha = 1; // reset alpha
    ctx.clearRect(0, 0, w, h);

if(mouse.w !== 0){
    if(mouse.buttonRaw & 4){ // change arc depth
        if(mouse.w < 0){
            arcDepth *= 1/1.05;
        }else{
            arcDepth *= 1.05;
        }
        recalcArcs = true;
        
    }else{  // change arc count
        box.arcCount += Math.sign(mouse.w);
        box.arcCount = Math.max(4,box.arcCount);
        box.recalculate = true;
    }
    mouse.w = 0;
}
// drag out box;
if(mouse.buttonRaw & 1){
    if(!dragging){
        box.x = mouse.x;
        box.y = mouse.y;
        dragging = true;
    }
    box.w = mouse.x - box.x;
    box.h = mouse.y - box.y;
    box.recalculate = true;
    if(box.w <0){
        box.x = box.x + box.w;
        box.w = - box.w;
    }
    if(box.h <0){
        box.y = box.y + box.h;
        box.h = - box.h;
    }
}else{
    dragging = false;
}
// stop error
if(box.w === 0 || box.h === 0){
    box.recalculate = false;
}

// calculate box arcs
if(box.recalculate){
    // reset arrays
    points.length = 0;
    arcs.length = 0;
    // get perimeter length
    var perimLen = (box.w + box.h)* 2;
    // get estimated step size
    var step = perimLen / box.arcCount;
    // get inset size for width and hight
    var wInStep = (box.w - (Math.floor(box.w/step)-1)*step) / 2;
    var hInStep = (box.h - (Math.floor(box.h/step)-1)*step) / 2;
    // fix if box to narrow
    if(box.w < step){
        wInStep = 0;
        hInStep = 0;
        step = box.h / (Math.floor(box.h/step));
    }else if(box.h < step){
        wInStep = 0;
        hInStep = 0;
        step = box.w / (Math.floor(box.w/step));
        
    }
    
    // Add points clock wise
    var x = box.x + wInStep;
    while(x < box.x + box.w){ // across top
        addPoint(x,box.y);
        x += step;
    }
    var y = box.y + hInStep; 
    while(y < box.y + box.h){ // down right side
        addPoint(box.x + box.w,y);
        y += step;
    }
    x = box.x + box.w - wInStep;
    while(x > box.x){          // left along bottom
        addPoint(x,box.y + box.h);
        x -= step;
    }
    var y = box.y + box.h - hInStep;
    while(y > box.y){  // up along left side
        addPoint(box.x,y);
        y -= step;
    }
    // calculate arcs.
    for(var i =0; i <points.length; i++){
        calcArc(addArc(i,(i + 1) % points.length,arcDepth));
    }
    box.recalculate = false;
}
// recalculate arcs if needed
for(var i = 0; i < arcs.length; i ++){
    if(recalcArcs){
        calcArc(arcs[i],arcDepth);
    }
}
// draw arcs
drawArcStart(arcWidth,arcCol)
for(var i = 0; i < arcs.length; i ++){
    drawArc(arcs[i]);
}
drawArcDone();
recalcArcs = false;
}


//===========================================================================================
// END OF ANSWER

// Boiler plate code from here down. Does mouse,canvas,resize and what not
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ;
(function () {
    const RESIZE_DEBOUNCE_TIME = 100;
    var createCanvas,
    resizeCanvas,
    setGlobals,
    resizeCount = 0;
    createCanvas = function () {
        var c,
        cs;
        cs = (c = document.createElement("canvas")).style;
        cs.position = "absolute";
        cs.top = cs.left = "0px";
        cs.zIndex = 1000;
        document.body.appendChild(c);
        return c;
    }
    resizeCanvas = function () {
        if (canvas === undefined) {
            canvas = createCanvas();
        }
        canvas.width = innerWidth;
        canvas.height = innerHeight;
        ctx = canvas.getContext("2d");
        if (typeof setGlobals === "function") {
            setGlobals();
        }
        if (typeof onResize === "function") {
            if (firstRun) {
                onResize();
                firstRun = false;
            } else {
                resizeCount += 1;
                setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
            }
        }
    }
    function debounceResize() {
        resizeCount -= 1;
        if (resizeCount <= 0) {
            onResize();
        }
    }
    setGlobals = function () {
        cw = (w = canvas.width) / 2;
        ch = (h = canvas.height) / 2;
    }
    mouse = (function () {
        function preventDefault(e) {
            e.preventDefault();
        }
        var mouse = {
            x : 0,
            y : 0,
            w : 0,
            alt : false,
            shift : false,
            ctrl : false,
            buttonRaw : 0,
            over : false,
            bm : [1, 2, 4, 6, 5, 3],
            active : false,
            bounds : null,
            crashRecover : null,
            mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
        };
        var m = mouse;
        function mouseMove(e) {
            var t = e.type;
            m.bounds = m.element.getBoundingClientRect();
            m.x = e.pageX - m.bounds.left;
            m.y = e.pageY - m.bounds.top;
            m.alt = e.altKey;
            m.shift = e.shiftKey;
            m.ctrl = e.ctrlKey;
            if (t === "mousedown") {
                m.buttonRaw |= m.bm[e.which - 1];
            } else if (t === "mouseup") {
                m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") {
                m.buttonRaw = 0;
                m.over = false;
            } else if (t === "mouseover") {
                m.over = true;
            } else if (t === "mousewheel") {
                m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") {
                m.w = -e.detail;
            }
            e.preventDefault();
        }
        m.start = function (element) {
            if (m.element !== undefined) {
                m.removeMouse();
            }
            m.element = element === undefined ? document : element;
            m.mouseEvents.forEach(n => {
                m.element.addEventListener(n, mouseMove);
            });
            m.element.addEventListener("contextmenu", preventDefault, false);
            m.active = true;
        }
        m.remove = function () {
            if (m.element !== undefined) {
                m.mouseEvents.forEach(n => {
                    m.element.removeEventListener(n, mouseMove);
                });
                m.element.removeEventListener("contextmenu", preventDefault);
                m.element = m.callbacks = undefined;
                m.active = false;
            }
        }
        return mouse;
    })();


    function update(timer) { // Main update loop
        if (ctx === undefined) {
            return;
        }
        globalTime = timer;
        display(); // call demo code
        requestAnimationFrame(update);
    }
    setTimeout(function () {
        resizeCanvas();
        mouse.start(canvas, true);
        window.addEventListener("resize", resizeCanvas);
        requestAnimationFrame(update);
    }, 0);
})();
Left click drag to create a box<br>Mouse wheel to change arc count<br>Hold right button down and wheel to change arc depth.<br>


谢谢,我也在这里看到了这个帖子。 但是我不想自由绘制它。我只想显示它。所以基本上我想要一个固定扇贝形状的代码。 - Khyana
用触摸板是不可能实现“按住右键并滚动鼠标改变弧度深度”的,你能否把它改成“保持笔直,同时用左脚触摸鼻子”呢? - Kaiido

2
你可以使用虚线来绘制此形状,例如这样(有点棘手)。
JavaScript:
const canvas = document.querySelector("#canvas");
canvas.width = canvas.height = 300;
const ctx = canvas.getContext("2d");
const rect = [50, 50, 200, 200];
//draw dotted line dash.
ctx.lineCap = "round";
ctx.setLineDash([0, 40]);
ctx.lineDashOffset = 20;
ctx.lineWidth = 42;
ctx.strokeStyle = "purple";
ctx.strokeRect(...rect);
//remove disuse range.
ctx.globalCompositeOperation = "destination-out";
ctx.lineWidth = 38;
ctx.strokeRect(...rect);
ctx.fillRect(...rect);

Demo:
http://jsdo.it/defghi1977/iFR7


为什么不直接使用二次曲线? - Assafi Cohen-Arazi
在这种情况下,我的代码不需要二次曲线。但是使用二次曲线更通用,因为我的代码很难操作形状的描边宽度轮廓。 - defghi1977

1
使用 https://www.w3schools.com/tags/canvas_beziercurveto.asp "贝塞尔曲线方法" 制作复杂形状。
我建议去 desmos 并尝试调整贝塞尔曲线,以便了解其复杂性。希望这可以帮到你 :)
编辑:贝塞尔曲线的工作方式如下:
ctx.bezierCurveTo(控制点x,控制点y,第二个控制点x,第二个控制点y,完成x,完成y);

哇,这真的很复杂。 - Khyana
@DownCrow 抱歉,但这是唯一的方法。相信我,一旦掌握了技巧,它会非常容易。 :) - Assafi Cohen-Arazi
@DownCrow 为什么需要呢?贝塞尔曲线/二次曲线可以按照自定义的方式制作,因此您可以制作任何想要的形状。 - Assafi Cohen-Arazi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接