How do I perform a series of commands/macros on the same text element?

Twine Version: 2.3.8
Story Format: 2.31.1

Hello again! Apologies if this is super basic question…

I want to perform a series of commands on the same bit of text. For example, I would like it to fade in. Stay for a variable amount of time. And then fade out.

Thanks to a previous thread, I can now set a variable, user-determined time (reading speed: $wps) to Sugar Cube’s <<timed>> macro. I’d now like to apply that to several macro functions. However, when I try something like this, the <<timed>> macro is ignored:

<span id = "P1"><<timed `(5/$wps) + "s"`>>+<<replace "#P1" t8n>><</replace>>This passage in five words long.<</timed>></span>

<<timed `(5/$wps) + "s"`t8n>>And this next thing based on the five words before it. It fades in and 5 words above fade out.<</timed>>

When I try this, the <<replace>> macro can’t identify #P1:

<span id = "P1"><<replace "#P1" t8n>><</replace>>+<<timed `(5/$wps) + "s"`>>This passage in five words long.<</timed>></span>

<<timed `(5/$wps) + "s"`t8n>>And this next thing based on the nineteen words before it....<</timed>>

This functionality is going to be super common in the story I am working on. Words and sentences appear and disappear, and a rate defined by the user’s reading speed.

So how can I create a sequence of macro commands, which include the timing variable?

Any advice would be most welcome!

Many thanks, Ben

Okay, so I managed the following. But it’s too inelegant a solution. The lines jump when they’re removed, and there is no fade quality to the outgoing text.

<div id = "P1"><<timed 1s t8n>>This text appears.<</timed>></div>
<div id = "P2"><<timed 2s t8n>>Then this text appears.<</timed>></div>
<div id = "P3"><<timed 3s t8n>>Then this text.<</timed>></div>

<<timed 2.5s t8n>><<replace #P1 t8n>><</replace>><</timed>>
<<timed 3.5s t8n>><<replace #P2 t8n>><</replace>><</timed>>
<<timed 4.5s t8n>><<replace #P3 t8n>><</replace>><</timed>>

I have created a fadeout animation, but can’t work out how to apply it to a Div ID within the second round of <<timed>> macros.

Any help would be super valuable!

Many thanks, Ben

Something like the following solution may work for you…

  1. CSS classes used to hide then display then re-hide the text. Placed in you Story Stylesheet area.
.hidden {
    -webkit-transition: opacity 2s ease-in-out;
    -moz-transition: opacity 2s ease-in-out;
    -ms-transition: opacity 2s ease-in-out;
    -o-transition: opacity 2s ease-in-out;
	opacity: 0;
}
.fade-in {
	opacity: 1;
}
  1. Passage content that makes usage of the above defined hidden and fade-in CSS classes.
<div id="p1" class="hidden">This text appears.</div>

<<timed 2s t8n>><<addclass "#p1" "fade-in">><</timed>>
<<timed 6s t8n>><<removeclass "#p1" "fade-in">><</timed>>

see: <<addclass>> and <<removeclass>>

1 Like

It would probably be best to turn that into a macro to do that for you automatically, so that you don’t have to keep writing that out every time.

To do that, I need a few pieces of information.

  1. How long do you want it to take to fade in?
  2. How long do you want it to take to fade out?
  3. Does the amount time the text is displayed include the fade in and fade out time?
  4. Do you want any delay between the completion of the fade out and the next fade in?
  5. Do you want the final piece of text to remain on screen or do you want that to fade out too?
  6. And I assume that all words count towards the total number of words, correct?

I ask the last one, because you have, “This passage in five words long.”, which is six words long.

Once I know that information I can write a macro to take care of that for you.

1 Like

You are all amazing! Thank you.

Thank you so much for the offer. It’s very kind. If you’d like to lend a hand, any of the following would be useful…

The timing will largely be based on the variable words-per-second reading time ($wps) you helped me out with earlier today. There are a few different ways I’d like to use this:

  1. As one line (A) appears, the previous line (B) disappears. So (B) disappears at the same time as (A) appears, while maintaining passage’s original spacing (eg the text doesn’t jump around because the line above has disappeared).
  2. Text appears, and when the passage is complete, individual words disappear, until the passage is blank.
  3. Similarly, text falls away around a single word in a passage, or before the arrival of a single word.
  4. Paragraphs build up sentence by sentence, and then disappear when a new paragraph starts. (This could end up being similar to 1. above)

Those I’d say are the main ones. They would all be tied to the $wps reading-speed variable.

I have some additional questions about other effects, but perhaps I should address them in a separate post?

Many thanks! Ben

OK, but I also need the answers to the 6 questions I asked.

Well… since you’re asking…(apologies in advance if this is too detailed. Just let me know and I can find a simpler, more straightforward application. This would be the big picture…)

Questions 1 and 2: I’m probably going to want 3 options, “normal,” “slow” and “super slow.” I’m not sure what the default time is on the <<timed t8n>> macro, maybe 0.5s? That works okay for “normal.” Could be a touch slower. So “slow” is more like 3s and “super slow” 4s.

Question 3: Yes. (I think. I’d need to test it).

Question 4: Interesting. For the first example above, (One line fade out as the next fades in) I think the delay should be on the line fading out, but just a fraction. So the new line begins, and then the previous line fades away.

Question 5: With this effect, the last piece of text also fades out.

Question 6: Yes. Note that with the <<timed>> macro, if it’s a line in and a line out, the word count is line by line. However, sometimes the words accumulate over the passage before everything fades out, in which case the word count adds on to what is already on the page, not just the new line.

I hope that helps. Apologies for the variety of conditions!

Many thanks, Ben

Here’s the code for the first method you described.

First, add this to your JavaScript section:

/* <<fadeLine>> macro - Start */
Macro.add("fadeLine", {
	tags	 : ["nextLine", "complete"],
	handler  : function () {
		var txt, words, show, hide, transition = 500;  // Default transiton time = 0.5s
		var wps = State.variables.wps / 1000;  // Get words per second from $wps.
		if (this.args.length >= 1) {  // See if a parameter was passed to the macro.
			// Use the transition time parameter passed to the macro instead of the default.
			transition = Util.fromCssTime(this.args[0]);
		}
		show = transition;  // Initial delay.
		for (var i = 0, len = this.payload.length; i < len; ++i) {
			// Handle each section of the macro.
			txt = this.payload[i].contents.trim();  // Get a line of text.
			words = (txt.match(/\s/g) || []).length + 1;  // Count the words based on spaces.
			hide = (words / wps) - transition;  // Calculate display time.
			if (hide < 0) {
				hide = 0;
			}
			if (this.payload[i].name === "complete") {
				// Display a <<complete>> section's line of text.
				$(this.output).wiki('<span class="fadeline" data-transition="' + transition + '" data-show="' + show + '">' + txt + '</span>');
			} else {
				// Display any other section's line of text.
				$(this.output).wiki('<span class="fadeline" data-transition="' + transition + '" data-show="' + show + '" data-hide="' + hide + '">' + txt + '</span>');
			}
			show += hide + transition;  // Calculate the delay for the next line of text.
		}
	}
});

$(document).on(":passagerender", function (event) {
	$(event.content).find(".fadeline").each(function () {
		// Set up the transitions for each item with the "fadeline" class.
		if ($(this).data("hide") === undefined) {
			// Fade in a line of text after a delay.
			$(this).delay($(this).data("show")).fadeTo($(this).data("transition"), 1);
		} else {
			// Fade in a line of text after a delay, then fade the text out after another delay.
			$(this).delay($(this).data("show")).fadeTo($(this).data("transition"), 1).delay($(this).data("hide")).fadeTo($(this).data("transition"), 0);
		}
	});
});
/* <<fadeLine>> macro - End */

(See the jQuery documentation for an explanation of how the .data(), .delay(), and .fadeTo() methods work.)

Then add this to your Stylesheet section:

.fadeline {
	display: block;
	opacity: 0;
}

If you don’t want the text to appear on separate lines, then just remove the display: block; line there.

Once you’ve done that, you can now put code like this within your passages:

<<fadeLine 0.25s>>
	This is the first line of text.
<<nextLine>>
	This is the second line of text.
<<nextLine>>
	This is the third line of text.
<<nextLine>>
	This is the fourth line of text.
<<nextLine>>
	This is the fifth line of text.
<<complete>>
	This is the last line of text.
<</fadeLine>>

The “0.25s” part represents the transition time that a fade out or fade in takes and also the delay before the first line starts to fade in. That transition time is optional, so if you leave that out it will default to 0.5s. The minimum time that a line of text will be shown will be twice that transition time.

The first line of text within the <<fadeLine>> macro, as well as any lines after a <<nextLine>>, will fade in and then fade out. The lines of text will be shown for (words / $wps) + transition amount of time. Lines after a <<complete>> will fade in, but won’t fade out. And you’ll need the <</fadeLine>> at the end. Each line fades in at the same time that the previous line fades out (assuming that it fades out).

Hopefully that gives you an idea of how you can create the other macros you need and also makes what you’re trying to do a lot simpler.

Please let me know if you have any questions on how any of that works.

Enjoy! :smiley:

1 Like

This is amazing!

I will check this out when I am back on the project later today and let you know how it goes.

Many thanks, Ben

Hey, This works beautifully. Is there a way I can change the timing on an individual line within a sequence. So a line hangs out a little longer than the $wps?

Alternatively, is this the section of the Javascript that determines whether the $wps is counted in the fadein and fadeout time?

// Calculate display time.
if (hide < 0) {
hide = 0;
}
if (this.payload[i].name === “complete”) {
// Display a <> section’s line of text.
$(this.output).wiki(’’ + txt + ‘’);
} else {
// Display any other section’s line of text.
$(this.output).wiki(’’ + txt + ‘’);
}

In which case, what would be the alteration to wait for the fadein to complete before counting the $wps (I remember this being one of your considerations yesterday)?

Many thanks, Ben

Amazing! I’ve got this working. Here’s my next question: What if I wanted individual words to fade out once the full passage had loaded? I have managed to get one word out by using the following code. But if I start to applying the idea it to an additional words, it doesn’t seem to work. In the sentence, “The one who disappeared,” so far I’ve managed “disappear” to disappear. How would I get the rest out?

> <p id = "line" class = "hidden" style = "text-align:right">The one who <span id = "word4" class = "hidden">disappeared</span></p>
> 
> <<timed 1.5s t8n>><<addclass "#line" "fade-in">><<addclass "#word4" "fade-in">><</timed>>
> <<timed 5s t8n>><<removeclass "#word4" "fade-in">><</timed>>

Many thanks, Ben

I tried to use it and what happens here is this: fadeline

Yes, just modify the code to add Util.fromCssTime(this.payload[i].args[0]) to the hide variable, like this:

			hide = (words / wps) - transition;  // Calculate display time.
			if ((i > 0) && (this.payload[i].args >= 1)) {
				hide += Util.fromCssTime(this.payload[i].args[0]);  // Add extra delay.
			}

and then you can add a delay parameter to any <<nextLine>> lines, like this:

<<fadeLine 0.25s>>
	This is the first line of text.
<<nextLine 1s>>
	This line of text stays visible for an extra second.
<</fadeLine>>

That should work the way you want.
 

Sort of, though you included a bit extra, and it just determines whether it fades out or not, depending on whether the code has a <<complete>> or not. This is the code which does that:

			if (this.payload[i].name === "complete") {
				// Display a <<complete>> section's line of text.
				$(this.output).wiki('<span class="fadeline" data-transition="' + transition + '" data-show="' + show + '">' + txt + '</span>');
			} else {
				// Display any other section's line of text.
				$(this.output).wiki('<span class="fadeline" data-transition="' + transition + '" data-show="' + show + '" data-hide="' + hide + '">' + txt + '</span>');
			}

The this.payload[i].name is looking to see if the macro name of the current chunk of the macro that’s being processed starts with “<<complete>>” or not.
 

The first chunk of code I showed in this post is the part which calculates how long the text is displayed, starting from after it’s completely faded in.
 

For that you’d want to create a new macro, maybe call it <<fadeWord>>. In that code you’d need to break each word down into its own <span>, and then hide those spans one-by-one. You could use the .split() method to split a string into an array of strings, which you could then output one at a time.

The code I gave you before should get you most of the way there, you’ll just want to split the words yourself, rather than having lines split by using <<nextLine>> or <<complete>> markers. Use this as an opportunity to learn a bit more about coding things like this for yourself.
 

What did you set $wps to? I set it to 4 during my testing. If you didn’t set it at all, then that may be why it’s acting weird.

Hope that helps! :smiley:

Thank you amigues! I’ll get cracking on that and leave you in peace (until the audio part of the project kicks in).

All the best, Ben

For the audio stuff you might want to check out the “Music” section of my Twine/SugarCube sample code collection. There’s a variety of helpful tips and tricks in there for working with audio.

Yeah, that was it, didn’t knew I had to set that. It’s working fine. Nice code. I might use it someday.