xxxxxxxxxx
233
let arcText;
function setup() {
createCanvas(400, 400);
// Create an ArcText instance.
// This example will reveal the text "This is an arc of text" gradually over 5 seconds.
arcText = new ArcText(
width / 2, // center x
height / 2, // center y
100, // radius
PI, // start angle (180°)
TWO_PI, // end angle (360°)
"This is an arc of text",
{
automation: true,
revealDuration: 5000, // 5 seconds for a full reveal
color: "blue",
fontSize: 16
}
);
slidingArc = new SlidingArcText(
width / 2, // center x
height / 2 + 100, // center y
100, // radius where text is drawn
PI, // start angle (180°)
TWO_PI, // end angle (360°)
"This is sliding arc text",
{
automation: true,
revealDuration: 5000,
color: "blue",
fontSize: 20
}
);
}
function draw() {
background(220);
// Update and draw the arc text.
arcText.update();
arcText.draw();
slidingArc.update();
slidingArc.draw();
}
class SlidingArcText {
constructor(x, y, radius, startAngle, endAngle, txt, options = {}) {
this.x = x;
this.y = y;
this.radius = radius;
this.startAngle = startAngle;
this.endAngle = endAngle;
this.txt = txt;
this.automation = options.automation ?? false;
this.revealDuration = options.revealDuration ?? 3000; // in milliseconds
this.color = options.color ?? "black";
this.fontSize = options.fontSize ?? 16;
// Extra margin so that rotated letters aren't clipped.
this.margin = options.margin ?? 10;
this.startTime = millis();
this.revealFraction = 0; // 0 (nothing revealed) to 1 (fully revealed)
// Pre-render the arc text to a buffer.
this.pg = createGraphics(width, height);
this.renderBuffer();
}
renderBuffer() {
this.pg.clear();
this.pg.push();
// Translate so that the arc's center is the origin in the buffer.
this.pg.translate(this.x, this.y);
this.pg.textAlign(CENTER, CENTER);
this.pg.fill(this.color);
this.pg.textSize(this.fontSize);
const totalChars = this.txt.length;
const totalAngle = this.endAngle - this.startAngle;
for (let i = 0; i < totalChars; i++) {
let c = this.txt.charAt(i);
// Map the character index to an angle along the arc.
let angle = this.startAngle + map(i, 0, totalChars - 1, 0, totalAngle);
let xPos = this.radius * cos(angle);
let yPos = this.radius * sin(angle);
this.pg.push();
this.pg.translate(xPos, yPos);
// Rotate so the character is tangent to the arc.
this.pg.rotate(angle + HALF_PI);
this.pg.text(c, 0, 0);
this.pg.pop();
}
this.pg.pop();
}
update() {
if (this.automation) {
this.revealFraction = constrain(
(millis() - this.startTime) / this.revealDuration,
0,
1
);
}
}
setReveal(frac) {
this.revealFraction = constrain(frac, 0, 1);
}
draw() {
// Calculate the current angle for the reveal.
const currentAngle =
this.startAngle + this.revealFraction * (this.endAngle - this.startAngle);
// Compute the mask radius with an added margin.
const maskRadius = this.radius + this.fontSize + this.margin;
// Save the drawing context state.
drawingContext.save();
// Move to the arc center for defining the clipping wedge.
drawingContext.translate(this.x, this.y);
// Begin the clipping path.
drawingContext.beginPath();
// epsilon helps to extend the wedge a bit at both edges.
const epsilon = 0.05;
drawingContext.moveTo(0, 0);
drawingContext.arc(0, 0, maskRadius, this.startAngle - epsilon, currentAngle + epsilon, false);
drawingContext.lineTo(0, 0);
drawingContext.closePath();
drawingContext.clip();
// Translate back so that the pre-rendered image aligns correctly.
drawingContext.translate(-this.x, -this.y);
// Draw the pre-rendered graphics buffer; only the clipped region will show.
image(this.pg, 0, 0);
// Restore the drawing context state.
drawingContext.restore();
}
}
class ArcText {
/**
*
* @param {number} x - X coordinate of the arc's center.
* @param {number} y - Y coordinate of the arc's center.
* @param {number} radius - Radius of the arc.
* @param {number} startAngle - Start angle (in radians).
* @param {number} endAngle - End angle (in radians).
* @param {string} txt - The text to display along the arc.
* @param {Object} options - Additional options.
* options.automation {boolean} - If true, reveal text over time (default false).
* options.revealDuration {number} - Duration in ms for full reveal (default 3000).
* options.color {string} - Text color (default "black").
* options.fontSize {number} - Font size (default 16).
*/
constructor(x, y, radius, startAngle, endAngle, txt, options = {}) {
this.x = x;
this.y = y;
this.radius = radius;
this.startAngle = startAngle;
this.endAngle = endAngle;
this.txt = txt;
// Options with defaults
this.automation = options.automation ?? false;
this.revealDuration = options.revealDuration ?? 3000;
this.color = options.color ?? "black";
this.fontSize = options.fontSize ?? 16;
// For automation timing
this.startTime = millis();
this.elapsed = 0;
}
update() {
// If automation is enabled, update elapsed time
if (this.automation) {
this.elapsed = millis() - this.startTime;
}
}
draw() {
push();
translate(this.x, this.y);
textAlign(CENTER, CENTER);
fill(this.color);
textSize(this.fontSize);
// Determine how much of the text to reveal.
// If automation is false, show all the text.
let revealFraction = 1;
if (this.automation) {
revealFraction = constrain(this.elapsed / this.revealDuration, 0, 1);
}
const totalChars = this.txt.length;
const charsToShow = floor(totalChars * revealFraction);
const totalAngle = this.endAngle - this.startAngle;
// Loop through each character to display it along the arc.
for (let i = 0; i < charsToShow; i++) {
const currentChar = this.txt.charAt(i);
// Map the index to an angle along the arc.
// (You can tweak this mapping if you need extra spacing or kerning adjustments.)
const angle = this.startAngle + map(i, 0, totalChars - 1, 0, totalAngle);
// Calculate the position on the arc.
const xPos = this.radius * cos(angle);
const yPos = this.radius * sin(angle);
push();
translate(xPos, yPos);
// Rotate so the character is tangent to the arc.
rotate(angle + HALF_PI);
text(currentChar, 0, 0);
pop();
}
pop();
}
}