javascript 如何使用 HTML 5 Canvas 沿圆弧路径绘制文本?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8974364/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 05:11:18  来源:igfitidea点击:

How Can I draw a Text Along arc path with HTML 5 Canvas?

javascripthtmlcanvas

提问by EnzGLKba

I want to draw a canvas graphic like this flash animation: http://www.cci.com.tr/tr/bizi-taniyin/tarihcemiz/

我想画一个像这个flash动画一样的画布图形:http: //www.cci.com.tr/tr/bizi-taniyin/tarihcemiz/

I drew six arcs and I want to write six words in these arcs. Any ideas?

我画了六个弧线,我想在这些弧线上写六个字。有任何想法吗?

回答by Makallus

I have a jsFiddle to apply text to any arbitrary Bezier curve definition. Enjoy http://jsfiddle.net/Makallus/hyyvpp8g/

我有一个 jsFiddle 可以将文本应用于任何任意贝塞尔曲线定义。享受http://jsfiddle.net/Makallus/hyyvpp8g/

<table>
    <TR>
        <TH>Bezier Curve</TH>
        <TD>
            <input size="80" type="text" id="curve" name="curve" value="99.2,177.2,130.02,60.0,300.5,276.2,300.7,176.2">
        </TD>
    </TR>
    <TR>
        <TH>Text</TH>
        <TD>
            <input size="80" type="text" id="text" name="text" value="testing 1234567890">
        </TD>
    </TR>
    <TR>
        <TD colspan=2>
            <div id="canvasDiv"></div>
        </TD>
    </TR>
</table>

var first = true;
startIt();


function startIt() {
    canvasDiv = document.getElementById('canvasDiv');
    canvasDiv.innerHTML = '<canvas id="layer0" width="300" height="300"></canvas>'; //for IE
    canvas = document.getElementById('layer0');
    ctx = canvas.getContext('2d');
    ctx.fillStyle = "black";
    ctx.font = "18px arial black";
    curve = document.getElementById('curve');
    curveText = document.getElementById('text');
    $(curve).keyup(function (e) {
        changeCurve();
    });
    $(curveText).keyup(function (e) {
        changeCurve();
    });




    if (first) {
        changeCurve();
        first = false;
    }

}

function changeCurve() {
    points = curve.value.split(',');
    if (points.length == 8) drawStack();

}

function drawStack() {
    Ribbon = {
        maxChar: 50,
        startX: points[0],
        startY: points[1],
        control1X: points[2],
        control1Y: points[3],
        control2X: points[4],
        control2Y: points[5],
        endX: points[6],
        endY: points[7]
    };

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.beginPath();

    ctx.moveTo(Ribbon.startX, Ribbon.startY);
    ctx.bezierCurveTo(Ribbon.control1X, Ribbon.control1Y,
    Ribbon.control2X, Ribbon.control2Y,
    Ribbon.endX, Ribbon.endY);

    ctx.stroke();
    ctx.restore();

    FillRibbon(curveText.value, Ribbon);
}

function FillRibbon(text, Ribbon) {

    var textCurve = [];
    var ribbon = text.substring(0, Ribbon.maxChar);
    var curveSample = 1000;


    xDist = 0;
    var i = 0;
    for (i = 0; i < curveSample; i++) {
        a = new bezier2(i / curveSample, Ribbon.startX, Ribbon.startY, Ribbon.control1X, Ribbon.control1Y, Ribbon.control2X, Ribbon.control2Y, Ribbon.endX, Ribbon.endY);
        b = new bezier2((i + 1) / curveSample, Ribbon.startX, Ribbon.startY, Ribbon.control1X, Ribbon.control1Y, Ribbon.control2X, Ribbon.control2Y, Ribbon.endX, Ribbon.endY);
        c = new bezier(a, b);
        textCurve.push({
            bezier: a,
            curve: c.curve
        });
    }

    letterPadding = ctx.measureText(" ").width / 4;
    w = ribbon.length;
    ww = Math.round(ctx.measureText(ribbon).width);


    totalPadding = (w - 1) * letterPadding;
    totalLength = ww + totalPadding;
    p = 0;

    cDist = textCurve[curveSample - 1].curve.cDist;

    z = (cDist / 2) - (totalLength / 2);

    for (i = 0; i < curveSample; i++) {
        if (textCurve[i].curve.cDist >= z) {
            p = i;
            break;
        }
    }

    for (i = 0; i < w; i++) {
        ctx.save();
        ctx.translate(textCurve[p].bezier.point.x, textCurve[p].bezier.point.y);
        ctx.rotate(textCurve[p].curve.rad);
        ctx.fillText(ribbon[i], 0, 0);
        ctx.restore();

        x1 = ctx.measureText(ribbon[i]).width + letterPadding;
        x2 = 0;
        for (j = p; j < curveSample; j++) {
            x2 = x2 + textCurve[j].curve.dist;
            if (x2 >= x1) {
                p = j;
                break;
            }
        }




    }
} //end FillRibon

function bezier(b1, b2) {
    //Final stage which takes p, p+1 and calculates the rotation, distance on the path and accumulates the total distance
    this.rad = Math.atan(b1.point.mY / b1.point.mX);
    this.b2 = b2;
    this.b1 = b1;
    dx = (b2.x - b1.x);
    dx2 = (b2.x - b1.x) * (b2.x - b1.x);
    this.dist = Math.sqrt(((b2.x - b1.x) * (b2.x - b1.x)) + ((b2.y - b1.y) * (b2.y - b1.y)));
    xDist = xDist + this.dist;
    this.curve = {
        rad: this.rad,
        dist: this.dist,
        cDist: xDist
    };
}

function bezierT(t, startX, startY, control1X, control1Y, control2X, control2Y, endX, endY) {
    //calculates the tangent line to a point in the curve; later used to calculate the degrees of rotation at this point.
    this.mx = (3 * (1 - t) * (1 - t) * (control1X - startX)) + ((6 * (1 - t) * t) * (control2X - control1X)) + (3 * t * t * (endX - control2X));
    this.my = (3 * (1 - t) * (1 - t) * (control1Y - startY)) + ((6 * (1 - t) * t) * (control2Y - control1Y)) + (3 * t * t * (endY - control2Y));
}

function bezier2(t, startX, startY, control1X, control1Y, control2X, control2Y, endX, endY) {
    //Quadratic bezier curve plotter
    this.Bezier1 = new bezier1(t, startX, startY, control1X, control1Y, control2X, control2Y);
    this.Bezier2 = new bezier1(t, control1X, control1Y, control2X, control2Y, endX, endY);
    this.x = ((1 - t) * this.Bezier1.x) + (t * this.Bezier2.x);
    this.y = ((1 - t) * this.Bezier1.y) + (t * this.Bezier2.y);
    this.slope = new bezierT(t, startX, startY, control1X, control1Y, control2X, control2Y, endX, endY);

    this.point = {
        t: t,
        x: this.x,
        y: this.y,
        mX: this.slope.mx,
        mY: this.slope.my
    };
}

function bezier1(t, startX, startY, control1X, control1Y, control2X, control2Y) {
    //linear bezier curve plotter; used recursivly in the quadratic bezier curve calculation
    this.x = ((1 - t) * (1 - t) * startX) + (2 * (1 - t) * t * control1X) + (t * t * control2X);
    this.y = ((1 - t) * (1 - t) * startY) + (2 * (1 - t) * t * control1Y) + (t * t * control2Y);

}

回答by Jralford

An old old question... nevertheless, on my blog, I take a fairly close look at creating circular text using HTML5 Canvas:

一个老问题……不过,在我的博客上,我相当仔细地研究了使用 HTML5 Canvas 创建圆形文本:

html5graphics.blogspot.com

html5graphics.blogspot.com

In the example, options include rounded text alignment (left, center and right) from a given angle, inward and outward facing text, kerning (adjustable gap between characters) and text inside or outside the radius.

在示例中,选项包括从给定角度的圆形文本对齐(左、中和右)、向内和向外的文本、字距调整(字符之间的可调整间隙)以及半径内或外的文本。

There is also a jsfiddlewith a working example.

还有一个带有工作示例的jsfiddle

It is as follows:

如下:

document.body.appendChild(getCircularText("ROUNDED TEXT LOOKS BEST IN CAPS!", 250, 0, "center", true, true, "Arial", "18pt", 0));

function getCircularText(text, diameter, startAngle, align, textInside, inwardFacing, fName, fSize, kerning) {
    // text:         The text to be displayed in circular fashion
    // diameter:     The diameter of the circle around which the text will
    //               be displayed (inside or outside)
    // startAngle:   In degrees, Where the text will be shown. 0 degrees
    //               if the top of the circle
    // align:        Positions text to left right or center of startAngle
    // textInside:   true to show inside the diameter. False draws outside
    // inwardFacing: true for base of text facing inward. false for outward
    // fName:        name of font family. Make sure it is loaded
    // fSize:        size of font family. Don't forget to include units
    // kearning:     0 for normal gap between letters. positive or
    //               negative number to expand/compact gap in pixels
 //------------------------------------------------------------------------

    // declare and intialize canvas, reference, and useful variables
    align = align.toLowerCase();
    var mainCanvas = document.createElement('canvas');
    var ctxRef = mainCanvas.getContext('2d');
    var clockwise = align == "right" ? 1 : -1; // draw clockwise for aligned right. Else Anticlockwise
    startAngle = startAngle * (Math.PI / 180); // convert to radians

    // calculate height of the font. Many ways to do this
    // you can replace with your own!
    var div = document.createElement("div");
    div.innerHTML = text;
    div.style.position = 'absolute';
    div.style.top = '-10000px';
    div.style.left = '-10000px';
    div.style.fontFamily = fName;
    div.style.fontSize = fSize;
    document.body.appendChild(div);
    var textHeight = div.offsetHeight;
    document.body.removeChild(div);

    // in cases where we are drawing outside diameter,
    // expand diameter to handle it
    if (!textInside) diameter += textHeight * 2;

    mainCanvas.width = diameter;
    mainCanvas.height = diameter;
    // omit next line for transparent background
    mainCanvas.style.backgroundColor = 'lightgray'; 
    ctxRef.font = fSize + ' ' + fName;

    // Reverse letter order for align Left inward, align right outward 
    // and align center inward.
    if (((["left", "center"].indexOf(align) > -1) && inwardFacing) || (align == "right" && !inwardFacing)) text = text.split("").reverse().join(""); 

    // Setup letters and positioning
    ctxRef.translate(diameter / 2, diameter / 2); // Move to center
    startAngle += (Math.PI * !inwardFacing); // Rotate 180 if outward
    ctxRef.textBaseline = 'middle'; // Ensure we draw in exact center
    ctxRef.textAlign = 'center'; // Ensure we draw in exact center

    // rotate 50% of total angle for center alignment
    if (align == "center") {
        for (var j = 0; j < text.length; j++) {
            var charWid = ctxRef.measureText(text[j]).width;
            startAngle += ((charWid + (j == text.length-1 ? 0 : kerning)) / (diameter / 2 - textHeight)) / 2 * -clockwise;
        }
    }

    // Phew... now rotate into final start position
    ctxRef.rotate(startAngle);

    // Now for the fun bit: draw, rotate, and repeat
    for (var j = 0; j < text.length; j++) {
        var charWid = ctxRef.measureText(text[j]).width; // half letter

        ctxRef.rotate((charWid/2) / (diameter / 2 - textHeight) * clockwise);  // rotate half letter

        // draw char at "top" if inward facing or "bottom" if outward
        ctxRef.fillText(text[j], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2));

        ctxRef.rotate((charWid/2 + kerning) / (diameter / 2 - textHeight) * clockwise); // rotate half letter
    }

    // Return it
    return (mainCanvas);
}

回答by Deepu S Nath

You can try the following code to see how to write text along an Arc Path using HTML5 Canvas

您可以尝试以下代码以了解如何使用 HTML5 Canvas 沿弧路径编写文本

function drawTextAlongArc(context, str, centerX, centerY, radius, angle) {
  var len = str.length,
    s;
  context.save();
  context.translate(centerX, centerY);
  context.rotate(-1 * angle / 2);
  context.rotate(-1 * (angle / len) / 2);
  for (var n = 0; n < len; n++) {
    context.rotate(angle / len);
    context.save();
    context.translate(0, -1 * radius);
    s = str[n];
    context.fillText(s, 0, 0);
    context.restore();
  }
  context.restore();
}
var canvas = document.getElementById('myCanvas'),
  context = canvas.getContext('2d'),
  centerX = canvas.width / 2,
  centerY = canvas.height - 30,
  angle = Math.PI * 0.8,
  radius = 150;

context.font = '30pt Calibri';
context.textAlign = 'center';
context.fillStyle = 'blue';
context.strokeStyle = 'blue';
context.lineWidth = 4;
drawTextAlongArc(context, 'Text along arc path', centerX, centerY, radius, angle);

// draw circle underneath text
context.arc(centerX, centerY, radius - 10, 0, 2 * Math.PI, false);
context.stroke();
<!DOCTYPE HTML>
<html>

<head>
  <style>
    body {
      margin: 0px;
      padding: 0px;
    }
  </style>
</head>

<body>
  <canvas id="myCanvas" width="578" height="250"></canvas>
</body>

</html>

回答by Simon Sarris

You can't in any built in way. Please note that SVG natively does support text along paths, so you might want to consider SVG instead!

你不能以任何内置的方式。请注意,SVG 本身确实支持沿路径的文本,因此您可能需要考虑使用 SVG!

But you can write custom code in order to achieve the same effect, as some of us did for this question here: HTML5 Canvas Circle Text

但是您可以编写自定义代码以实现相同的效果,就像我们中的一些人在此处针对此问题所做的那样:HTML5 Canvas Circle Text