xxxxxxxxxx
125
let slidingArc;
function setup() {
createCanvas(400, 400);
// For example, start at 45° (PI/4 radians) and span 180° (PI radians)
slidingArc = new SlidingArcText(
width / 2, // center x
height / 2, // center y
130, // radius for text placement
PI / 4, // start angle in radians
PI*1.3, // arc size in radians (180°)
"This is rotating sliding arc text",
{
automation: true,
revealDuration: 5000,
color: "blue",
fontSize: 20,
margin: 10
}
);
}
function draw() {
background(220);
slidingArc.update();
slidingArc.draw();
}
class SlidingArcText {
constructor(x, y, radius, startAngle, arcAngle, txt, options = {}) {
this.x = x;
this.y = y;
this.radius = radius;
this.startAngle = startAngle; // starting angle for text (in radians)
this.arcAngle = arcAngle; // total arc size (in radians, max 2*PI)
this.txt = txt;
this.automation = options.automation ?? false;
this.revealDuration = options.revealDuration ?? 3000; // in ms
this.color = options.color ?? "black";
this.fontSize = options.fontSize ?? 16;
this.margin = options.margin ?? 10;
this.rotationSpeed = options.rotationSpeed ?? 1; // radians per second
this.angleOffset = 0; // current rotation offset
this.startTime = millis();
this.revealFraction = 0; // from 0 (nothing revealed) to 1 (fully revealed)
// Pre-render the text onto an offscreen buffer.
this.pg = createGraphics(width, height);
this.renderBuffer();
}
renderBuffer() {
this.pg.clear();
this.pg.push();
// Translate so that (this.x, this.y) is the center.
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;
for (let i = 0; i < totalChars; i++) {
let c = this.txt.charAt(i);
// Evenly space the characters along the specified arc.
let angle = this.startAngle + map(i, 0, totalChars - 1, 0, this.arcAngle);
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);
}
// Update rotation offset. deltaTime is in ms so convert to seconds.
this.angleOffset += this.rotationSpeed * (deltaTime / 1000);
}
setReveal(frac) {
this.revealFraction = constrain(frac, 0, 1);
}
draw() {
// Use the drawingContext to apply rotation and clipping.
drawingContext.save();
// Rotate the entire system around the arc center.
drawingContext.translate(this.x, this.y);
drawingContext.rotate(this.angleOffset);
drawingContext.translate(-this.x, -this.y);
// If fully revealed, simply draw the pre-rendered image.
if (this.revealFraction >= 1) {
image(this.pg, 0, 0);
drawingContext.restore();
return;
}
// Otherwise, define a clipping wedge that gradually reveals the arc.
drawingContext.save();
const currentAngle = this.startAngle + this.revealFraction * this.arcAngle;
const maskRadius = this.radius + this.fontSize + this.margin;
const epsilon = 0.05; // slight extension to ensure edges are fully revealed
drawingContext.beginPath();
drawingContext.moveTo(this.x, this.y);
drawingContext.arc(this.x, this.y, maskRadius, this.startAngle - epsilon, currentAngle + epsilon, false);
drawingContext.lineTo(this.x, this.y);
drawingContext.closePath();
drawingContext.clip();
image(this.pg, 0, 0);
drawingContext.restore();
drawingContext.restore();
}
}