Displaying text once the user hovers on a link?

0 votes
asked Jan 7 by vanillacoke (430 points)
edited Jan 7 by vanillacoke

Is it possible to create a variant of linkappend, where, instead of having to click on a link to display text below, the player just needs to move his/her cursor over the link?

 

Edit: There's this answer but it brings the player to the next passage instead of appending text.

2 Answers

0 votes
answered Jan 7 by JaR (670 points)

What format (and version) are you using?

I'm using Harlowe 2.1.0 and the code to use is explained here:

https://twine2.neocities.org/#macro_mouseover-append

commented Jan 7 by vanillacoke (430 points)
I'm using sugarcube 2.210 (it's stated in the tag).
0 votes
answered Jan 7 by greyelf (90,650 points)
edited Jan 9 by greyelf

EDIT: Replaced my version of the point 1 code example with that supplied by TheMadExile.

NOTE: The code of the follow solution is heavily based on the source code of the SugarCube 2 <<linkappend>> related macros, you should thank TheMaxExile for all the good parts, and blame me for any bugs that I have introduced due to any lacking in my Javascript knowledge.

1. The following Javascript example creates three new custom macros named: <<hoverappend>>, <<hoverprepend>>, and <<hoverreplace>>. This code should be placed within your Story Javascript area.

The three new macros work very similar to the associated <<linkappend>> related macros except that the TwineScript contain within their body is executed the first time the mouse cursor is moved over the related "Link Text" instead of upon selection of it.

/*
	<<hoverappend>>, <<hoverprepend>>, & <<hoverreplace>>
*/
Macro.add(['hoverappend', 'hoverprepend', 'hoverreplace'], {
	isAsync : true,
	tags    : null,
	handler : function () {
		if (this.args.length === 0) {
			return this.error('no hover text specified');
		}

		// Custom debug view setup.
		if (Config.debug) {
			this.createDebugView(
				this.name,
				this.source + this.payload[0].contents + '<</' + this.name + '>>''
			);
		}

		const $hover     = jQuery(document.createElement('span'));
		const $insert    = jQuery(document.createElement('span'));
		const transition = this.args.length > 1 && /^(?:transition|t8n)$/.test(this.args[1]);

		$hover
			.wikiWithOptions({ profile : 'core' }, this.args[0])
			.addClass('link-hover macro-' + this.name)
			.one('mouseenter', (function (macroName, contents) {
				return this.createShadowWrapper(function (event) {
					if (macroName === 'hoverreplace') {
						$hover.remove();
					}
					else {
						$hover.removeClass('link-hover');
					}

					if (contents !== '') {
						const frag = document.createDocumentFragment();
						new Wikifier(frag, contents);
						$insert.append(frag);
					}

					if (transition) {
						setTimeout(function () {
							$insert.removeClass('macro-' + macroName + '-in');
						}, Engine.minDomActionDelay);
					}
				});
			})(this.name, handler.payload[0].contents))
			.appendTo(this.output);

		$insert.addClass('macro-' + this.name + '-insert');

		if (transition) {
			$insert.addClass('macro-' + this.name + '-in');
		}

		if (this.name === 'hoverprepend') {
			$insert.insertBefore($hover);
		}
		else {
			$insert.insertAfter($hover);
		}
	}
});

2. The following CSS is used to style the Link Text of the new macros, it should be placed within your Story Stylesheet area. You should change this CSS to suit your needs.

.link-hover {
    cursor: pointer;
    color: orange;
    text-decoration: none;
    -webkit-transition-duration: .2s;
    -o-transition-duration: .2s;
    transition-duration: .2s;
}

3. The following TwineScript demonstrates how to use the new macros within a Passage.

Some text with a <<hoverappend "link">> that displays this text ''after'' the link once hovered over.<</hoverappend>>

Some text with a <<hoverprepend "link">> that displays this text ''before'' the link once hovered over.<</hoverprepend>>

Some text with a <<hoverreplace "link">> that displays this text ''instead of'' the link once hovered over.<</hoverreplace>>

 

commented Jan 8 by TheMadExile (45,500 points)

I'd suggest one, relatively, small change.  Instead of keeping a reference to the entire macro execution context, via this, in the call to .one(), I'd simply use a capture to hold onto the two pieces of data you need—the macro's name and contents.  For example:

			.one('mouseenter', ((macroName, content) => this.createShadowWrapper(function (event) {
				if (macroName === 'hoverreplace') {
					$hover.remove();
				}
				else {
					$hover.removeClass('link-hover');
				}

				if (content !== '') {
					const frag = document.createDocumentFragment();
					new Wikifier(frag, content);
					$insert.append(frag);
				}

				if (transition) {
					setTimeout(() => $insert.removeClass(`macro-${macroName}-in`), Engine.minDomActionDelay);
				}
			}))(this.name, handler.payload[0].contents))

 

commented Jan 8 by greyelf (90,650 points)

@TheMadExile: Is there still an issue with including ES6 anonymous function syntax in examples?

I remember you warned me about doing that in this comment which is why I replaced them in the example I gave. I understand it was due to the potential version of Twine 2 an end-user was running, and that some people are still not running the latest version of Twine 2.

commented Jan 8 by TheMadExile (45,500 points)
edited Jan 8 by TheMadExile

I, generally, do not recommend using syntax from >=ES2015 since it cannot be polyfilled and old (i.e.pre-ES2015) browsers are still in use.  That said, those browsers are probably rare enough now that I don't bother making a fuss about it, as I used to do.

The referenced comment was related to Twine 2 itself, which, at the time, used a version of NW.js which was not >=ES2015 compatible.  That, at least, has definitely changed.

Also.  I only used an arrow function for the capture IIFE within my example because you do still have several bits of ES2015 syntax within yours; the shorthand method syntax, one arrow function (see the transition timeout callback), and several uses of template strings.

[EDIT] Still, better safe than sorry, so here's a completely ES5 compatible version:

/*
	<<hoverappend>>, <<hoverprepend>>, & <<hoverreplace>>
*/
Macro.add(['hoverappend', 'hoverprepend', 'hoverreplace'], {
	isAsync : true,
	tags    : null,
	handler : function () {
		if (this.args.length === 0) {
			return this.error('no hover text specified');
		}

		// Custom debug view setup.
		if (Config.debug) {
			this.createDebugView(
				this.name,
				this.source + this.payload[0].contents + '<</' + this.name + '>>''
			);
		}

		const $hover     = jQuery(document.createElement('span'));
		const $insert    = jQuery(document.createElement('span'));
		const transition = this.args.length > 1 && /^(?:transition|t8n)$/.test(this.args[1]);

		$hover
			.wikiWithOptions({ profile : 'core' }, this.args[0])
			.addClass('link-hover macro-' + this.name)
			.one('mouseenter', (function (macroName, contents) {
				return this.createShadowWrapper(function (event) {
					if (macroName === 'hoverreplace') {
						$hover.remove();
					}
					else {
						$hover.removeClass('link-hover');
					}

					if (contents !== '') {
						const frag = document.createDocumentFragment();
						new Wikifier(frag, contents);
						$insert.append(frag);
					}

					if (transition) {
						setTimeout(function () {
							$insert.removeClass('macro-' + macroName + '-in');
						}, Engine.minDomActionDelay);
					}
				});
			})(this.name, handler.payload[0].contents))
			.appendTo(this.output);

		$insert.addClass('macro-' + this.name + '-insert');

		if (transition) {
			$insert.addClass('macro-' + this.name + '-in');
		}

		if (this.name === 'hoverprepend') {
			$insert.insertBefore($hover);
		}
		else {
			$insert.insertAfter($hover);
		}
	}
});

 

Welcome to Twine Q&A, where you can ask questions and receive answers from other members of the community.

You can also find hints and information on Twine on the official wiki and the old forums archive.

See a spam question? Flag it instead of downvoting. A question flagged enough times will automatically be hidden while moderators review it.
...