0 votes
by (910 points)
I don't know if the documentation is lacking or if I've just completely missed something, but I for the life of me can't figure this out. I want one passage to link to another... but not to the beginning of the passage, to a point somewhere in the middle. Something like this can be done easily in HTML, but it doesn't seem to work in Twine.

I've scoured the Harlowe documentation, but haven't seen anything similar to this. There's tons of really complex stuff, which leads me to believe that whoever wrote it thought this too simple or common knowledge to include... but everything I've tried either creates a link that leads to a "file not found" error, or creates a new passage with the code I tried to use as its name.

Any help would be greatly appreciated!

3 Answers

0 votes
by (159k points)
selected by
 
Best answer

The issue is that normally when you are moving from one HTML page to another page that has an ID target element you would be using a link that changes the URL referenced in the web-browser's location bar, thus triggering the scroll-into-view behaviour.

But a Twine based story HTML file is a web-application and the current story formats don't use the location bar to navigate between passages, this means you will need to use JavaScript to trigger the same scroll-into-view behaviour after transitioning from one Passage to another.

The following is one implementation of such a solution, it uses a Story variable to track which ID you want the next passage to scroll to.

1. Initialise the story variable within your project's startup tagged special passage.

(set: $anchor to "")

2. Use a Link with Setter to assign a value to the story variable before going to the Passage containing the target ID'ed element.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

(link: "Next")[
	(set: $anchor to "sed")
	(goto: "Next")
]

3. Assign the anchor ID (eg "sed") to one of the elements within the target Passage (eg. "Next")

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

<span id="sed">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</span>

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

4. Use a footer tagged special passage to execute the relevant JavaScript code when the story variable has been assigned an anchor ID. The execution needs to be delayed until after the HTML elements generated for the next passage have been added to the DOM.
note: You can name this passage whatever you like but you will need to remember that name for the next step, I named mine "ScrollIntoView"

(if: $anchor is not "")[
	(live: 20ms)[
		(stop:)
		(print: "<script>document.getElementById('" + $anchor + "').scrollIntoView()</script>")
		(set: $anchor to "")
	]
]

5. Place CSS like the following within the Story Stylesheet are to hide all visual output generated by the startup and footer tagged special passages, this is where you need to remember the passage name from point 4.

tw-include[type="startup"], tw-include[title="ScrollIntoView"] {
	display: none;
}

 

by (910 points)
Ah, thanks, that works perfectly! I guess that fraction of a second makes a pretty big difference.
0 votes
by (200 points)

So do you just want the new text to appear as a new page or to add on to the bottom of the last page?

If you want new page: 

Text stuff blah blah blah
[[Text you click on to go to next page->Next passage name]]

Or 

Text stuff blah blah blah
[[Next passage name]]

(the next passage name appears as the text you click on)

OR If you want to add on, you should ask someone with >300 points :)

by (910 points)
No, I want to do exactly what I said: link to a specific paragraph within an existing passage. My game has a point at which the player has made a conscious decision to do the morally correct thing, and has missed out on interesting things because of it. I want to allow them to navigate from this passage to the middle of the action in another branch of the story, at a specific point of my choosing.

This is the simplest thing in the world in HTML, so I can't for the life of me figure out why there isn't an obvious way to do it in Twine.
by (63.1k points)
Edit: nvm. Needed to reread it .
by (200 points)
K I see you guys have this covered sorry
by (910 points)
No worries. Trying to help should never be looked down on.
0 votes
by (63.1k points)

The problem is that passages are not web pages. You're building a single-page web app, which means none of your links go to urls, so the answer is much more complex than just tossing a hash on your url. Instead, you'll need to let the receiving passage know that the preceding passage was attempting to link to a specific point. Something like: 

(link: "go to #whatever")[
    (set: $anchor to "#whatever")
    (goto: "some passage")
]

You'd be wise to initialize $anchor to an empty string in a startup-tagged passage for the next part. 

The next bit is hard though, because Harlowe doesn't have a JavaScript API, so we'll need to abuse the (print:) macro. Place this in a header-tagged passage: 

{
(unless: $anchor is "")[
    (print: "<script>$(window).scrollTop('" + $anchor + "')</script>")
    (set: $anchor to "")
]
}

 

by (910 points)
How exactly do you assign a passage as a header? The wiki describes what they are, but makes no attempt to explain how to utilize them. I've tried using (header:) in a passage in various ways, but none of them result in text or code appearing as a header on any other passage.
by (63.1k points)
Use a passage tag. There's a little plus sign in the passage editor. Click it, type in the tag, then confirm it.
by (910 points)
Ah, okay. I didn't realize those had any kind of code utility... I thought they were just to allow you to keep track of things within the editor.

So what exactly is #whatever meant to be? Aside from the value that $anchor is temporarily being set to, I mean. I assume it's something that I'm meant to insert at the point I want the page to scroll to, but nothing I've tried seems to be working.
by (63.1k points)

It's an id. 

<span id='whatever'>scroll here</span>

 

by (910 points)

It doesn't seem to be working... I've confirmed through debug that both the startup and header are working, but nothing happens upon reaching the target passage. I've tried it without the bit at the end of the header that sets the string to empty, just so I could see whether the link was properly setting the string; it was, which confirms that both the link and header code are executing when they should be. It's just... not scrolling down.

Startup:

(set: $anchor to "")

Header:

{
(unless: $anchor is "")[
    (print: "<script>$(window).scrollTop('" + $anchor + "')</script>")
    (set: $anchor to "")
]
}

Linking passage:

(link: "testing with passage Speak Now")[
    (set: $anchor to "#whatever")
    (goto: "Speak Now")
]

Linked passage (halfway down the page):

<span id='whatever'>scroll here</span>

 

I've tried both the link and span id with and without the #, because within the debug it was showing the value as "#whatever" rather than just "whatever", but it doesn't seem to make a difference.

by (159k points)

Have you tried my version of the solution, which:

1. Moves the JavaScript execution to a footer tagged passage, to make sure that the target element has been generated before trying to reference it.

2. Adds a slight delay to that execution, to overcome the potential situation where Harlowe's passage transition effect (which detaches and re-attaches the passage's HTML elements) can cause the page to scroll back to the top.

 

...