Text reveal along a path

Hi there!

Help needed…Please

I’m new to coding…

What I am trying to do is create an interacted animation for the background of a website where; when you move the mouse down and to the right, and/ or scroll the page down it will reveal a sentence on a curved path (Like shown in the pic below)…

and when you move the mouse up and to the left, and/ or scroll the page up, the text will be hidden again.


need it to be responsive too mobile etc too.

Can anyone please help :slight_smile:

Dan

What have you got working so far? Post your code!

Without seeing your code, we have no idea how to help!

Are you stuck on doing the letters?

Positioning them on a path?

Rotating them?

Dealing with mouse inputs?

Making it work on mobile?

Make it easy for us to help you! If I have to start by writing a sketch from scratch just to even BEGIN helping, I might not even bother putting the effort in!

If you are asking for help, EVERY POST YOU MAKE SHOULD HAVE YOUR CURRENT CODE.

Make sure to hit the </> button to format your code too.

Thanks for the reply @TfGuy44,

Here is what I have so far…

Cheers,
Dan


String message = "Lorem ipsum dolor sit amet,";
PFont f;
float r = 200;
float lineLength = width/5; 

void setup() {
  size(1000, 1000);
  f = createFont("futura",40,true);
  textFont(f);
  // The text must be centered!
  textAlign(CENTER);
  smooth();
}

void draw() {
  background(#1f23d6);

  translate(mouseX,mouseY); 
  noFill();
  stroke(0);
  ellipse(0, 0, r*2, r*2);


  float arclength = 0;

  for (int i = 0; i < message.length(); i++)
  {
    char currentChar = message.charAt(i);
    float w = textWidth(currentChar);

    arclength += w/2;
    float theta = PI + arclength / r;    
    pushMatrix();
    translate(r*cos(theta), r*sin(theta));
    rotate(theta+PI/2);
    fill(0);
    text(currentChar,0,0);
    popMatrix();
    arclength += w/2;
  }
}

I have also been playing around with html and css here so maybe it could be a better way at doing it?

see below…

HTML

<svg viewBox="0 0 425 300">
  <path id="curve" d="M6,150C49.63,93,105.79,36.65,156.2,47.55,207.89,58.74,213,131.91,264,150c40.67,14.43,108.57-6.91,229-145" />
  <text x="25">
    <textPath xlink:href="#curve">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed dignissim turpis odio, vel elementum erat mollis eget. Nunc scelerisque vulputate metus vitae cursus. Suspendisse ac pretium ex. Pellentesque pellentesque lacus eget massa tempus, sed bibendum est semper. Vestibulum accumsan molestie est, nec rutrum i
    </textPath>
  </text>
</svg>

CSS

body {
  background-color: #2E3B97;
  font-family: 'futura', cursive;
  font-size: 5px;
}

path {
  fill: transparent;
}

text {
  fill: #000000;
}

Okay. So you have some code that shows you have to lop over the letters in a string, position each letter at a different position, and rotate it accordingly.

If you’re going to make the letters go in a path (not just a circle), you’re going to need to associate some information with each letter:

  • What that letter is.
  • Which number letter it is.
  • Where it should appear.
  • How much it should be rotated.

You might also want code that does useful things for a letter:

  • Creates a new letter given some information about it.
  • Draws the letter.
  • Determines if a letter should appear.

The point I’m trying to make is that you should write a Letter class.


Oh, I see now that you have some letters going in a curve already. Okay. Different approach…

  • How many letters are you going to draw?
  • How are you going to keep track of that information?
  • Can you make the number of letters drawn depend on that piece of information? Maybe use substring()?
  • Initially, how many letters are displayed?
  • What actions cause more letters to appear?
  • What actions cause less letters to appear?
  • How much does the number of shown letters change?

cheers @TfGuy44,

  • I will be drawing 100 plus letters. (that includes spaces too). The amount of letters will be a matter of how many can fit on the path, so factors that will affect the amount of letters will be the size of the text, the kerning and the length of the path…
  • how would I use substring() for to calculate this info?
  • the mouse moving down or to the right and/or scrolling down the page will make the letters appear.
  • the mouse movie up, to the left and/or scrolling up the page will make the letters disappear.
  • the amount of number of letters appearing or disappearing is valued at the speed of the mouse…the faster the speed the more letters appear or disappear…

Cheers,
Dan

Hi dnganeko,

I suspect that creating your background with HTML and CSS, or with an SVG editor, would be an easier way to do it… Even p5.js may be preferable to Java-based Processing.

There’s more than one way to create a curve and then calculate a point on that curve from a percentage (a step value in the range 0.0 to 1.0). For Bezier curves, two functions in the Processing API that you may find helpful are bezierPoint (in p5.js) and bezierTangent (in p5.js); for Catmull-Rom curves, curvePoint (in p5.js) and curveTangent (in p5.js).

String message = "Lorem ipsum dolor sit amet,"
  + "consectetur adipiscing elit.";
char[] messageAsChars = message.toCharArray();
int charCount = messageAsChars.length;
float charCountToStep = 1.0 / float(charCount);

float ap0x, ap0y, 
  cp0x, cp0y, 
  cp1x, cp1y, 
  ap1x, ap1y;

void setup() {
  size(1024, 512);
  smooth(8);
  randomizePoints();
  textSize(32);
  textAlign(CENTER, CENTER);
  textMode(MODEL);
}

void draw() {
  background(255);
  noFill();
  stroke(0xff007fff);
  bezier(ap0x, ap0y, 
    cp0x, cp0y, 
    cp1x, cp1y, 
    ap1x, ap1y);

  noStroke();
  fill(0xff000000);
  
  float step0 = frameCount * 0.01;
  
  for (int i = 0; i < charCount; ++i) {
    float step1 = i * charCountToStep;
    float step2 = (step0 + step1) % 1.0;
    
    float x = bezierPoint(ap0x, cp0x, cp1x, ap1x, step2);
    float y = bezierPoint(ap0y, cp0y, cp1y, ap1y, step2);
    
    float tanx = bezierTangent(ap0x, cp0x, cp1x, ap1x, step2);
    float tany = bezierTangent(ap0y, cp0y, cp1y, ap1y, step2);
    
    float angle = atan2(tany, tanx);

    pushMatrix();
    translate(x, y);
    rotate(angle);
    text(messageAsChars[i], 0.0, 0.0);
    popMatrix();
  }
}

void mouseReleased() {
  randomizePoints();
}

void randomizePoints() {
  float halfw = width * 0.5;
  float halfh = height * 0.5;
  ap0x = random(0.0, halfw);
  cp0x = random(0.0, width);
  cp1x = random(0.0, width);
  ap1x = random(halfw, width);

  ap0y = random(0.0, halfh);
  cp0y = random(0.0, height);
  cp1y = random(0.0, height);
  ap1y = random(halfh, height);
}

From bezierTangent or curveTangent, you can use atan2 (in p5.js) to find the angle of rotation for a point along the curve. This is simply one curve, not multiple curves connected together, where the last anchor point of the first curve is the first anchor point of the next. Even though the steps provided to bezierPoint are evenly distributed, the resulting points can crowd along the curve.

There are resources here and there which may help with the maths once you know the key words to search for. I’ve found the following helpful:

1 Like

The point I was trying to make is that you can have a variable (let’s call it number_letters) that tracks how many letters you should display at any time.

This variable needs to be at least 0, and at most the number of letters in your text.

You can use this variable as the upper limit when you loop over your letters. That way you only draw as many letters as you want, and not every letter.

Here is an example. Click the left/right mouse buttons to increase/decrease the value of this new variable. See how changing it’s value causes more or less letters to appear, as it is the value used as the limit when looping over letters:

String message = "Lorem ipsum dolor sit amet,";
PFont f;
float r = 200;
float lineLength = width/5; 

int number_letters = 3;

void setup() {
  size(1000, 1000);
  f = createFont("futura", 40, true);
  textFont(f);
  // The text must be centered!
  textAlign(CENTER);
  smooth();
}

void draw() {
  background(#1f23d6);

  translate(mouseX, mouseY); 
  noFill();
  stroke(0);
  ellipse(0, 0, r*2, r*2);


  float arclength = 0;

  number_letters = constrain(number_letters, 0, message.length());

  for (int i = 0; i < number_letters; i++)
  {
    char currentChar = message.charAt(i);
    float w = textWidth(currentChar);

    arclength += w/2;
    float theta = PI + arclength / r;    
    pushMatrix();
    translate(r*cos(theta), r*sin(theta));
    rotate(theta+PI/2);
    fill(0);
    text(currentChar, 0, 0);
    popMatrix();
    arclength += w/2;
  }
}

void mousePressed() {
  if ( mouseButton == LEFT ) {
    number_letters++;
  } else {
    number_letters--;
  }
}

Now it’s up to you to have this value change in an appropriate way when the mouse is moved or the page is scrolled. Try looking at the mouseMoved()function.

Hi @behreajj,

Thanks heaps for the help! This has given me a lot to play with. I’ll let you know how I go :slight_smile:

Dan

Hi @TfGuy44,

Cheers for that. I understand now. kind of haha. I’m going to have a play and will let you know how I go.

Cheers again mate.

Dan

Hey behreajj,

Sorry, this may be a simple question but how do you change the font ? I’m trying to use PFont?

String message = "Lorem ipsum dolor sit amet,"
  + "consectetur adipiscing elit.";
char[] messageAsChars = message.toCharArray();
int charCount = messageAsChars.length;
float charCountToStep = 1.0 / float(charCount);

PFont font;
font = createFont("ACaslon-Italic-48.vlw");

float ap0x, ap0y, 
  cp0x, cp0y, 
  cp1x, cp1y, 
  ap1x, ap1y;

void setup() {
  size(1024, 512);
  smooth(8);
  randomizePoints();
  textSize(32);
  textFont(font);
  textAlign(CENTER, CENTER);
  textMode(MODEL);
}

void draw() {
  background(255);
  noFill();
  stroke(0xff007fff);
  bezier(ap0x, ap0y, 
    cp0x, cp0y, 
    cp1x, cp1y, 
    ap1x, ap1y);

  noStroke();
  fill(0xff000000);
  
  float step0 = frameCount * 0.01;
  
  for (int i = 0; i < charCount; ++i) {
    float step1 = i * charCountToStep;
    float step2 = (step0 + step1) % 1.0;
    
    float x = bezierPoint(ap0x, cp0x, cp1x, ap1x, step2);
    float y = bezierPoint(ap0y, cp0y, cp1y, ap1y, step2);
    
    float tanx = bezierTangent(ap0x, cp0x, cp1x, ap1x, step2);
    float tany = bezierTangent(ap0y, cp0y, cp1y, ap1y, step2);
    
    float angle = atan2(tany, tanx);

    pushMatrix();
    translate(x, y);
    rotate(angle);
    text(messageAsChars[i], 0.0, 0.0);
    popMatrix();
  }
}

void mouseMoved() {
  randomizePoints();
}

void randomizePoints() {
  float halfw = width * 0.5;
  float halfh = height * 0.5;
  ap0x = random(0.0, halfw);
  cp0x = random(0.0, width);
  cp1x = random(0.0, width);
  ap1x = random(halfw, width);

  ap0y = random(0.0, halfh);
  cp0y = random(0.0, height);
  cp1y = random(0.0, height);
  ap1y = random(halfh, height);
}

Hi dnganeko,

Depending on your goals, you can accomplish what you want to do with the HTML, CSS and SVG you’ve already posted. Neither Processing nor p5.js are required. Two key pieces of information are 1. how to get the amount that the user has scrolled down the page as a percentage; then 2. how to convert that to a substring of the complete text. I added to your original code in a CodePen here.

There are two ways of creating a font. With createFont:

String content = "Lorem ipsum";

void setup() {
  size(512, 256);

  String[] available = PFont.list();
  printArray(available);
  int chosenFontIndex = 152;
  int fontSize = 32;
  PFont font = createFont(available[chosenFontIndex], fontSize);

  textAlign(CENTER, CENTER);
  textSize(fontSize);
  textFont(font);
}

void draw() {
  background(255);
  fill(0);
  text(content, width * 0.5, height * 0.5);
}

With loadFont:

String content = "Lorem ipsum";

void setup() {
  size(512, 256);

  int fontSize = 32;
  PFont font = loadFont("FiraCode-Regular-48.vlw");

  textAlign(CENTER, CENTER);
  textSize(fontSize);
  textFont(font);
}

void draw() {
  background(255);
  fill(0);
  text(content, width * 0.5, height * 0.5);
}

The .vlw file is created by going to the Processing IDE’s menu Tools > Create Font .... The .vlw file is placed in the sketch’s data folder.

Hi behreajj,

Thanks heaps for the help! It has helped a lot!

Dan

Hi Behreajj,

With this sketch (below)… I’m trying to make it work on mobile too. for example- instead of the mouse movements creating the random Points… when I touch the screen and move my finger around the screen, it want it to do the same…

Can you please help me with ho I would do this?

String message = "MOOOOOOOOOOOOOOOOOOOOOOOOOOOOVE....MOTION...MOVING";
char[] messageAsChars = message.toCharArray();
int charCount = messageAsChars.length;
float charCountToStep = 1.0 / float(charCount);

float ap0x, ap0y, 
  cp0x, cp0y, 
  cp1x, cp1y, 
  ap1x, ap1y;

void setup() {
  size(1024, 512);
  smooth(8);
  randomizePoints();
  textSize(10);
  textAlign(CENTER, CENTER);
  textMode(MODEL);
}

void draw() {
  background(#2E3B97);
  noFill();
  stroke(#2E3B97);
  bezier(ap0x, ap0y, 
    cp0x, cp0y, 
    cp1x, cp1y, 
    ap1x, ap1y);

  noStroke();
  fill(#FBE44D);
  
  float step0 = frameCount * 0.0;
  
  for (int i = 0; i < charCount; ++i) {
    float step1 = i * charCountToStep;
    float step2 = (step0 + step1) % 2.0;
    
    float x = bezierPoint(ap0x, cp0x, cp1x, ap1x, step2);
    float y = bezierPoint(ap0y, cp0y, cp1y, ap1y, step2);
    
    float tanx = bezierTangent(ap0x, cp0x, cp1x, ap1x, step2);
    float tany = bezierTangent(ap0y, cp0y, cp1y, ap1y, step2);
    
    float angle = atan2(tany, tanx);

    pushMatrix();
    translate(x, y);
    rotate(angle);
    text(messageAsChars[i], 0.0, 0.0);
    popMatrix();
  }
}

void mouseMoved() {
  randomizePoints();
}

void randomizePoints() {
  float halfw = width * 0.5;
  float halfh = height * 0.5;
  ap0x = random(0.0, halfw);
  cp0x = random(0.0, width);
  cp1x = random(0.0, width);
  ap1x = random(halfw, width);

  ap0y = random(0.0, halfh);
  cp0y = random(0.0, height);
  cp1y = random(0.0, height);
  ap1y = random(halfh, height);
}

Hi Dan,

Wish I could be more help, but mobile development isn’t my specialty. (I don’t have a touch screen device either.)

The first question I would ask were I in your place would be: do the mouse pressed, released and dragged functions accurately represent touch events on the mobile device? I’m not sure because different flavors of Processing, like Android and p5.js also include a touches array and touch event listeners.

Searching the forum may also provide more information. Two threads which bring up touch are here and here. They both focus on buttons, but may offer some leads.

The next matter I’d think about has more to do with interaction design. How can the user’s touches be converted into a curve animation? For example, a touch started event sets the first anchor point, then a touch end event sets the second anchor point of the curve… Or maybe the distance from the beginning of a touch to the end of a touch can be measured against a maximum possible distance to create a step supplied to bezierPoint and bezierTangent.

Hope that’s a helpful start!

In Android, I believe the field mousePressed used in draw() (instead of mousePressed as a function) will allow you to sense when the mouse is down, as the name implies. Your task will be to add these coordinates to an auto-growing array, like ArrayList. The key point here is to add points oly if they are not in the list so you are not adding redundant points. For instance, if you press down and you don’t move your finger, you only want to add one point at that location. If you move your finger then you will add a new point to your list only if the new point is “further apart” from your previous added point in the list.

Why to use mousePressed instead of mousePressed(). My understandig is that the function only detects and returns once when it detects the pressing action. The former, it continuosly returns true. You detect this state in draw().

There is a bezier tool, and some previous posts with bezier functions that you could use to integrate your points.

Kf